From 7eaab0934a5234fb81332d499390749e02a8dd21 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Thu, 1 Oct 2026 13:20:17 +0100 Subject: [PATCH 1/5] fix: preview setup during deploy dry runs --- .changeset/dry-run-autoconfig-preview.md | 7 +++ .../__tests__/commands/deploy/index.test.ts | 37 ++++++++++++++ .../src/__tests__/commands/migrate.test.ts | 19 +++++++ .../commands/triggers-deploy.test.ts | 21 ++++++++ .../lib/autoconfig-migration.test.ts | 50 +++++++++++++++++++ .../commands/_generated/_meta/commands.json | 6 +-- .../_meta/hand-written-commands.json | 6 +-- packages/cli/src/commands/build/index.ts | 20 ++++++-- packages/cli/src/commands/deploy/meta.json | 2 +- packages/cli/src/commands/deploy/shared.ts | 11 +++- .../src/commands/workers/triggers/deploy.ts | 12 ++++- .../src/commands/workers/triggers/meta.json | 2 +- .../workers/versions/create/meta.json | 2 +- packages/cli/src/lib/autoconfig.ts | 21 ++++++-- packages/cli/src/lib/wrangler-migration.ts | 5 +- 15 files changed, 199 insertions(+), 22 deletions(-) create mode 100644 .changeset/dry-run-autoconfig-preview.md diff --git a/.changeset/dry-run-autoconfig-preview.md b/.changeset/dry-run-autoconfig-preview.md new file mode 100644 index 000000000..157d571fa --- /dev/null +++ b/.changeset/dry-run-autoconfig-preview.md @@ -0,0 +1,7 @@ +--- +"cf": patch +--- + +Preview project setup during deploy dry runs + +Pass `--dry-run` to autoconfiguration and Wrangler migration for `cf deploy`, `cf workers versions create`, and `cf workers triggers deploy`. If setup is needed, show the preview and skip the build and upload. diff --git a/packages/cli/src/__tests__/commands/deploy/index.test.ts b/packages/cli/src/__tests__/commands/deploy/index.test.ts index df4bdbd99..bee422cf0 100644 --- a/packages/cli/src/__tests__/commands/deploy/index.test.ts +++ b/packages/cli/src/__tests__/commands/deploy/index.test.ts @@ -1,3 +1,4 @@ +import { existsSync, readFileSync } from "node:fs"; import { mockConsoleMethods, runInTempDir, @@ -1137,6 +1138,42 @@ describe("cf deploy", () => { }); describe("--dry-run", () => { + it("previews setup without changing an existing Astro project or building", async () => { + const packageJson = JSON.stringify({ + name: "astro-project", + scripts: { deploy: "astro build && wrangler deploy" }, + dependencies: { + astro: "7.3.5", + "@astrojs/cloudflare": "14.3.3", + }, + devDependencies: { cf: "1.0.0-beta.6", wrangler: "^4.142.0" }, + }); + const tsconfig = '{"include":["src/**/*"]}\n'; + const lockfile = "existing lockfile\n"; + const requests = recordRequests(); + await seed({ + "package.json": packageJson, + "package-lock.json": lockfile, + "tsconfig.json": tsconfig, + "wrangler.jsonc": '{"name":"astro-project"}', + "node_modules/astro/package.json": JSON.stringify({ + name: "astro", + version: "7.3.5", + }), + }); + + const { exitCode } = await runCf(["deploy", "--dry-run"]); + + expect(exitCode).toBe(0); + expect(readFileSync("package.json", "utf8")).toBe(packageJson); + expect(readFileSync("package-lock.json", "utf8")).toBe(lockfile); + expect(readFileSync("tsconfig.json", "utf8")).toBe(tsconfig); + expect(existsSync("public/.assetsignore")).toBe(false); + expect(buildDelegateWasCalled()).toBe(false); + expect(requests).toEqual([]); + expect(std.out).toContain("Autoconfig process run in dry-run mode"); + }); + it("does not upload the worker", async () => { const upload = mockWorkerUpload(); await seed({ diff --git a/packages/cli/src/__tests__/commands/migrate.test.ts b/packages/cli/src/__tests__/commands/migrate.test.ts index 566b9c459..db96aa8cd 100644 --- a/packages/cli/src/__tests__/commands/migrate.test.ts +++ b/packages/cli/src/__tests__/commands/migrate.test.ts @@ -191,6 +191,25 @@ describe("cf migrate", () => { ); }); + it("previews an accepted project workflow migration during a dry run", async () => { + await seed({ "wrangler.jsonc": "{}" }); + const confirmMigration = vi.fn().mockResolvedValue(true); + + await expect( + maybeMigrateWranglerProject( + process.cwd(), + confirmMigration, + "stdout", + true + ) + ).resolves.toBe(true); + + expect(migrateWranglerToCf).toHaveBeenCalledWith( + path.join(process.cwd(), "wrangler.jsonc"), + expect.objectContaining({ dryRun: true }) + ); + }); + it("uses Vite for automatic migration when the plugin is declared", async () => { await seed({ "config/package.json": JSON.stringify({ diff --git a/packages/cli/src/__tests__/commands/triggers-deploy.test.ts b/packages/cli/src/__tests__/commands/triggers-deploy.test.ts index 47b6bae48..3a49b0b09 100644 --- a/packages/cli/src/__tests__/commands/triggers-deploy.test.ts +++ b/packages/cli/src/__tests__/commands/triggers-deploy.test.ts @@ -83,6 +83,27 @@ describe("cf workers triggers deploy", () => { expect(requests).toEqual([]); }); + it("stops after previewing project setup during a dry run", async () => { + const requests = recordRequests(); + await seed({ + "package.json": JSON.stringify({ + name: "astro-project", + dependencies: { astro: "7.3.5" }, + }), + "node_modules/astro/package.json": JSON.stringify({ + name: "astro", + version: "7.3.5", + }), + }); + + const { exitCode } = await runCf([...TRIGGERS_DEPLOY_COMMAND, "--dry-run"]); + + expect(exitCode).toBe(0); + expect(buildDelegateWasCalled()).toBe(false); + expect(requests).toEqual([]); + expect(std.out).toContain("Autoconfig process run in dry-run mode"); + }); + it("builds and deploys scheduled triggers with --local=false", async () => { let schedulesBody: unknown; msw.use( diff --git a/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts b/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts index 1180b7eaa..3affbec8f 100644 --- a/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts +++ b/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts @@ -42,6 +42,19 @@ describe("project preparation", () => { expect(mocks.maybeMigrateWranglerProject).not.toHaveBeenCalled(); }); + it("does not preview setup for a configured project during a dry run", async () => { + const configuredDetails = { ...unconfiguredDetails, configured: true }; + mocks.getDetailsForAutoConfig.mockResolvedValue(configuredDetails); + + await expect(prepareProject("/project", { dryRun: true })).resolves.toEqual( + { + details: configuredDetails, + } + ); + expect(mocks.maybeMigrateWranglerProject).not.toHaveBeenCalled(); + expect(mocks.runAutoConfig).not.toHaveBeenCalled(); + }); + it("offers migration even when autoconfig cannot analyze the legacy project", async () => { const configuredDetails = { ...unconfiguredDetails, @@ -83,4 +96,41 @@ describe("project preparation", () => { }); expect(mocks.runAutoConfig).toHaveBeenCalledOnce(); }); + + it("previews autoconfig without applying setup during a dry run", async () => { + mocks.getDetailsForAutoConfig.mockResolvedValue(unconfiguredDetails); + mocks.maybeMigrateWranglerProject.mockResolvedValue(false); + mocks.runAutoConfig.mockResolvedValue({ buildCommand: "npm run build" }); + + await expect( + prepareProject("/project", { dryRun: true }) + ).resolves.toMatchObject({ + details: unconfiguredDetails, + setupPreviewed: true, + }); + expect(mocks.runAutoConfig).toHaveBeenCalledWith( + unconfiguredDetails, + expect.objectContaining({ dryRun: true, runBuild: false }) + ); + expect(mocks.maybeMigrateWranglerProject).toHaveBeenCalledWith( + "/project", + expect.any(Function), + undefined, + true + ); + }); + + it("stops after previewing a Wrangler migration", async () => { + mocks.getDetailsForAutoConfig.mockResolvedValue(unconfiguredDetails); + mocks.maybeMigrateWranglerProject.mockResolvedValue(true); + + await expect(prepareProject("/project", { dryRun: true })).resolves.toEqual( + { + details: unconfiguredDetails, + setupPreviewed: true, + } + ); + expect(mocks.getDetailsForAutoConfig).toHaveBeenCalledOnce(); + expect(mocks.runAutoConfig).not.toHaveBeenCalled(); + }); }); diff --git a/packages/cli/src/commands/_generated/_meta/commands.json b/packages/cli/src/commands/_generated/_meta/commands.json index 36c2437f5..ece5395e8 100644 --- a/packages/cli/src/commands/_generated/_meta/commands.json +++ b/packages/cli/src/commands/_generated/_meta/commands.json @@ -42881,7 +42881,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Build a project and run checks without actually uploading the Worker", + "description": "Preview setup if needed; otherwise build and check without uploading the Worker", "default": false }, { @@ -131552,7 +131552,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Run checks without applying triggers", + "description": "Preview setup if needed; otherwise build and check without applying triggers", "default": false }, { @@ -131619,7 +131619,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Build a project and run checks without actually uploading the Worker", + "description": "Preview setup if needed; otherwise build and check without uploading the Worker", "default": false }, { diff --git a/packages/cli/src/commands/_generated/_meta/hand-written-commands.json b/packages/cli/src/commands/_generated/_meta/hand-written-commands.json index cfd3584db..6166fa70d 100644 --- a/packages/cli/src/commands/_generated/_meta/hand-written-commands.json +++ b/packages/cli/src/commands/_generated/_meta/hand-written-commands.json @@ -898,7 +898,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Build a project and run checks without actually uploading the Worker", + "description": "Preview setup if needed; otherwise build and check without uploading the Worker", "default": false }, { @@ -1562,7 +1562,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Run checks without applying triggers", + "description": "Preview setup if needed; otherwise build and check without applying triggers", "default": false }, { @@ -1641,7 +1641,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Build a project and run checks without actually uploading the Worker", + "description": "Preview setup if needed; otherwise build and check without uploading the Worker", "default": false }, { diff --git a/packages/cli/src/commands/build/index.ts b/packages/cli/src/commands/build/index.ts index 85c7cf1c6..1105dfb5d 100644 --- a/packages/cli/src/commands/build/index.ts +++ b/packages/cli/src/commands/build/index.ts @@ -24,16 +24,29 @@ interface RunBuildOptions extends CommandOutputOptions { // Validate the Worker the caller will consume, so an invalid default // Worker cannot block a workflow that selected another one. worker?: string; + dryRun?: boolean; } export async function runBuild( mode?: string, - { worker: selectedWorker, ...options }: RunBuildOptions = {}, + { worker: selectedWorker, dryRun = false, ...options }: RunBuildOptions = {}, ctx: { isPreview?: boolean } = {} -): Promise { +): Promise<"built" | "setup-previewed"> { const output = options.output ?? "stdout"; const cwd = process.cwd(); - const { details, configuration } = await prepareProject(cwd, options); + const { details, configuration, setupPreviewed } = await prepareProject(cwd, { + ...options, + dryRun, + }); + if (setupPreviewed) { + if (output !== "silent") { + clack.log.message("Build skipped because project setup was previewed.", { + spacing: 0, + output: output === "stderr" ? process.stderr : undefined, + }); + } + return "setup-previewed"; + } const buildCommand = configuration?.buildCommand ?? details?.buildCommand; const env: Record = { ...details?.env, @@ -91,6 +104,7 @@ export async function runBuild( output: output === "stderr" ? process.stderr : undefined, }); } + return "built"; } function formatImplName(discovered: DiscoveredImpl): string { diff --git a/packages/cli/src/commands/deploy/meta.json b/packages/cli/src/commands/deploy/meta.json index b6b556940..593fac18e 100644 --- a/packages/cli/src/commands/deploy/meta.json +++ b/packages/cli/src/commands/deploy/meta.json @@ -37,7 +37,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Build a project and run checks without actually uploading the Worker", + "description": "Preview setup if needed; otherwise build and check without uploading the Worker", "default": false }, { diff --git a/packages/cli/src/commands/deploy/shared.ts b/packages/cli/src/commands/deploy/shared.ts index cb796e21f..aa6259145 100644 --- a/packages/cli/src/commands/deploy/shared.ts +++ b/packages/cli/src/commands/deploy/shared.ts @@ -67,7 +67,7 @@ export function sharedUploadBuilder(yargs: Argv) { }) .option("dry-run", { describe: - "Build a project and run checks without actually uploading the Worker", + "Preview setup if needed; otherwise build and check without uploading the Worker", type: "boolean", default: false, }) @@ -93,7 +93,14 @@ type UploadArgs = SharedUploadArgs & { export async function runUpload(argv: UploadArgs, ctx: UploadCommand) { // Delegate the build before applying cf's dotenv values. if (!argv.prebuilt) { - await runBuild(argv.mode, { worker: argv.worker }); + const build = await runBuild(argv.mode, { + worker: argv.worker, + dryRun: argv["dry-run"], + }); + if (build === "setup-previewed") { + clack.log.success("Dry run complete"); + return; + } clack.log.message("", { spacing: 0 }); } diff --git a/packages/cli/src/commands/workers/triggers/deploy.ts b/packages/cli/src/commands/workers/triggers/deploy.ts index d8ddb15de..e0d300cbd 100644 --- a/packages/cli/src/commands/workers/triggers/deploy.ts +++ b/packages/cli/src/commands/workers/triggers/deploy.ts @@ -33,7 +33,8 @@ function builder(yargs: Argv) { }) .option("dry-run", { type: "boolean", - description: "Run checks without applying triggers", + description: + "Preview setup if needed; otherwise build and check without applying triggers", default: false, }) .option("worker", buildOutputWorkerOption); @@ -56,7 +57,14 @@ const triggersDeployCommand: CommandModule< } if (!argv.prebuilt) { - await runBuild(argv.mode, { worker: argv.worker }); + const build = await runBuild(argv.mode, { + worker: argv.worker, + dryRun: argv["dry-run"], + }); + if (build === "setup-previewed") { + clack.log.success("Dry run complete"); + return; + } clack.log.message("", { spacing: 0 }); } diff --git a/packages/cli/src/commands/workers/triggers/meta.json b/packages/cli/src/commands/workers/triggers/meta.json index 7dccc9715..871a6ca25 100644 --- a/packages/cli/src/commands/workers/triggers/meta.json +++ b/packages/cli/src/commands/workers/triggers/meta.json @@ -19,7 +19,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Run checks without applying triggers", + "description": "Preview setup if needed; otherwise build and check without applying triggers", "default": false }, { diff --git a/packages/cli/src/commands/workers/versions/create/meta.json b/packages/cli/src/commands/workers/versions/create/meta.json index c3b7dfa49..3b8f3319c 100644 --- a/packages/cli/src/commands/workers/versions/create/meta.json +++ b/packages/cli/src/commands/workers/versions/create/meta.json @@ -36,7 +36,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Build a project and run checks without actually uploading the Worker", + "description": "Preview setup if needed; otherwise build and check without uploading the Worker", "default": false }, { diff --git a/packages/cli/src/lib/autoconfig.ts b/packages/cli/src/lib/autoconfig.ts index 1059262e2..10826fa42 100644 --- a/packages/cli/src/lib/autoconfig.ts +++ b/packages/cli/src/lib/autoconfig.ts @@ -24,6 +24,10 @@ export interface CommandOutputOptions { output?: CommandOutput; } +interface ProjectPreparationOptions extends CommandOutputOptions { + dryRun?: boolean; +} + export interface RunProjectCommandOptions extends CommandOutputOptions { env?: Readonly>; args?: readonly string[]; @@ -79,21 +83,23 @@ export async function analyzeProject( export async function configureProject( details: AutoConfigDetails, - options: CommandOutputOptions = {} + options: ProjectPreparationOptions = {} ): Promise { return runAutoConfig(details, { target: "cf", context: createAutoConfigContext(options), + dryRun: options.dryRun, runBuild: false, }); } export async function prepareProject( cwd: string, - options: CommandOutputOptions = {} + options: ProjectPreparationOptions = {} ): Promise<{ details: AutoConfigDetails | undefined; configuration?: AutoConfigSummary; + setupPreviewed?: true; }> { let details = await analyzeProject(cwd, options); if (details?.configured) { @@ -105,9 +111,13 @@ export async function prepareProject( await maybeMigrateWranglerProject( cwd, (text, confirmOptions) => context.dialogs.confirm(text, confirmOptions), - options.output + options.output, + options.dryRun ) ) { + if (options.dryRun) { + return { details, setupPreviewed: true }; + } details = await analyzeProject(cwd, options); return { details }; } @@ -115,7 +125,10 @@ export async function prepareProject( return { details, ...(details - ? { configuration: await configureProject(details, options) } + ? { + configuration: await configureProject(details, options), + ...(options.dryRun ? { setupPreviewed: true as const } : {}), + } : {}), }; } diff --git a/packages/cli/src/lib/wrangler-migration.ts b/packages/cli/src/lib/wrangler-migration.ts index c132e8638..9be87bf71 100644 --- a/packages/cli/src/lib/wrangler-migration.ts +++ b/packages/cli/src/lib/wrangler-migration.ts @@ -195,7 +195,8 @@ export async function runWranglerMigration( export async function maybeMigrateWranglerProject( projectPath: string, confirmMigration: ConfirmMigration, - output: MigrationOutput = "stdout" + output: MigrationOutput = "stdout", + dryRun = false ): Promise { const configPath = await findWranglerConfig(projectPath, { failOnMultiple: false, @@ -218,6 +219,6 @@ export async function maybeMigrateWranglerProject( } const bundler = await detectWranglerMigrationBundler(configPath); - await runWranglerMigration(configPath, { bundler, output }); + await runWranglerMigration(configPath, { bundler, output, dryRun }); return true; } From 0fbdb414c1cf8db8429482e55889468531faae67 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Fri, 2 Oct 2026 11:26:35 +0100 Subject: [PATCH 2/5] refactor: call out setup needed during dry runs --- .../cli/src/__tests__/lib/autoconfig-migration.test.ts | 4 ++-- packages/cli/src/commands/build/index.ts | 8 ++++---- packages/cli/src/commands/deploy/shared.ts | 2 +- packages/cli/src/commands/workers/triggers/deploy.ts | 2 +- packages/cli/src/lib/autoconfig.ts | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts b/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts index 3affbec8f..4c15a41f7 100644 --- a/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts +++ b/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts @@ -106,7 +106,7 @@ describe("project preparation", () => { prepareProject("/project", { dryRun: true }) ).resolves.toMatchObject({ details: unconfiguredDetails, - setupPreviewed: true, + setupNeeded: true, }); expect(mocks.runAutoConfig).toHaveBeenCalledWith( unconfiguredDetails, @@ -127,7 +127,7 @@ describe("project preparation", () => { await expect(prepareProject("/project", { dryRun: true })).resolves.toEqual( { details: unconfiguredDetails, - setupPreviewed: true, + setupNeeded: true, } ); expect(mocks.getDetailsForAutoConfig).toHaveBeenCalledOnce(); diff --git a/packages/cli/src/commands/build/index.ts b/packages/cli/src/commands/build/index.ts index 1105dfb5d..bf553d99e 100644 --- a/packages/cli/src/commands/build/index.ts +++ b/packages/cli/src/commands/build/index.ts @@ -31,21 +31,21 @@ export async function runBuild( mode?: string, { worker: selectedWorker, dryRun = false, ...options }: RunBuildOptions = {}, ctx: { isPreview?: boolean } = {} -): Promise<"built" | "setup-previewed"> { +): Promise<"built" | "setup-needed"> { const output = options.output ?? "stdout"; const cwd = process.cwd(); - const { details, configuration, setupPreviewed } = await prepareProject(cwd, { + const { details, configuration, setupNeeded } = await prepareProject(cwd, { ...options, dryRun, }); - if (setupPreviewed) { + if (setupNeeded) { if (output !== "silent") { clack.log.message("Build skipped because project setup was previewed.", { spacing: 0, output: output === "stderr" ? process.stderr : undefined, }); } - return "setup-previewed"; + return "setup-needed"; } const buildCommand = configuration?.buildCommand ?? details?.buildCommand; const env: Record = { diff --git a/packages/cli/src/commands/deploy/shared.ts b/packages/cli/src/commands/deploy/shared.ts index aa6259145..76ff7e9fc 100644 --- a/packages/cli/src/commands/deploy/shared.ts +++ b/packages/cli/src/commands/deploy/shared.ts @@ -97,7 +97,7 @@ export async function runUpload(argv: UploadArgs, ctx: UploadCommand) { worker: argv.worker, dryRun: argv["dry-run"], }); - if (build === "setup-previewed") { + if (build === "setup-needed") { clack.log.success("Dry run complete"); return; } diff --git a/packages/cli/src/commands/workers/triggers/deploy.ts b/packages/cli/src/commands/workers/triggers/deploy.ts index e0d300cbd..4120776f9 100644 --- a/packages/cli/src/commands/workers/triggers/deploy.ts +++ b/packages/cli/src/commands/workers/triggers/deploy.ts @@ -61,7 +61,7 @@ const triggersDeployCommand: CommandModule< worker: argv.worker, dryRun: argv["dry-run"], }); - if (build === "setup-previewed") { + if (build === "setup-needed") { clack.log.success("Dry run complete"); return; } diff --git a/packages/cli/src/lib/autoconfig.ts b/packages/cli/src/lib/autoconfig.ts index 10826fa42..dae47df75 100644 --- a/packages/cli/src/lib/autoconfig.ts +++ b/packages/cli/src/lib/autoconfig.ts @@ -99,7 +99,7 @@ export async function prepareProject( ): Promise<{ details: AutoConfigDetails | undefined; configuration?: AutoConfigSummary; - setupPreviewed?: true; + setupNeeded?: true; }> { let details = await analyzeProject(cwd, options); if (details?.configured) { @@ -116,7 +116,7 @@ export async function prepareProject( ) ) { if (options.dryRun) { - return { details, setupPreviewed: true }; + return { details, setupNeeded: true }; } details = await analyzeProject(cwd, options); return { details }; @@ -127,7 +127,7 @@ export async function prepareProject( ...(details ? { configuration: await configureProject(details, options), - ...(options.dryRun ? { setupPreviewed: true as const } : {}), + ...(options.dryRun ? { setupNeeded: true as const } : {}), } : {}), }; From 453b52d19b320ebadba361ef77cbc581dc2b03c7 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Fri, 2 Oct 2026 11:31:46 +0100 Subject: [PATCH 3/5] docs: restore deploy dry-run flag descriptions --- packages/cli/src/commands/_generated/_meta/commands.json | 6 +++--- .../commands/_generated/_meta/hand-written-commands.json | 6 +++--- packages/cli/src/commands/deploy/meta.json | 2 +- packages/cli/src/commands/deploy/shared.ts | 2 +- packages/cli/src/commands/workers/triggers/deploy.ts | 3 +-- packages/cli/src/commands/workers/triggers/meta.json | 2 +- packages/cli/src/commands/workers/versions/create/meta.json | 2 +- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/_generated/_meta/commands.json b/packages/cli/src/commands/_generated/_meta/commands.json index ece5395e8..36c2437f5 100644 --- a/packages/cli/src/commands/_generated/_meta/commands.json +++ b/packages/cli/src/commands/_generated/_meta/commands.json @@ -42881,7 +42881,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without uploading the Worker", + "description": "Build a project and run checks without actually uploading the Worker", "default": false }, { @@ -131552,7 +131552,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without applying triggers", + "description": "Run checks without applying triggers", "default": false }, { @@ -131619,7 +131619,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without uploading the Worker", + "description": "Build a project and run checks without actually uploading the Worker", "default": false }, { diff --git a/packages/cli/src/commands/_generated/_meta/hand-written-commands.json b/packages/cli/src/commands/_generated/_meta/hand-written-commands.json index 6166fa70d..cfd3584db 100644 --- a/packages/cli/src/commands/_generated/_meta/hand-written-commands.json +++ b/packages/cli/src/commands/_generated/_meta/hand-written-commands.json @@ -898,7 +898,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without uploading the Worker", + "description": "Build a project and run checks without actually uploading the Worker", "default": false }, { @@ -1562,7 +1562,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without applying triggers", + "description": "Run checks without applying triggers", "default": false }, { @@ -1641,7 +1641,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without uploading the Worker", + "description": "Build a project and run checks without actually uploading the Worker", "default": false }, { diff --git a/packages/cli/src/commands/deploy/meta.json b/packages/cli/src/commands/deploy/meta.json index 593fac18e..b6b556940 100644 --- a/packages/cli/src/commands/deploy/meta.json +++ b/packages/cli/src/commands/deploy/meta.json @@ -37,7 +37,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without uploading the Worker", + "description": "Build a project and run checks without actually uploading the Worker", "default": false }, { diff --git a/packages/cli/src/commands/deploy/shared.ts b/packages/cli/src/commands/deploy/shared.ts index 76ff7e9fc..e2e669410 100644 --- a/packages/cli/src/commands/deploy/shared.ts +++ b/packages/cli/src/commands/deploy/shared.ts @@ -67,7 +67,7 @@ export function sharedUploadBuilder(yargs: Argv) { }) .option("dry-run", { describe: - "Preview setup if needed; otherwise build and check without uploading the Worker", + "Build a project and run checks without actually uploading the Worker", type: "boolean", default: false, }) diff --git a/packages/cli/src/commands/workers/triggers/deploy.ts b/packages/cli/src/commands/workers/triggers/deploy.ts index 4120776f9..2cbc42a65 100644 --- a/packages/cli/src/commands/workers/triggers/deploy.ts +++ b/packages/cli/src/commands/workers/triggers/deploy.ts @@ -33,8 +33,7 @@ function builder(yargs: Argv) { }) .option("dry-run", { type: "boolean", - description: - "Preview setup if needed; otherwise build and check without applying triggers", + description: "Run checks without applying triggers", default: false, }) .option("worker", buildOutputWorkerOption); diff --git a/packages/cli/src/commands/workers/triggers/meta.json b/packages/cli/src/commands/workers/triggers/meta.json index 871a6ca25..7dccc9715 100644 --- a/packages/cli/src/commands/workers/triggers/meta.json +++ b/packages/cli/src/commands/workers/triggers/meta.json @@ -19,7 +19,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without applying triggers", + "description": "Run checks without applying triggers", "default": false }, { diff --git a/packages/cli/src/commands/workers/versions/create/meta.json b/packages/cli/src/commands/workers/versions/create/meta.json index 3b8f3319c..c3b7dfa49 100644 --- a/packages/cli/src/commands/workers/versions/create/meta.json +++ b/packages/cli/src/commands/workers/versions/create/meta.json @@ -36,7 +36,7 @@ "name": "dry-run", "type": "boolean", "required": false, - "description": "Preview setup if needed; otherwise build and check without uploading the Worker", + "description": "Build a project and run checks without actually uploading the Worker", "default": false }, { From 8c0d26056f3fef4f8a7318291ea9578452016f35 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Fri, 2 Oct 2026 12:14:22 +0100 Subject: [PATCH 4/5] refactor: avoid duplicate dry-run completion messages --- packages/cli/src/commands/build/index.ts | 6 ------ packages/cli/src/commands/deploy/shared.ts | 8 ++++---- packages/cli/src/commands/workers/triggers/deploy.ts | 8 ++++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/commands/build/index.ts b/packages/cli/src/commands/build/index.ts index bf553d99e..b88d16567 100644 --- a/packages/cli/src/commands/build/index.ts +++ b/packages/cli/src/commands/build/index.ts @@ -39,12 +39,6 @@ export async function runBuild( dryRun, }); if (setupNeeded) { - if (output !== "silent") { - clack.log.message("Build skipped because project setup was previewed.", { - spacing: 0, - output: output === "stderr" ? process.stderr : undefined, - }); - } return "setup-needed"; } const buildCommand = configuration?.buildCommand ?? details?.buildCommand; diff --git a/packages/cli/src/commands/deploy/shared.ts b/packages/cli/src/commands/deploy/shared.ts index e2e669410..3922897cf 100644 --- a/packages/cli/src/commands/deploy/shared.ts +++ b/packages/cli/src/commands/deploy/shared.ts @@ -98,7 +98,7 @@ export async function runUpload(argv: UploadArgs, ctx: UploadCommand) { dryRun: argv["dry-run"], }); if (build === "setup-needed") { - clack.log.success("Dry run complete"); + clack.log.success("--dry-run: exiting now."); return; } clack.log.message("", { spacing: 0 }); @@ -220,7 +220,7 @@ async function uploadBuildOutput(argv: UploadArgs, ctx: UploadCommand) { ); } - clack.log.success( - argv["dry-run"] ? "Dry run complete" : `${ctx.command} complete` - ); + if (!argv["dry-run"]) { + clack.log.success(`${ctx.command} complete`); + } } diff --git a/packages/cli/src/commands/workers/triggers/deploy.ts b/packages/cli/src/commands/workers/triggers/deploy.ts index 2cbc42a65..bb5f66e91 100644 --- a/packages/cli/src/commands/workers/triggers/deploy.ts +++ b/packages/cli/src/commands/workers/triggers/deploy.ts @@ -61,7 +61,7 @@ const triggersDeployCommand: CommandModule< dryRun: argv["dry-run"], }); if (build === "setup-needed") { - clack.log.success("Dry run complete"); + clack.log.success("--dry-run: exiting now."); return; } clack.log.message("", { spacing: 0 }); @@ -107,9 +107,9 @@ async function deployTriggers(argv: TriggersDeployArgs): Promise { await triggersDeploy( createTriggerProps(worker, wranglerConfig, accountId, argv) ); - clack.log.success( - argv["dry-run"] ? "Dry run complete" : "Trigger deploy complete" - ); + if (!argv["dry-run"]) { + clack.log.success("Trigger deploy complete"); + } } export default triggersDeployCommand; From e551802e531477c023ed6279255820b30f1f2037 Mon Sep 17 00:00:00 2001 From: Samuel Macleod Date: Fri, 2 Oct 2026 19:39:50 +0100 Subject: [PATCH 5/5] docs: clarify dry-run setup routes --- .changeset/dry-run-autoconfig-preview.md | 7 ------ .changeset/dry-run-project-setup.md | 7 ++++++ .../__tests__/commands/deploy/index.test.ts | 2 +- .../src/__tests__/commands/migrate.test.ts | 12 +++++++--- .../commands/triggers-deploy.test.ts | 2 +- .../lib/autoconfig-migration.test.ts | 14 +++++++---- packages/cli/src/commands/build/index.ts | 2 ++ packages/cli/src/lib/autoconfig.ts | 24 ++++++++++++------- packages/cli/src/lib/wrangler-migration.ts | 5 ++++ 9 files changed, 51 insertions(+), 24 deletions(-) delete mode 100644 .changeset/dry-run-autoconfig-preview.md create mode 100644 .changeset/dry-run-project-setup.md diff --git a/.changeset/dry-run-autoconfig-preview.md b/.changeset/dry-run-autoconfig-preview.md deleted file mode 100644 index 157d571fa..000000000 --- a/.changeset/dry-run-autoconfig-preview.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"cf": patch ---- - -Preview project setup during deploy dry runs - -Pass `--dry-run` to autoconfiguration and Wrangler migration for `cf deploy`, `cf workers versions create`, and `cf workers triggers deploy`. If setup is needed, show the preview and skip the build and upload. diff --git a/.changeset/dry-run-project-setup.md b/.changeset/dry-run-project-setup.md new file mode 100644 index 000000000..1bc7e5981 --- /dev/null +++ b/.changeset/dry-run-project-setup.md @@ -0,0 +1,7 @@ +--- +"cf": patch +--- + +Avoid project setup changes during deploy dry runs + +Pass `--dry-run` to framework setup and Wrangler config conversion for `cf deploy`, `cf workers versions create`, and `cf workers triggers deploy`. If setup is needed, show the planned changes and skip the build and upload. diff --git a/packages/cli/src/__tests__/commands/deploy/index.test.ts b/packages/cli/src/__tests__/commands/deploy/index.test.ts index bee422cf0..31d72ff14 100644 --- a/packages/cli/src/__tests__/commands/deploy/index.test.ts +++ b/packages/cli/src/__tests__/commands/deploy/index.test.ts @@ -1138,7 +1138,7 @@ describe("cf deploy", () => { }); describe("--dry-run", () => { - it("previews setup without changing an existing Astro project or building", async () => { + it("keeps existing project files unchanged and skips the build when setup is needed", async () => { const packageJson = JSON.stringify({ name: "astro-project", scripts: { deploy: "astro build && wrangler deploy" }, diff --git a/packages/cli/src/__tests__/commands/migrate.test.ts b/packages/cli/src/__tests__/commands/migrate.test.ts index db96aa8cd..aca73ae74 100644 --- a/packages/cli/src/__tests__/commands/migrate.test.ts +++ b/packages/cli/src/__tests__/commands/migrate.test.ts @@ -165,7 +165,7 @@ describe("cf migrate", () => { expect(migrateWranglerToCf).not.toHaveBeenCalled(); }); - it("offers to run the same migration for project workflows", async () => { + it("offers to convert a Wrangler config before project setup", async () => { await seed({ "wrangler.jsonc": "{}" }); const confirmMigration = vi.fn().mockResolvedValue(true); @@ -191,7 +191,7 @@ describe("cf migrate", () => { ); }); - it("previews an accepted project workflow migration during a dry run", async () => { + it("dry-runs an accepted Wrangler config conversion before project setup", async () => { await seed({ "wrangler.jsonc": "{}" }); const confirmMigration = vi.fn().mockResolvedValue(true); @@ -206,8 +206,14 @@ describe("cf migrate", () => { expect(migrateWranglerToCf).toHaveBeenCalledWith( path.join(process.cwd(), "wrangler.jsonc"), - expect.objectContaining({ dryRun: true }) + { + bundler: "wrangler", + dryRun: true, + force: false, + installDependencies: true, + } ); + expect(migrateWranglerToCf).toHaveBeenCalledOnce(); }); it("uses Vite for automatic migration when the plugin is declared", async () => { diff --git a/packages/cli/src/__tests__/commands/triggers-deploy.test.ts b/packages/cli/src/__tests__/commands/triggers-deploy.test.ts index 3a49b0b09..f0f3b8ea2 100644 --- a/packages/cli/src/__tests__/commands/triggers-deploy.test.ts +++ b/packages/cli/src/__tests__/commands/triggers-deploy.test.ts @@ -83,7 +83,7 @@ describe("cf workers triggers deploy", () => { expect(requests).toEqual([]); }); - it("stops after previewing project setup during a dry run", async () => { + it("skips the build and trigger deployment when setup is needed during a dry run", async () => { const requests = recordRequests(); await seed({ "package.json": JSON.stringify({ diff --git a/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts b/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts index 4c15a41f7..03dfcca01 100644 --- a/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts +++ b/packages/cli/src/__tests__/lib/autoconfig-migration.test.ts @@ -42,7 +42,7 @@ describe("project preparation", () => { expect(mocks.maybeMigrateWranglerProject).not.toHaveBeenCalled(); }); - it("does not preview setup for a configured project during a dry run", async () => { + it("does not run setup for a configured project during a dry run", async () => { const configuredDetails = { ...unconfiguredDetails, configured: true }; mocks.getDetailsForAutoConfig.mockResolvedValue(configuredDetails); @@ -80,7 +80,7 @@ describe("project preparation", () => { expect(mocks.runAutoConfig).not.toHaveBeenCalled(); }); - it("runs autoconfig when migration is unavailable or declined", async () => { + it("runs framework setup when Wrangler config conversion did not run", async () => { const configuration = { scripts: {}, outputDir: "dist", @@ -97,7 +97,7 @@ describe("project preparation", () => { expect(mocks.runAutoConfig).toHaveBeenCalledOnce(); }); - it("previews autoconfig without applying setup during a dry run", async () => { + it("dry-runs framework setup when Wrangler config conversion did not run", async () => { mocks.getDetailsForAutoConfig.mockResolvedValue(unconfiguredDetails); mocks.maybeMigrateWranglerProject.mockResolvedValue(false); mocks.runAutoConfig.mockResolvedValue({ buildCommand: "npm run build" }); @@ -120,7 +120,7 @@ describe("project preparation", () => { ); }); - it("stops after previewing a Wrangler migration", async () => { + it("stops after an accepted Wrangler config conversion dry run", async () => { mocks.getDetailsForAutoConfig.mockResolvedValue(unconfiguredDetails); mocks.maybeMigrateWranglerProject.mockResolvedValue(true); @@ -130,6 +130,12 @@ describe("project preparation", () => { setupNeeded: true, } ); + expect(mocks.maybeMigrateWranglerProject).toHaveBeenCalledWith( + "/project", + expect.any(Function), + undefined, + true + ); expect(mocks.getDetailsForAutoConfig).toHaveBeenCalledOnce(); expect(mocks.runAutoConfig).not.toHaveBeenCalled(); }); diff --git a/packages/cli/src/commands/build/index.ts b/packages/cli/src/commands/build/index.ts index b88d16567..13a1f8607 100644 --- a/packages/cli/src/commands/build/index.ts +++ b/packages/cli/src/commands/build/index.ts @@ -39,6 +39,8 @@ export async function runBuild( dryRun, }); if (setupNeeded) { + // A Wrangler config conversion or framework setup was dry-run, so the + // configuration the build needs has not been written yet. return "setup-needed"; } const buildCommand = configuration?.buildCommand ?? details?.buildCommand; diff --git a/packages/cli/src/lib/autoconfig.ts b/packages/cli/src/lib/autoconfig.ts index dae47df75..aacd1b107 100644 --- a/packages/cli/src/lib/autoconfig.ts +++ b/packages/cli/src/lib/autoconfig.ts @@ -81,6 +81,10 @@ export async function analyzeProject( } } +/** + * Run framework autoconfig after detection, applying or dry-running setup + * without building. + */ export async function configureProject( details: AutoConfigDetails, options: ProjectPreparationOptions = {} @@ -93,6 +97,11 @@ export async function configureProject( }); } +/** + * Choose project setup before a build: an accepted Wrangler config conversion + * takes precedence over framework setup. In a dry run, `setupNeeded` means + * a setup route was selected but not applied, so callers must skip the build. + */ export async function prepareProject( cwd: string, options: ProjectPreparationOptions = {} @@ -107,14 +116,13 @@ export async function prepareProject( } const context = createAutoConfigContext(options); - if ( - await maybeMigrateWranglerProject( - cwd, - (text, confirmOptions) => context.dialogs.confirm(text, confirmOptions), - options.output, - options.dryRun - ) - ) { + const migrationRan = await maybeMigrateWranglerProject( + cwd, + (text, confirmOptions) => context.dialogs.confirm(text, confirmOptions), + options.output, + options.dryRun + ); + if (migrationRan) { if (options.dryRun) { return { details, setupNeeded: true }; } diff --git a/packages/cli/src/lib/wrangler-migration.ts b/packages/cli/src/lib/wrangler-migration.ts index 9be87bf71..cc2d29665 100644 --- a/packages/cli/src/lib/wrangler-migration.ts +++ b/packages/cli/src/lib/wrangler-migration.ts @@ -192,6 +192,11 @@ export async function runWranglerMigration( } } +/** + * Offer to convert a Wrangler config before framework setup. Returns true + * only after the user accepts and the conversion command completes, including + * when it runs in dry-run mode. + */ export async function maybeMigrateWranglerProject( projectPath: string, confirmMigration: ConfirmMigration,