diff --git a/app/api/playground/copy-repo/route.ts b/app/api/playground/copy-repo/route.ts index 5a55a0b17..6eadcfa9a 100644 --- a/app/api/playground/copy-repo/route.ts +++ b/app/api/playground/copy-repo/route.ts @@ -1,6 +1,9 @@ import { NextRequest, NextResponse } from "next/server" -import { copyRepoToExistingContainer } from "@/lib/utils/container" +import { + copyRepoToExistingContainer, + startDevServerInContainer, +} from "@/lib/utils/container" import { setupLocalRepository } from "@/lib/utils/utils-server" export async function POST(req: NextRequest) { @@ -39,6 +42,17 @@ export async function POST(req: NextRequest) { ) } + // Start a dev server in the background inside the container + try { + await startDevServerInContainer({ containerName, mountPath }) + } catch (err) { + // Do not fail the whole request if dev server fails to start; surface as warning + return NextResponse.json( + { error: `Copied repo but failed to start dev server: ${err}` }, + { status: 202 } + ) + } + return NextResponse.json({ success: true }) } catch (err) { return NextResponse.json( diff --git a/lib/utils/container.ts b/lib/utils/container.ts index a6714cbad..9878619e7 100644 --- a/lib/utils/container.ts +++ b/lib/utils/container.ts @@ -366,3 +366,42 @@ export async function copyRepoToExistingContainer({ throw new Error(`Failed to copy repository to container: ${e}`) } } + +/** + * Start a dev server inside an existing container for the app in `mountPath`. + * This determines the appropriate package manager based on lockfiles and then + * runs install and dev in the background using nohup so this call can return + * immediately. Logs are written to .dev.log and PID to .dev.pid under mountPath. + */ +export async function startDevServerInContainer({ + containerName, + mountPath = "/workspace", +}: { + containerName: string + mountPath?: string +}): Promise<{ stdout: string; stderr: string; exitCode: number }> { + // Build a shell script that: + // - chooses package manager (pnpm > yarn > npm) + // - runs install (preferring frozen/ci if lockfile present) + // - starts dev server + // - backgrounds the process with nohup and stores logs/pid + const script = [ + "set -e", + `cd ${mountPath}`, + // Pick package manager + "if [ -f pnpm-lock.yaml ]; then PM=pnpm; elif [ -f yarn.lock ]; then PM=yarn; else PM=npm; fi", + // Construct install command + 'if [ "$PM" = "pnpm" ]; then INSTALL="pnpm install --frozen-lockfile || pnpm install"; ' + + 'elif [ "$PM" = "yarn" ]; then INSTALL="yarn install --frozen-lockfile || yarn install"; ' + + 'else INSTALL="if [ -f package-lock.json ]; then npm ci || npm i; else npm i; fi"; fi', + // Start dev in background after install completes + 'nohup sh -c "$INSTALL && "$PM" run -s dev" > .dev.log 2>&1 & echo $! > .dev.pid', + 'echo "Dev server starting with $PM. Logs: .dev.log, PID: $(cat .dev.pid)"', + ].join(" && ") + + return await execInContainerWithDockerode({ + name: containerName, + command: script, + cwd: mountPath, + }) +} diff --git a/shared/src/lib/utils/container.ts b/shared/src/lib/utils/container.ts index 9a072358a..e45998791 100644 --- a/shared/src/lib/utils/container.ts +++ b/shared/src/lib/utils/container.ts @@ -362,3 +362,42 @@ export async function copyRepoToExistingContainer({ await chownExec.start({}) } } + +/** + * Start a dev server inside an existing container for the app in `mountPath`. + * This determines the appropriate package manager based on lockfiles and then + * runs install and dev in the background using nohup so this call can return + * immediately. Logs are written to .dev.log and PID to .dev.pid under mountPath. + */ +export async function startDevServerInContainer({ + containerName, + mountPath = "/workspace", +}: { + containerName: string + mountPath?: string +}): Promise<{ stdout: string; stderr: string; exitCode: number }> { + // Build a shell script that: + // - chooses package manager (pnpm > yarn > npm) + // - runs install (preferring frozen/ci if lockfile present) + // - starts dev server + // - backgrounds the process with nohup and stores logs/pid + const script = [ + "set -e", + `cd ${mountPath}`, + // Pick package manager + "if [ -f pnpm-lock.yaml ]; then PM=pnpm; elif [ -f yarn.lock ]; then PM=yarn; else PM=npm; fi", + // Construct install command + 'if [ "$PM" = "pnpm" ]; then INSTALL="pnpm install --frozen-lockfile || pnpm install"; ' + + 'elif [ "$PM" = "yarn" ]; then INSTALL="yarn install --frozen-lockfile || yarn install"; ' + + 'else INSTALL="if [ -f package-lock.json ]; then npm ci || npm i; else npm i; fi"; fi', + // Start dev in background after install completes + 'nohup sh -c "$INSTALL && "$PM" run -s dev" > .dev.log 2>&1 & echo $! > .dev.pid', + 'echo "Dev server starting with $PM. Logs: .dev.log, PID: $(cat .dev.pid)"', + ].join(" && ") + + return await execInContainerWithDockerode({ + name: containerName, + command: script, + cwd: mountPath, + }) +}