diff --git a/.bumpy/wrangler-preview-command.md b/.bumpy/wrangler-preview-command.md new file mode 100644 index 000000000..005caec0e --- /dev/null +++ b/.bumpy/wrangler-preview-command.md @@ -0,0 +1,5 @@ +--- +"@varlock/cloudflare-integration": minor +--- + +varlock-wrangler now supports the new `wrangler preview` command, uploading resolved vars and secrets to branch preview deployments diff --git a/packages/integrations/cloudflare/src/varlock-wrangler.ts b/packages/integrations/cloudflare/src/varlock-wrangler.ts index ab3e9b128..44646fcf9 100644 --- a/packages/integrations/cloudflare/src/varlock-wrangler.ts +++ b/packages/integrations/cloudflare/src/varlock-wrangler.ts @@ -11,6 +11,7 @@ import { spawn, execSync } from 'node:child_process'; import { execSyncVarlock, VarlockExecError } from 'varlock/exec-sync-varlock'; import { encryptEnvBlobSync, generateEncryptionKeyHex } from 'varlock/encrypt-env'; import { formatEnvLine } from './format-env-line'; +import { isPreviewDeployCommand } from './wrangler-command-detection'; const isWindows = process.platform === 'win32'; const debugEnabled = !!process.env.VARLOCK_DEBUG; @@ -355,6 +356,7 @@ function isVersionsUploadCommand(args: Array) { function isDeployCommand(args: Array) { if (args[0] === 'deploy') return true; if (isVersionsUploadCommand(args)) return true; + if (isPreviewDeployCommand(args)) return true; return false; } @@ -444,7 +446,8 @@ async function handleDeploy(args: Array) { try { debug('deploy: spawning wrangler'); const wranglerArgs = [...args, ...varFlags, '--secrets-file', tmp.filePath]; - if (!isVersionsUploadCommand(args)) wranglerArgs.push('--keep-vars=false'); + // --keep-vars only applies to `deploy` (not `versions upload` or `preview`) + if (args[0] === 'deploy') wranglerArgs.push('--keep-vars=false'); exitCode = await spawnWrangler(wranglerArgs); debug('deploy: wrangler exited with code', exitCode); } finally { @@ -751,6 +754,7 @@ async function main() { console.log('Enhanced commands:'); console.log(' dev - injects resolved env via named pipe (no secrets on disk)'); console.log(' deploy / versions upload - uploads env as Cloudflare vars and secrets'); + console.log(' preview - deploys a branch preview with env as vars and secrets'); console.log(' types - generates types including varlock-managed env vars'); console.log(''); console.log('All other commands are passed through to wrangler unchanged.'); diff --git a/packages/integrations/cloudflare/src/wrangler-command-detection.ts b/packages/integrations/cloudflare/src/wrangler-command-detection.ts new file mode 100644 index 000000000..16b3f930c --- /dev/null +++ b/packages/integrations/cloudflare/src/wrangler-command-detection.ts @@ -0,0 +1,28 @@ +const PREVIEW_SUBCOMMANDS = ['delete', 'settings', 'secret', 'base-config']; + +const GLOBAL_OPTIONS_WITH_VALUES = [ + '--config', + '-c', + '--cwd', + '--env', + '-e', + '--env-file', + '--profile', +]; + +export function isPreviewDeployCommand(args: Array) { + if (args[0] !== 'preview' || args.includes('--help') || args.includes('-h')) return false; + + for (let i = 1; i < args.length; i++) { + const arg = args[i]; + if (arg === '--') return true; + if (GLOBAL_OPTIONS_WITH_VALUES.includes(arg)) { + i++; + continue; + } + if (arg.startsWith('-')) continue; + return !PREVIEW_SUBCOMMANDS.includes(arg); + } + + return true; +} diff --git a/packages/integrations/cloudflare/test/wrangler-command-detection.test.ts b/packages/integrations/cloudflare/test/wrangler-command-detection.test.ts new file mode 100644 index 000000000..b339321d7 --- /dev/null +++ b/packages/integrations/cloudflare/test/wrangler-command-detection.test.ts @@ -0,0 +1,26 @@ +import { + describe, expect, it, +} from 'vitest'; +import { isPreviewDeployCommand } from '../src/wrangler-command-detection'; + +describe('isPreviewDeployCommand', () => { + it.each([ + ['preview', '--help'], + ['preview', '-h'], + ['preview', '--config', 'wrangler.jsonc', 'settings', '--help'], + ['preview', '--config=wrangler.jsonc', 'delete'], + ['preview', '-c', 'wrangler.jsonc', 'secret', 'list'], + ['preview', '--env', 'staging', 'base-config', 'secret', 'list'], + ])('passes through non-deploying invocation %j', (...args) => { + expect(isPreviewDeployCommand(args)).toBe(false); + }); + + it.each([ + ['preview'], + ['preview', 'src/index.ts'], + ['preview', '--config', 'wrangler.jsonc'], + ['preview', '--config=wrangler.jsonc', 'src/index.ts'], + ])('routes deployment invocation %j through varlock', (...args) => { + expect(isPreviewDeployCommand(args)).toBe(true); + }); +}); diff --git a/packages/varlock-website/src/content/docs/integrations/cloudflare.mdx b/packages/varlock-website/src/content/docs/integrations/cloudflare.mdx index 5cad224bd..03f2293a2 100644 --- a/packages/varlock-website/src/content/docs/integrations/cloudflare.mdx +++ b/packages/varlock-website/src/content/docs/integrations/cloudflare.mdx @@ -204,10 +204,12 @@ Wraps `wrangler dev` with automatic env injection. Resolves your environment var
-### `varlock-wrangler deploy` / `versions upload` +### `varlock-wrangler deploy` / `versions upload` / `preview` Resolves your environment variables from `.env.schema` + `.env` files, then uploads non-sensitive values as Cloudflare vars (`--var`), sensitive values as Cloudflare secrets (`--secrets-file`), and includes a `__VARLOCK_ENV` secret containing the full resolved env graph for the varlock runtime. +`varlock-wrangler preview` works the same way for [branch preview deployments](https://developers.cloudflare.com/workers/configuration/previews/). Note that `wrangler preview` is currently in beta on Cloudflare's side. Subcommands that manage existing previews (`preview delete`, `preview settings`, `preview secret`, `preview base-config`) are passed through to wrangler unchanged. + :::note[Secure secret handling] On Unix, secrets are passed to wrangler commands via a named pipe (FIFO) and never written to disk. On Windows, a temporary file is used and immediately deleted after the deploy completes. :::