From f6a4dd3f95a4e4156b684f0d2857aa93e17fce32 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Fri, 6 Nov 2026 12:36:13 +0000 Subject: [PATCH 1/2] feat: add --secrets-file to cf previews deploy --- .changeset/previews-deploy-secrets-file.md | 7 ++++ .../commands/hand-written-metadata.test.ts | 11 +++++++ .../commands/previews-deploy.test.ts | 32 +++++++++++++++++++ .../commands/_generated/_meta/commands.json | 6 ++++ .../_meta/hand-written-commands.json | 6 ++++ .../deploy/secrets-file-description.ts | 10 ++++-- packages/cli/src/commands/previews/deploy.ts | 10 ++++-- .../cli/src/commands/previews/deploy/meta.ts | 7 ++++ 8 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 .changeset/previews-deploy-secrets-file.md diff --git a/.changeset/previews-deploy-secrets-file.md b/.changeset/previews-deploy-secrets-file.md new file mode 100644 index 0000000000..5b43365334 --- /dev/null +++ b/.changeset/previews-deploy-secrets-file.md @@ -0,0 +1,7 @@ +--- +"cf": minor +--- + +Add `--secrets-file` to `cf previews deploy` + +Use `cf previews deploy --secrets-file .env.preview` to upload secrets with a Preview deployment, in the same JSON or `.env` format that `cf deploy --secrets-file` accepts. Previously, Preview secrets could only be set in the Cloudflare dashboard or with Wrangler. diff --git a/packages/cli/src/__tests__/commands/hand-written-metadata.test.ts b/packages/cli/src/__tests__/commands/hand-written-metadata.test.ts index 696e4e8317..8689cc1128 100644 --- a/packages/cli/src/__tests__/commands/hand-written-metadata.test.ts +++ b/packages/cli/src/__tests__/commands/hand-written-metadata.test.ts @@ -175,6 +175,17 @@ describe("hand-written command metadata", () => { }); }); + it("publishes Preview secrets-file metadata", () => { + expect(commands.get("cf previews deploy")?.options).toContainEqual({ + name: "secrets-file", + type: "string", + required: false, + description: expect.stringMatching( + /^Path to a file containing secrets to upload with the Preview deployment / + ), + }); + }); + it("publishes Quick Tunnel email protection for completions and tools", () => { expect(commands.get("cf tunnels quick-start")?.options).toContainEqual( expect.objectContaining({ diff --git a/packages/cli/src/__tests__/commands/previews-deploy.test.ts b/packages/cli/src/__tests__/commands/previews-deploy.test.ts index 1d4d756f69..fe33a521f0 100644 --- a/packages/cli/src/__tests__/commands/previews-deploy.test.ts +++ b/packages/cli/src/__tests__/commands/previews-deploy.test.ts @@ -242,6 +242,38 @@ describe("cf previews deploy", () => { ); }); + it("uploads secrets from --secrets-file", async () => { + const rootConfig = { buildContext: { isPreview: true } }; + const worker = { + config: { type: "worker", name: "preview-worker" }, + bundleDir: "/project/bundle", + }; + mocks.readBuildOutput.mockResolvedValue({ + rootConfig, + workers: { default: worker }, + }); + mocks.assembleBuildResult.mockReturnValue({ content: "worker code" }); + mocks.getAuthToken.mockResolvedValue("token"); + mocks.getAccountId.mockResolvedValue("account-id"); + mocks.createDeployContext.mockReturnValue({ + logger: { log: vi.fn(), info: vi.fn() }, + }); + mocks.previewBuildOutput.mockResolvedValue(previewResult); + + await runPreviewDeploy({ + "preview-name": "feature", + prebuilt: true, + "secrets-file": ".env.preview", + } as Parameters[0]); + + expect(mocks.previewBuildOutput).toHaveBeenCalledWith( + "account-id", + { name: "feature", json: true, secretsFile: ".env.preview" }, + expect.any(Object), + expect.any(Object) + ); + }); + it("uses the built region to select an account when output omits its ID", async () => { const worker = { config: { diff --git a/packages/cli/src/commands/_generated/_meta/commands.json b/packages/cli/src/commands/_generated/_meta/commands.json index 718037581a..c979e7c979 100644 --- a/packages/cli/src/commands/_generated/_meta/commands.json +++ b/packages/cli/src/commands/_generated/_meta/commands.json @@ -88663,6 +88663,12 @@ "required": false, "description": "Name of the Worker in the Build Output to use (defaults to the default Worker)" }, + { + "name": "secrets-file", + "type": "string", + "required": false, + "description": "Path to a file containing secrets to upload with the Preview deployment (JSON or .env format). Dotenv values expand $VAR and ${VAR} using process environment values and earlier file values; process environment values override file values, including empty values. Only declared file keys are uploaded. Only use trusted dotenv secrets files: they can read and upload any process environment value. Escape literal dollar signs with \\$ or use JSON." + }, { "name": "dry-run", "type": "boolean", 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 12eb99e425..c27f68df3a 100644 --- a/packages/cli/src/commands/_generated/_meta/hand-written-commands.json +++ b/packages/cli/src/commands/_generated/_meta/hand-written-commands.json @@ -1498,6 +1498,12 @@ "required": false, "description": "Name of the Worker in the Build Output to use (defaults to the default Worker)" }, + { + "name": "secrets-file", + "type": "string", + "required": false, + "description": "Path to a file containing secrets to upload with the Preview deployment (JSON or .env format). Dotenv values expand $VAR and ${VAR} using process environment values and earlier file values; process environment values override file values, including empty values. Only declared file keys are uploaded. Only use trusted dotenv secrets files: they can read and upload any process environment value. Escape literal dollar signs with \\$ or use JSON." + }, { "name": "dry-run", "type": "boolean", diff --git a/packages/cli/src/commands/deploy/secrets-file-description.ts b/packages/cli/src/commands/deploy/secrets-file-description.ts index 60d17b2d1e..9be91772af 100644 --- a/packages/cli/src/commands/deploy/secrets-file-description.ts +++ b/packages/cli/src/commands/deploy/secrets-file-description.ts @@ -1,2 +1,8 @@ -export const secretsFileDescription = - "Path to a file containing secrets to upload with the version (JSON or .env format). Dotenv values expand $VAR and ${VAR} using process environment values and earlier file values; process environment values override file values, including empty values. Only declared file keys are uploaded. Only use trusted dotenv secrets files: they can read and upload any process environment value. Escape literal dollar signs with \\$ or use JSON."; +function describeSecretsFile(upload: string) { + return `Path to a file containing secrets to upload with the ${upload} (JSON or .env format). Dotenv values expand $VAR and \${VAR} using process environment values and earlier file values; process environment values override file values, including empty values. Only declared file keys are uploaded. Only use trusted dotenv secrets files: they can read and upload any process environment value. Escape literal dollar signs with \\$ or use JSON.`; +} + +export const secretsFileDescription = describeSecretsFile("version"); + +export const previewSecretsFileDescription = + describeSecretsFile("Preview deployment"); diff --git a/packages/cli/src/commands/previews/deploy.ts b/packages/cli/src/commands/previews/deploy.ts index ff3a7e596c..885f0fc555 100644 --- a/packages/cli/src/commands/previews/deploy.ts +++ b/packages/cli/src/commands/previews/deploy.ts @@ -33,6 +33,7 @@ import { createContainerDeployConfig, isLiveContainerExport, } from "../deploy/containers.js"; +import { previewSecretsFileDescription } from "../deploy/secrets-file-description.js"; import { prepareStaticAssetsBuildOutput, staticAssetsDirectoryOption, @@ -56,7 +57,12 @@ function builder(yargs: Argv) { "Use existing Preview Build Output Specification files without building", default: false, }) - .option("worker", buildOutputWorkerOption); + .option("worker", buildOutputWorkerOption) + .option("secrets-file", { + type: "string", + description: previewSecretsFileDescription, + requiresArg: true, + }); } type PreviewDeployArgs = InferArgs; @@ -185,7 +191,7 @@ async function deployPreviewBuildOutput( const result = await previewBuildOutput( accountId, - { name: previewName, json: true }, + { name: previewName, json: true, secretsFile: argv["secrets-file"] }, { workerConfig: builtConfig, rootConfig: output.rootConfig, diff --git a/packages/cli/src/commands/previews/deploy/meta.ts b/packages/cli/src/commands/previews/deploy/meta.ts index 8d8b8d5375..099aefb7ea 100644 --- a/packages/cli/src/commands/previews/deploy/meta.ts +++ b/packages/cli/src/commands/previews/deploy/meta.ts @@ -1,3 +1,4 @@ +import { previewSecretsFileDescription } from "../../deploy/secrets-file-description.js"; import type { LeafCommandMeta } from "../../../lib/command-meta.js"; export default { @@ -40,6 +41,12 @@ export default { description: "Name of the Worker in the Build Output to use (defaults to the default Worker)", }, + { + name: "secrets-file", + type: "string", + required: false, + description: previewSecretsFileDescription, + }, { name: "dry-run", type: "boolean", From 220999d04609f1e42cb0c9fc758e2841e0769ee8 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Fri, 6 Nov 2026 12:38:43 +0000 Subject: [PATCH 2/2] chore: use deploy-helpers preview with previewBuildOutput secrets --- packages/cli/package.json | 2 +- pnpm-lock.yaml | 15 ++++++++++----- pnpm-workspace.yaml | 8 +++++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ce0bdb52f9..7ea78a931b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -84,7 +84,7 @@ "@cloudflare/cli-shared-helpers": "0.3.4", "@cloudflare/codemods": "0.7.4", "@cloudflare/containers-shared": "0.23.9", - "@cloudflare/deploy-helpers": "0.26.0", + "@cloudflare/deploy-helpers": "https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@b05642f25d4efdcd5fc54dc33b5767dc19f002bb", "@cloudflare/forge": "https://pkg.pr.new/cloudflare/forge/@cloudflare/forge@0d1877431691b07cbe3b042a4b64cb0bd8e81c03", "@cloudflare/workers-auth": "0.16.0", "@cloudflare/workers-utils": "0.52.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d787db22e..7af2fa19fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,6 +172,10 @@ catalogs: version: 1.4.0 overrides: + '@cloudflare/deploy-helpers@0.26.0>@cloudflare/config': 0.30.2 + '@cloudflare/deploy-helpers@0.26.0>@cloudflare/containers-shared': 0.23.9 + '@cloudflare/deploy-helpers@0.26.0>@cloudflare/workers-utils': 0.52.0 + '@cloudflare/deploy-helpers@0.26.0>miniflare': 5.20261104.0-beta vite@*: npm:@voidzero-dev/vite-plus-core@1.4.0 vitest@*: 5.0.5 @@ -291,8 +295,8 @@ importers: specifier: 0.23.9 version: 0.23.9(patch_hash=24a1792d9428e4c51d527fb2f14ecfc1ac65f943bf5cb5b2a896465894413924)(vitest@5.0.5) '@cloudflare/deploy-helpers': - specifier: 0.26.0 - version: 0.26.0(patch_hash=30711b17bc29dc3b500369bdb5740031dc239625cd1bfe5cf26b9661e49ae235)(@types/node@26.6.6)(vitest@5.0.5) + specifier: https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@b05642f25d4efdcd5fc54dc33b5767dc19f002bb + version: https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@b05642f25d4efdcd5fc54dc33b5767dc19f002bb(patch_hash=30711b17bc29dc3b500369bdb5740031dc239625cd1bfe5cf26b9661e49ae235)(@types/node@26.6.6)(vitest@5.0.5) '@cloudflare/forge': specifier: https://pkg.pr.new/cloudflare/forge/@cloudflare/forge@0d1877431691b07cbe3b042a4b64cb0bd8e81c03 version: https://pkg.pr.new/cloudflare/forge/@cloudflare/forge@0d1877431691b07cbe3b042a4b64cb0bd8e81c03(@bytecodealliance/jco@1.40.0) @@ -714,8 +718,9 @@ packages: resolution: {integrity: sha512-+AglKk+Q7rjMlZifC9lPn4Jqd31/z781LtRI6DRyqmpSMvU7ZzGTgknIVrnEeLOaR1amKRrOZPcpPN9mN8rdmw==} engines: {node: '>=22.0.0'} - '@cloudflare/deploy-helpers@0.26.0': - resolution: {integrity: sha512-rhx4c+nuM8s2jnNwNVxI0AqaM7pWMyRLdK7xV0iLCLJcOVd6K0joI/TqG+7F6rZhU1dNSd4qRwjhBfrDtjYY4w==} + '@cloudflare/deploy-helpers@https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@b05642f25d4efdcd5fc54dc33b5767dc19f002bb': + resolution: {integrity: sha512-8dk+W9bQNJkTp2RstlJlft0T08WWwZsMjQeAcYlc0CvpgFEBi0ks/SwOFkKbWxM/FOv9Dh+0zkyn/tpCTuK90Q==, tarball: https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@b05642f25d4efdcd5fc54dc33b5767dc19f002bb} + version: 0.26.0 '@cloudflare/forge@https://pkg.pr.new/cloudflare/forge/@cloudflare/forge@0d1877431691b07cbe3b042a4b64cb0bd8e81c03': resolution: {integrity: sha512-Z2l+z2uDYkQdQqAJMAVL8xLdd3LD64nxJcsKYYvovOwb9FszzwZkLAWLS6chBPZNetMDPOV4YRmNV+RfDuNyyQ==, tarball: https://pkg.pr.new/cloudflare/forge/@cloudflare/forge@0d1877431691b07cbe3b042a4b64cb0bd8e81c03} @@ -4260,7 +4265,7 @@ snapshots: transitivePeerDependencies: - vitest - '@cloudflare/deploy-helpers@0.26.0(patch_hash=30711b17bc29dc3b500369bdb5740031dc239625cd1bfe5cf26b9661e49ae235)(@types/node@26.6.6)(vitest@5.0.5)': + '@cloudflare/deploy-helpers@https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/deploy-helpers@b05642f25d4efdcd5fc54dc33b5767dc19f002bb(patch_hash=30711b17bc29dc3b500369bdb5740031dc239625cd1bfe5cf26b9661e49ae235)(@types/node@26.6.6)(vitest@5.0.5)': dependencies: '@cloudflare/cli-shared-helpers': 0.3.4(vitest@5.0.5) '@cloudflare/config': 0.30.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 04429522d9..e2b4f79e94 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,7 +8,7 @@ patchedDependencies: "@cloudflare/deploy-helpers@*": patches/@cloudflare__deploy-helpers@0.20.1.patch # Prevent transitive deps from pulling from git / tarball URLs. -# Direct deps (the pinned pkg.pr.new Forge build) are allowed. +# Direct deps (the pinned pkg.pr.new Forge and deploy-helpers builds) are allowed. blockExoticSubdeps: true # Allow Cloudflare release batches before pnpm's maturity cutoff. Keep these @@ -34,6 +34,12 @@ catalog: vite: npm:@voidzero-dev/vite-plus-core@1.4.0 vite-plus: 1.4.0 overrides: + # The deploy-helpers preview rewrites sibling deps to previews of these same + # released versions. Retain cf's shared instances and container cleanup patch. + "@cloudflare/deploy-helpers@0.26.0>@cloudflare/config": "0.30.2" + "@cloudflare/deploy-helpers@0.26.0>@cloudflare/containers-shared": "0.23.9" + "@cloudflare/deploy-helpers@0.26.0>@cloudflare/workers-utils": "0.52.0" + "@cloudflare/deploy-helpers@0.26.0>miniflare": "5.20261104.0-beta" vite@*: "catalog:" vitest@*: 5.0.5 peerDependencyRules: