From 67978e4eb3020bd81da47609a216be33ad4d6a8d Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Mon, 6 Jul 2026 15:03:47 +0530 Subject: [PATCH] fix(cli): honor explicit --entry under auto build-type; report loaded config path Local 'app build'/'app run' with an explicit --entry and --build-type auto silently let framework detection (e.g. Next.js) override the entrypoint, contradicting deploy's resolution and the assertSupportedEntrypoint contract ('auto may fall back to Bun'). Resolve an explicit entrypoint to a Bun build before detection, matching 'app deploy'. Also report deploySettings.config.path as the compute config file in effect whenever one loaded, even without a build block, so 'path: null' means 'no config loaded' rather than 'no build-settings block'; status still conveys build-block ownership. --- docs/product/command-spec.md | 5 ++- packages/cli/src/controllers/app.ts | 14 ++++++- packages/cli/src/lib/app/build.ts | 11 +++++- packages/cli/tests/app-build.test.ts | 28 ++++++++++++++ packages/cli/tests/app-controller.test.ts | 6 +++ packages/cli/tests/app-local-dev.test.ts | 47 +++++++++++++++++++++++ 6 files changed, 107 insertions(+), 4 deletions(-) diff --git a/docs/product/command-spec.md b/docs/product/command-spec.md index a72eb3e..2cb0705 100644 --- a/docs/product/command-spec.md +++ b/docs/product/command-spec.md @@ -1217,7 +1217,7 @@ Purpose: Behavior: - resolves the optional `[app]` target, app root, framework, and entrypoint from `prisma.compute.ts` exactly like `app deploy`; explicit `--entry` and a non-`auto` `--build-type` override the config -- detects supported project shapes when `--build-type auto` is used and no config framework applies +- detects supported project shapes when `--build-type auto` is used and no config framework applies; an explicit `--entry` targets a Bun build and wins over that detection, exactly like `app deploy` resolves `--entry` - supports Bun, Next.js, Nuxt, Astro, NestJS, TanStack Start, and custom artifact app builds in the beta package - fails with `USAGE_ERROR` when framework detection is ambiguous @@ -1244,7 +1244,7 @@ Behavior: - resolves the optional `[app]` target, app root, framework, entrypoint, and port from `prisma.compute.ts` exactly like `app deploy`; explicit `--entry`, `--port`, and a non-`auto` `--build-type` override the config - fails with `USAGE_ERROR` when the configured framework has no local dev server in the current preview -- detects supported project shapes when `--build-type auto` is used and no config framework applies +- detects supported project shapes when `--build-type auto` is used and no config framework applies; an explicit `--entry` targets a Bun app and wins over that detection, exactly like `app deploy` resolves `--entry` - starts the local framework command - reports `RUN_FAILED` when the local process cannot start or exits unsuccessfully @@ -1306,6 +1306,7 @@ same reasons they are excluded today. - `[app]` without any compute config file is a usage error - a config that fails to load or validate fails with `COMPUTE_CONFIG_INVALID` before any remote work - settings sourced from the config are annotated `set by prisma.compute.ts` in human output and deploy settings metadata +- `deploySettings.config.path` reports the compute config file in effect whenever one loaded, even when it has no `build` block, so `path: null` means "no config loaded" rather than "no build block"; `deploySettings.config.status` stays `"config"` when the build block owned the build settings and `"inferred"` when they came from framework defaults ```ts import { defineComputeConfig } from "@prisma/compute-sdk/config"; diff --git a/packages/cli/src/controllers/app.ts b/packages/cli/src/controllers/app.ts index 73040ef..ed8cde4 100644 --- a/packages/cli/src/controllers/app.ts +++ b/packages/cli/src/controllers/app.ts @@ -328,6 +328,7 @@ export async function runAppRun( requestedBuildType: buildType, configFramework: compute.target?.framework ?? null, appDir, + entrypoint: merged.entrypoint, }); // Hono apps get the same src/index.ts entrypoint default as deploy. const entrypoint = @@ -852,7 +853,11 @@ async function runSingleAppDeploy( promoted: deployResult.promoted, deploySettings: { config: { - path: buildSettingsResolution.relativeConfigPath, + // The compute config in effect, even when it has no build block, so + // `path: null` means "no config loaded" rather than "no build-settings + // block". `status` still says whether the build block owned the + // build settings ("config") or they were inferred ("inferred"). + path: computeConfig.config?.relativeConfigPath ?? null, status: buildSettingsResolution.status, }, buildCommand: { @@ -4641,8 +4646,15 @@ async function resolveLocalRunFramework( requestedBuildType: AppBuildType; configFramework: ComputeFramework | null; appDir: string; + entrypoint?: string; }, ): Promise { + // An explicit entrypoint targets a Bun app, so honor it over framework + // auto-detection, matching deploy and local build. + if (options.requestedBuildType === "auto" && options.entrypoint) { + return frameworkFromUserFacingValue("bun", "set by --entry"); + } + if ( (LOCAL_DEV_BUILD_TYPES as readonly string[]).includes( options.requestedBuildType, diff --git a/packages/cli/src/lib/app/build.ts b/packages/cli/src/lib/app/build.ts index 7b7c03e..50cdee8 100644 --- a/packages/cli/src/lib/app/build.ts +++ b/packages/cli/src/lib/app/build.ts @@ -146,12 +146,21 @@ export async function resolveAppBuildStrategy(options: { strategy: BuildStrategy; buildType: ResolvedAppBuildType; }> { + // An explicit entrypoint targets a Bun build, so honor it over framework + // auto-detection instead of letting a detected framework (e.g. Next.js) + // silently ignore --entry. This mirrors how deploy resolves --entry and the + // "auto may fall back to Bun" contract in assertSupportedEntrypoint. + const buildType = + options.buildType === "auto" && options.entrypoint + ? "bun" + : options.buildType; + // Detection, per-framework construction, and Bun entrypoint resolution // (package.json `main`) all live in the SDK now; the CLI forwards. The CLI's // build types map 1:1 to the SDK's, and `auto` is the SDK default. return resolveBuildStrategy({ appPath: options.appPath, - buildType: options.buildType, + buildType, entrypoint: options.entrypoint, buildSettings: options.buildSettings, signal: options.signal, diff --git a/packages/cli/tests/app-build.test.ts b/packages/cli/tests/app-build.test.ts index 39b6876..1b80a38 100644 --- a/packages/cli/tests/app-build.test.ts +++ b/packages/cli/tests/app-build.test.ts @@ -674,6 +674,34 @@ describe("preview build strategy", () => { }); }); + it("resolves an explicit entrypoint to Bun even when Next.js is detectable", async () => { + const { resolveAppBuildStrategy } = await import("../src/lib/app/build"); + const cwd = await createTempCwd(); + const appPath = path.join(cwd, "app"); + + await mkdir(appPath, { recursive: true }); + await writeFile( + path.join(appPath, "package.json"), + `${JSON.stringify({ dependencies: { next: "15.0.0" } }, null, 2)}\n`, + "utf8", + ); + await writeFile( + path.join(appPath, "server.ts"), + "export default { fetch: () => new Response('ok') };\n", + "utf8", + ); + + await expect( + resolveAppBuildStrategy({ + appPath, + entrypoint: "server.ts", + buildType: "auto", + }), + ).resolves.toMatchObject({ + buildType: "bun", + }); + }); + it("runs package.json build scripts before staging Next.js output", async () => { const cwd = await createTempCwd(); const appPath = path.join(cwd, "app"); diff --git a/packages/cli/tests/app-controller.test.ts b/packages/cli/tests/app-controller.test.ts index 1eafbcb..09a2ecc 100644 --- a/packages/cli/tests/app-controller.test.ts +++ b/packages/cli/tests/app-controller.test.ts @@ -455,6 +455,12 @@ describe("app controller", () => { expect(asSingleDeployResult(result).result.deploySettings.region).toBe( "us-west-1", ); + // A loaded config with no build block still reports its path; only the + // build-settings ownership stays "inferred". + expect(asSingleDeployResult(result).result.deploySettings.config).toEqual({ + path: "prisma.compute.ts", + status: "inferred", + }); }); it("uses --region when creating a new app", async () => { diff --git a/packages/cli/tests/app-local-dev.test.ts b/packages/cli/tests/app-local-dev.test.ts index 07a12f5..a4b83bd 100644 --- a/packages/cli/tests/app-local-dev.test.ts +++ b/packages/cli/tests/app-local-dev.test.ts @@ -480,4 +480,51 @@ describe("app local dev commands", () => { command: "bun --watch server.ts", }); }); + + it("run resolves an explicit entrypoint to Bun even when Next.js is detectable", async () => { + const runLocalApp = vi.fn().mockResolvedValue({ + framework: "bun", + entrypoint: "server.ts", + port: 3000, + command: "bun --watch server.ts", + exitCode: 0, + signal: null, + }); + + vi.doMock("../src/lib/app/local-dev", async () => { + const actual = await vi.importActual< + typeof import("../src/lib/app/local-dev") + >("../src/lib/app/local-dev"); + return { + ...actual, + runLocalApp, + }; + }); + + const { createTempCwd, createTestCommandContext } = await import( + "./helpers" + ); + const { runAppRun } = await import("../src/controllers/app"); + const cwd = await createTempCwd(); + await writeFile( + path.join(cwd, "package.json"), + `${JSON.stringify({ dependencies: { next: "15.0.0" } }, null, 2)}\n`, + "utf8", + ); + await writeFile( + path.join(cwd, "server.ts"), + "export default { fetch: () => new Response('ok') };\n", + "utf8", + ); + const { context } = await createTestCommandContext({ + cwd, + stateDir: path.join(cwd, ".state"), + }); + + await runAppRun(context, { entrypoint: "server.ts", buildType: "auto" }); + + expect(runLocalApp).toHaveBeenCalledWith( + expect.objectContaining({ buildType: "bun", entrypoint: "server.ts" }), + ); + }); });