diff --git a/.changeset/c3-exit-code-on-error.md b/.changeset/c3-exit-code-on-error.md deleted file mode 100644 index e618468124..0000000000 --- a/.changeset/c3-exit-code-on-error.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-cloudflare": patch ---- - -Fix `create cloudflare` exiting with code `0` even after an unhandled error diff --git a/.changeset/c3-frameworks-update-14235.md b/.changeset/c3-frameworks-update-14235.md deleted file mode 100644 index 396fc92986..0000000000 --- a/.changeset/c3-frameworks-update-14235.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"create-cloudflare": patch ---- - -Update dependencies of "create-cloudflare" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ------------- | ------ | ------ | -| @tanstack/cli | 0.69.1 | 0.69.2 | diff --git a/.changeset/cf-auth-injectable-identity.md b/.changeset/cf-auth-injectable-identity.md deleted file mode 100644 index 3060e66482..0000000000 --- a/.changeset/cf-auth-injectable-identity.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@cloudflare/workers-auth": minor ---- - -Make the OAuth identity and token storage injectable, and add a shared env-credential resolver - -`createOAuthFlow` now takes the consumer's OAuth identity (`clientId`, `consent`, `redirectUri`) and token `storage` on its context, so other Cloudflare CLIs can reuse the flow under their own OAuth app and store tokens in their own location/format. Also adds a shared env→credential resolver (`getAuthFromEnv`, `getAPIToken`, `requireApiToken`). diff --git a/.changeset/cf-auth-wrangler-resolver.md b/.changeset/cf-auth-wrangler-resolver.md deleted file mode 100644 index 91274f8ef5..0000000000 --- a/.changeset/cf-auth-wrangler-resolver.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": patch ---- - -Use the shared env-credential resolver from `@cloudflare/workers-auth` - -No user-facing behaviour change. Credential resolution order (global API key + email → `CLOUDFLARE_API_TOKEN` → stored OAuth token) is preserved. diff --git a/.changeset/cf-vite-drop-config-flag.md b/.changeset/cf-vite-drop-config-flag.md deleted file mode 100644 index db9b5e370e..0000000000 --- a/.changeset/cf-vite-drop-config-flag.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@cloudflare/vite-plugin": patch ---- - -Drop the `--config` flag from the experimental internal `cf-vite` delegate binary. - -The wrangler config file is now discovered by `cloudflare()` itself rather than being passed through, keeping `cf-vite`'s flag surface (`--mode`, `--port`, `--host`, `--local`) in sync with the sibling `cf-wrangler` delegate. `cf-vite` is an internal integration point spawned by Cloudflare tooling and is not intended to be run directly by users. diff --git a/.changeset/cf-wrangler-delegate-entrypoint.md b/.changeset/cf-wrangler-delegate-entrypoint.md deleted file mode 100644 index 791629810a..0000000000 --- a/.changeset/cf-wrangler-delegate-entrypoint.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"wrangler": patch ---- - -Add an experimental `cf-wrangler` delegate entrypoint for projects that can't use `@cloudflare/vite-plugin` (service workers, old compatibility dates, Python, Rust, etc.). - -`cf-wrangler dev` starts the same local dev server as `wrangler dev` — it sits directly on wrangler's internal dev server, so the bundling and runtime behaviour are identical — but exposes a deliberately narrow CLI surface (`--mode`, `--port`, `--host`, `--local`) for a parent CLI to delegate to, and other dev server config options are read from the wrangler config file. - -This replaces the separate `@cloudflare/wrangler-bundler` package. This is an internal integration point and is not intended to be run directly by users. diff --git a/.changeset/dependabot-update-14246.md b/.changeset/dependabot-update-14246.md deleted file mode 100644 index 2fa1808630..0000000000 --- a/.changeset/dependabot-update-14246.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"miniflare": patch -"wrangler": patch ---- - -Update dependencies of "miniflare", "wrangler" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ---------- | ------------ | ------------ | -| workerd | 1.20260609.1 | 1.20260610.1 | diff --git a/.changeset/dependabot-update-14256.md b/.changeset/dependabot-update-14256.md deleted file mode 100644 index 677fe0c372..0000000000 --- a/.changeset/dependabot-update-14256.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"miniflare": patch -"wrangler": patch ---- - -Update dependencies of "miniflare", "wrangler" - -The following dependency versions have been updated: - -| Dependency | From | To | -| ---------- | ------------ | ------------ | -| workerd | 1.20260610.1 | 1.20260611.1 | diff --git a/.changeset/gate-network-enable-when-headless.md b/.changeset/gate-network-enable-when-headless.md deleted file mode 100644 index 4dbf947577..0000000000 --- a/.changeset/gate-network-enable-when-headless.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": patch ---- - -Fix a memory leak that could make long-running headless `wrangler dev` sessions unresponsive - -Long-running `wrangler dev` sessions with no DevTools attached (for example using the containers feature under sustained traffic) could gradually consume unbounded memory and eventually stop accepting connections. The inspector proxy now only enables network tracking while a DevTools client is connected, so the buildup no longer happens. Interactive debugging is unaffected. Fixes #14191. diff --git a/.changeset/improve-d1-errors.md b/.changeset/improve-d1-errors.md deleted file mode 100644 index 94204d4ae9..0000000000 --- a/.changeset/improve-d1-errors.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"wrangler": patch ---- - -Improve D1 error messages for missing or conflicting options - -Error messages for `d1 execute`, `d1 export`, `d1 time-travel restore`, and `d1 insights` now clearly state which option is missing or conflicting, explain why the combination is invalid, and suggest how to fix the issue. - -Additionally, duration validation errors in `d1 insights` are now thrown as `UserError` instead of plain `Error`, ensuring they are displayed cleanly to users rather than as unexpected crashes. diff --git a/.changeset/improve-login-error-messages.md b/.changeset/improve-login-error-messages.md deleted file mode 100644 index 2d24a90ac8..0000000000 --- a/.changeset/improve-login-error-messages.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@cloudflare/workers-auth": patch -"wrangler": patch ---- - -Improve authentication error messages with specific failure reasons - -When authentication fails (e.g. during `wrangler dev --remote` or when using remote bindings), the error message now explains exactly what went wrong -- whether no credentials were found, the token expired, or the environment is non-interactive -- and lists actionable steps to fix it, including a `wrangler whoami` tip. - -Previously, auth failures could produce multiple confusing errors (e.g. "Failed to fetch auth token: 400 Bad Request" followed by "Failed to start the remote proxy session"). Now a single, clear error is shown. diff --git a/.changeset/improve-r2-sippy-error-messages.md b/.changeset/improve-r2-sippy-error-messages.md deleted file mode 100644 index e5773fdbbe..0000000000 --- a/.changeset/improve-r2-sippy-error-messages.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": patch ---- - -Improve R2 Sippy error messages - -Now error messages in `wrangler r2 bucket sippy` follow a consistent pattern: they describe what is missing, name the exact `--flag` to use, and provide context (e.g. example values, links to the dashboard). Previously, many errors said only "Error: must provide --flag." with no guidance on what the flag does or how to obtain the value. diff --git a/.changeset/move-build-before-deploy.md b/.changeset/move-build-before-deploy.md deleted file mode 100644 index 726a9bbbbd..0000000000 --- a/.changeset/move-build-before-deploy.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"wrangler": patch -"@cloudflare/deploy-helpers": patch ---- - -Move worker build step earlier in deploy/upload step, before upload specific config validation diff --git a/.changeset/r2-local-public-bucket-miniflare.md b/.changeset/r2-local-public-bucket-miniflare.md deleted file mode 100644 index 4e38fe1019..0000000000 --- a/.changeset/r2-local-public-bucket-miniflare.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"miniflare": minor ---- - -Add support for serving R2 bucket objects publicly via the dev server - -Each local R2 bucket is now exposed under `/cdn-cgi/local/r2/public//` on the existing user-facing dev server. The `` is the bucket's `id` when set, otherwise its binding name. Buckets with a `remoteProxyConnectionString` are not exposed. The endpoint supports GET and HEAD, range requests, conditional headers, and forwards stored HTTP metadata. diff --git a/.changeset/r2-local-public-bucket-wrangler.md b/.changeset/r2-local-public-bucket-wrangler.md deleted file mode 100644 index c7f857e07c..0000000000 --- a/.changeset/r2-local-public-bucket-wrangler.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"wrangler": minor ---- - -Serve local R2 bucket objects publicly via the dev server - -When running `wrangler dev` locally, objects in each local R2 binding are now reachable under `/cdn-cgi/local/r2/public//` on the existing dev server, simulating a public bucket. The `` is the bucket's `bucket_name` when set, otherwise its `binding`. Bindings configured with `remote: true` are not exposed. diff --git a/.changeset/swift-fox-flies.md b/.changeset/swift-fox-flies.md deleted file mode 100644 index d45ba36510..0000000000 --- a/.changeset/swift-fox-flies.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"create-cloudflare": patch ---- - -Fix `create cloudflare` aborting with `ERR_PNPM_IGNORED_BUILDS` on pnpm 11 - -pnpm 11 flipped `strictDepBuilds` to `true` by default, which makes the install fail when dependencies have unapproved build scripts. `wrangler` depends on `workerd` and `esbuild`, and (via miniflare) on `sharp` — all three need their postinstall scripts to produce platform binaries. - -C3 now writes or merges in a `pnpm-workspace.yaml` in the generated project that approves exactly those three packages. If other packages trigger this error, C3 also now interactively offers to retry with `pnpm approve-builds …` diff --git a/.github/workflows/auto-assign-issues.yml b/.github/workflows/auto-assign-issues.yml deleted file mode 100644 index 16f24788a5..0000000000 --- a/.github/workflows/auto-assign-issues.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Auto-assign Issues - -on: - issues: - types: [labeled] - -jobs: - assign-issue: - runs-on: ubuntu-slim - - permissions: - issues: write - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: 22 # need this version for `Set` methods - - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 - - - name: Install Dependencies - run: pnpm i -F tools --frozen-lockfile - - - name: Auto-assign issue - run: node -r esbuild-register tools/github-workflow-helpers/auto-assign-issues.ts - env: - INPUT_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.oxfmtrc.jsonc b/.oxfmtrc.jsonc index 8d65267e44..90dfeed2fb 100644 --- a/.oxfmtrc.jsonc +++ b/.oxfmtrc.jsonc @@ -57,6 +57,7 @@ ".github/pull_request_template.md", "fixtures/interactive-dev-tests/src/startup-error.ts", "packages/vite-plugin-cloudflare/playground/**/*.d.ts", + "fixtures/**/worker-configuration.d.ts", "packages/local-explorer-ui/src/routeTree.gen.ts", "packages/local-explorer-ui/src/api/generated", ], diff --git a/fixtures/experimental-new-config/cloudflare.config.ts b/fixtures/experimental-new-config/cloudflare.config.ts new file mode 100644 index 0000000000..936521a489 --- /dev/null +++ b/fixtures/experimental-new-config/cloudflare.config.ts @@ -0,0 +1,11 @@ +import { bindings, defineWorker } from "wrangler/experimental-config"; +import * as entrypoint from "./src/index.ts" with { type: "cf-worker" }; + +export default defineWorker((ctx) => ({ + name: "experimental-new-config", + entrypoint, + compatibilityDate: "2026-05-18", + env: { + MY_TEXT: bindings.text(`The mode is ${ctx.mode}`), + }, +})); diff --git a/fixtures/experimental-new-config/package.json b/fixtures/experimental-new-config/package.json new file mode 100644 index 0000000000..83bbda8431 --- /dev/null +++ b/fixtures/experimental-new-config/package.json @@ -0,0 +1,21 @@ +{ + "name": "@fixture/experimental-new-config", + "private": true, + "scripts": { + "build": "wrangler deploy --x-new-config --dry-run --outdir=dist", + "check:type": "tsc --build", + "deploy": "wrangler deploy --x-new-config", + "dev": "wrangler dev --x-new-config", + "test:ci": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "catalog:default", + "@fixture/shared": "workspace:*", + "typescript": "catalog:default", + "undici": "catalog:default", + "vitest": "catalog:default", + "wrangler": "workspace:*" + } +} diff --git a/fixtures/experimental-new-config/src/index.ts b/fixtures/experimental-new-config/src/index.ts new file mode 100644 index 0000000000..ee695db5c7 --- /dev/null +++ b/fixtures/experimental-new-config/src/index.ts @@ -0,0 +1,7 @@ +import { env } from "cloudflare:workers"; + +export default { + fetch() { + return new Response(env.MY_TEXT); + }, +} satisfies ExportedHandler; diff --git a/fixtures/experimental-new-config/test/index.test.ts b/fixtures/experimental-new-config/test/index.test.ts new file mode 100644 index 0000000000..f37cb96b6f --- /dev/null +++ b/fixtures/experimental-new-config/test/index.test.ts @@ -0,0 +1,210 @@ +import childProcess from "node:child_process"; +import { existsSync, writeFileSync } from "node:fs"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { removeDir } from "@fixture/shared/src/fs-helpers"; +import { afterAll, beforeAll, describe, test } from "vitest"; +import { + runWranglerDev, + wranglerEntryPath, +} from "../../shared/src/run-wrangler-long-lived"; + +const fixtureDir = path.resolve(__dirname, ".."); + +/** + * Spawn `wrangler` synchronously inside the given working directory. + */ +function spawnWrangler(cwd: string, args: string[]) { + return childProcess.spawnSync( + process.execPath, + [wranglerEntryPath, ...args], + { + cwd, + env: { + ...process.env, + WRANGLER_LOG_PATH: "", + NO_COLOR: "1", + FORCE_COLOR: "0", + }, + } + ); +} + +async function getTmpDir() { + return fs.mkdtemp(path.join(os.tmpdir(), "wrangler-new-config-")); +} + +/** + * Stage the fixture in a temporary directory. Symlinks `node_modules` so + * the staged cloudflare.config.ts can resolve `wrangler/experimental-config`. + */ +async function stageFixture(): Promise { + const tmp = await getTmpDir(); + for (const name of [ + "package.json", + "src", + "cloudflare.config.ts", + "wrangler.config.ts", + "worker-configuration.d.ts", + "tsconfig.json", + "tsconfig.node.json", + "tsconfig.worker.json", + ]) { + const srcPath = path.join(fixtureDir, name); + if (existsSync(srcPath)) { + await fs.cp(srcPath, path.join(tmp, name), { recursive: true }); + } + } + // Symlink the fixture's node_modules so package resolution works. + const fixtureNodeModules = path.join(fixtureDir, "node_modules"); + if (existsSync(fixtureNodeModules)) { + await fs.symlink(fixtureNodeModules, path.join(tmp, "node_modules"), "dir"); + } + return tmp; +} + +describe("--x-new-config deploy --dry-run", () => { + test("builds successfully and emits the worker bundle", async ({ + expect, + }) => { + const tmpDir = await stageFixture(); + try { + const outDir = path.join(tmpDir, "out"); + const result = spawnWrangler(tmpDir, [ + "deploy", + "--x-new-config", + "--dry-run", + `--outdir=${outDir}`, + ]); + expect(result.status, result.stderr.toString()).toBe(0); + expect(existsSync(path.join(outDir, "index.js"))).toBe(true); + } finally { + removeDir(tmpDir, { fireAndForget: true }); + } + }); + + test("rejects --config when used with --x-new-config", async ({ expect }) => { + const tmpDir = await stageFixture(); + try { + const result = spawnWrangler(tmpDir, [ + "deploy", + "--x-new-config", + "--config", + "./some-other.jsonc", + "--dry-run", + ]); + expect(result.status).not.toBe(0); + expect(result.stderr.toString()).toContain( + "--config is not supported with --experimental-new-config" + ); + } finally { + removeDir(tmpDir, { fireAndForget: true }); + } + }); + + test("rejects on out-of-scope commands (kv namespace list)", async ({ + expect, + }) => { + const tmpDir = await stageFixture(); + try { + const result = spawnWrangler(tmpDir, [ + "kv", + "namespace", + "list", + "--x-new-config", + ]); + expect(result.status).not.toBe(0); + // Yargs strict-mode rejection — the flag is only declared on the + // commands that support it, so yargs reports it as unknown elsewhere. + expect(result.stderr.toString()).toContain("Unknown arguments"); + expect(result.stderr.toString()).toContain("x-new-config"); + } finally { + removeDir(tmpDir, { fireAndForget: true }); + } + }); + + test("silently ignores adjacent wrangler.json", async ({ expect }) => { + const tmpDir = await stageFixture(); + try { + writeFileSync( + path.join(tmpDir, "wrangler.json"), + JSON.stringify({ + name: "should-be-ignored", + main: "src/does-not-exist.ts", + compatibility_date: "2020-01-01", + }) + ); + const outDir = path.join(tmpDir, "out"); + const result = spawnWrangler(tmpDir, [ + "deploy", + "--x-new-config", + "--dry-run", + `--outdir=${outDir}`, + ]); + // Should still succeed — `cloudflare.config.ts` is used; the + // `wrangler.json` is silently ignored. + expect(result.status, result.stderr.toString()).toBe(0); + expect(existsSync(path.join(outDir, "index.js"))).toBe(true); + } finally { + removeDir(tmpDir, { fireAndForget: true }); + } + }); + + test("--env staging surfaces in ctx.mode (bound text contains 'staging')", async ({ + expect, + }) => { + const tmpDir = await stageFixture(); + try { + const outDir = path.join(tmpDir, "out"); + const result = spawnWrangler(tmpDir, [ + "deploy", + "--x-new-config", + "--env", + "staging", + "--dry-run", + `--outdir=${outDir}`, + ]); + expect(result.status, result.stderr.toString()).toBe(0); + // The deploy --dry-run output prints the bound `MY_TEXT` value as + // part of the bindings table. With `--env staging`, the function- + // form `cloudflare.config.ts` evaluates `ctx.mode === "staging"`, so + // `bindings.text(`The mode is ${ctx.mode}`)` becomes + // `"The mode is staging"`. + const combined = result.stdout.toString() + result.stderr.toString(); + expect(combined).toContain("staging"); + } finally { + removeDir(tmpDir, { fireAndForget: true }); + } + }); +}); + +describe("--x-new-config dev", () => { + let tmpDir: string; + let stop: (() => Promise) | undefined; + let ip: string; + let port: number; + + beforeAll(async () => { + tmpDir = await stageFixture(); + ({ ip, port, stop } = await runWranglerDev(tmpDir, [ + "--x-new-config", + "--env", + "dev", + "--port=0", + "--inspector-port=0", + ])); + }); + + afterAll(async () => { + await stop?.(); + removeDir(tmpDir, { fireAndForget: true }); + }); + + test("serves the correct response for a worker configured via cloudflare.config.ts", async ({ + expect, + }) => { + const response = await fetch(`http://${ip}:${port}/`); + expect(await response.text()).toBe("The mode is dev"); + }); +}); diff --git a/fixtures/experimental-new-config/tsconfig.json b/fixtures/experimental-new-config/tsconfig.json new file mode 100644 index 0000000000..b52af703bd --- /dev/null +++ b/fixtures/experimental-new-config/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/fixtures/experimental-new-config/tsconfig.node.json b/fixtures/experimental-new-config/tsconfig.node.json new file mode 100644 index 0000000000..78911ed4e2 --- /dev/null +++ b/fixtures/experimental-new-config/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["test"] +} diff --git a/fixtures/experimental-new-config/tsconfig.worker.json b/fixtures/experimental-new-config/tsconfig.worker.json new file mode 100644 index 0000000000..60c89aca62 --- /dev/null +++ b/fixtures/experimental-new-config/tsconfig.worker.json @@ -0,0 +1,13 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "compilerOptions": { + "allowImportingTsExtensions": true, + "types": ["@cloudflare/workers-types/experimental"] + }, + "include": [ + "src", + "cloudflare.config.ts", + "wrangler.config.ts", + "worker-configuration.d.ts" + ] +} diff --git a/fixtures/experimental-new-config/turbo.json b/fixtures/experimental-new-config/turbo.json new file mode 100644 index 0000000000..6556dcf3e5 --- /dev/null +++ b/fixtures/experimental-new-config/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/fixtures/experimental-new-config/vitest.config.mts b/fixtures/experimental-new-config/vitest.config.mts new file mode 100644 index 0000000000..846cddc419 --- /dev/null +++ b/fixtures/experimental-new-config/vitest.config.mts @@ -0,0 +1,9 @@ +import { defineProject, mergeConfig } from "vitest/config"; +import configShared from "../../vitest.shared"; + +export default mergeConfig( + configShared, + defineProject({ + test: {}, + }) +); diff --git a/fixtures/experimental-new-config/worker-configuration.d.ts b/fixtures/experimental-new-config/worker-configuration.d.ts new file mode 100644 index 0000000000..2e7a5f1f5c --- /dev/null +++ b/fixtures/experimental-new-config/worker-configuration.d.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ +// Generated by @cloudflare/config +import type { InferEnv, InferDurableNamespaces, InferMainModule, UnwrapConfig } from "wrangler/experimental-config"; +import type Config from "./cloudflare.config"; + +type WorkerConfig = UnwrapConfig; + +declare global { + namespace Cloudflare { + interface GlobalProps { + mainModule: InferMainModule; + durableNamespaces: InferDurableNamespaces; + } + interface Env extends InferEnv {} + } + interface Env extends Cloudflare.Env {} +} diff --git a/fixtures/experimental-new-config/wrangler.config.ts b/fixtures/experimental-new-config/wrangler.config.ts new file mode 100644 index 0000000000..92f77e91ea --- /dev/null +++ b/fixtures/experimental-new-config/wrangler.config.ts @@ -0,0 +1,6 @@ +import { defineWranglerConfig } from "wrangler/experimental-config"; + +// Minimal empty form — exercises the optional case (no tooling overrides; +// defaults apply). Function form + non-empty configs are exercised by +// other unit tests in `packages/wrangler/src/__tests__/`. +export default defineWranglerConfig({}); diff --git a/packages/config/src/worker-definition.ts b/packages/config/src/worker-definition.ts index 23a8cc78e0..a05e8ca205 100644 --- a/packages/config/src/worker-definition.ts +++ b/packages/config/src/worker-definition.ts @@ -13,10 +13,12 @@ import type { UserConfig } from "./types"; // TODO: Use declaration merging in the consuming package once this package is published export interface ConfigContext { /** - * The Vite [`mode`](https://vite.dev/guide/env-and-mode.html#modes) the - * config is being evaluated in (e.g. `"development"`, `"production"`). + * The mode the config is being evaluated in. + * Set via the `--mode` CLI flag. + * In Vite the mode defaults to `development` in `vite dev` and `production` in `vite build` ([more info](https://vite.dev/guide/env-and-mode.html#modes)). + * In Wrangler the mode defaults to `undefined`. */ - mode: string; + mode: string | undefined; } // We currently use Symbol.for rather than Symbol so that the symbol matches if duplicated across bundles diff --git a/packages/create-cloudflare/CHANGELOG.md b/packages/create-cloudflare/CHANGELOG.md index 59713e0085..8838b9aaae 100644 --- a/packages/create-cloudflare/CHANGELOG.md +++ b/packages/create-cloudflare/CHANGELOG.md @@ -1,5 +1,25 @@ # create-cloudflare +## 2.70.2 + +### Patch Changes + +- [#14193](https://github.com/cloudflare/workers-sdk/pull/14193) [`88519f9`](https://github.com/cloudflare/workers-sdk/commit/88519f9d0c3caf9a008a228c31568010477d80c6) Thanks [@petebacondarwin](https://github.com/petebacondarwin)! - Fix `create cloudflare` exiting with code `0` even after an unhandled error + +- [#14235](https://github.com/cloudflare/workers-sdk/pull/14235) [`13abd5a`](https://github.com/cloudflare/workers-sdk/commit/13abd5a1c4e51b264ac9dea02f86024a6cff570c) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "create-cloudflare" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ------------- | ------ | ------ | + | @tanstack/cli | 0.69.1 | 0.69.2 | + +- [#14193](https://github.com/cloudflare/workers-sdk/pull/14193) [`88519f9`](https://github.com/cloudflare/workers-sdk/commit/88519f9d0c3caf9a008a228c31568010477d80c6) Thanks [@petebacondarwin](https://github.com/petebacondarwin)! - Fix `create cloudflare` aborting with `ERR_PNPM_IGNORED_BUILDS` on pnpm 11 + + pnpm 11 flipped `strictDepBuilds` to `true` by default, which makes the install fail when dependencies have unapproved build scripts. `wrangler` depends on `workerd` and `esbuild`, and (via miniflare) on `sharp` — all three need their postinstall scripts to produce platform binaries. + + C3 now writes or merges in a `pnpm-workspace.yaml` in the generated project that approves exactly those three packages. If other packages trigger this error, C3 also now interactively offers to retry with `pnpm approve-builds …` + ## 2.70.1 ### Patch Changes diff --git a/packages/create-cloudflare/package.json b/packages/create-cloudflare/package.json index 16f5b9c41f..6a1516d00b 100644 --- a/packages/create-cloudflare/package.json +++ b/packages/create-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "create-cloudflare", - "version": "2.70.1", + "version": "2.70.2", "description": "A CLI for creating and deploying new applications to Cloudflare.", "keywords": [ "cloudflare", diff --git a/packages/deploy-helpers/CHANGELOG.md b/packages/deploy-helpers/CHANGELOG.md index aa6e8ab05f..afa180d03a 100644 --- a/packages/deploy-helpers/CHANGELOG.md +++ b/packages/deploy-helpers/CHANGELOG.md @@ -1,5 +1,14 @@ # @cloudflare/deploy-helpers +## 0.1.3 + +### Patch Changes + +- [#14259](https://github.com/cloudflare/workers-sdk/pull/14259) [`2ae6099`](https://github.com/cloudflare/workers-sdk/commit/2ae6099db77c076fb7e6e782d2f0ebd7ba86dbbb) Thanks [@emily-shen](https://github.com/emily-shen)! - Move worker build step earlier in deploy/upload step, before upload specific config validation + +- Updated dependencies [[`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f), [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe)]: + - miniflare@4.20260611.0 + ## 0.1.2 ### Patch Changes diff --git a/packages/deploy-helpers/package.json b/packages/deploy-helpers/package.json index c246d638ed..4ea4e1ca94 100644 --- a/packages/deploy-helpers/package.json +++ b/packages/deploy-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/deploy-helpers", - "version": "0.1.2", + "version": "0.1.3", "description": "Internal deploy helpers for workers-sdk. Not intended for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/deploy-helpers#readme", "bugs": { diff --git a/packages/miniflare/CHANGELOG.md b/packages/miniflare/CHANGELOG.md index 8c143de4d1..13065c2a43 100644 --- a/packages/miniflare/CHANGELOG.md +++ b/packages/miniflare/CHANGELOG.md @@ -1,5 +1,31 @@ # miniflare +## 4.20260611.0 + +### Minor Changes + +- [#14119](https://github.com/cloudflare/workers-sdk/pull/14119) [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe) Thanks [@tahmid-23](https://github.com/tahmid-23)! - Add support for serving R2 bucket objects publicly via the dev server + + Each local R2 bucket is now exposed under `/cdn-cgi/local/r2/public//` on the existing user-facing dev server. The `` is the bucket's `id` when set, otherwise its binding name. Buckets with a `remoteProxyConnectionString` are not exposed. The endpoint supports GET and HEAD, range requests, conditional headers, and forwards stored HTTP metadata. + +### Patch Changes + +- [#14246](https://github.com/cloudflare/workers-sdk/pull/14246) [`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ---------- | ------------ | ------------ | + | workerd | 1.20260609.1 | 1.20260610.1 | + +- [#14256](https://github.com/cloudflare/workers-sdk/pull/14256) [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ---------- | ------------ | ------------ | + | workerd | 1.20260610.1 | 1.20260611.1 | + ## 4.20260609.0 ### Patch Changes diff --git a/packages/miniflare/package.json b/packages/miniflare/package.json index 85664a506a..b0a52223bb 100644 --- a/packages/miniflare/package.json +++ b/packages/miniflare/package.json @@ -1,6 +1,6 @@ { "name": "miniflare", - "version": "4.20260609.0", + "version": "4.20260611.0", "description": "Fun, full-featured, fully-local simulator for Cloudflare Workers", "keywords": [ "cloudflare", diff --git a/packages/mock-npm-registry/src/index.ts b/packages/mock-npm-registry/src/index.ts index f3efe3b663..5828b224ca 100644 --- a/packages/mock-npm-registry/src/index.ts +++ b/packages/mock-npm-registry/src/index.ts @@ -213,6 +213,11 @@ async function writeVerdaccioConfig( // @ts-expect-error the `listen` property can also be a simple string. listen: `localhost:${registryPort}`, storage: "./storage", + // Raise the publish payload limit well above Verdaccio's 10mb default. + // Workspace packages such as `wrangler` produce tarballs (with source + // maps and metafiles) whose base64-encoded JSON publish body now + // exceeds the default, causing HTTP 413 from `pnpm publish`. + max_body_size: "100mb", uplinks: { // Consider adding the Cloudflare internal mirror registry too. npmJS: { url: "https://registry.npmjs.org/" }, diff --git a/packages/pages-shared/CHANGELOG.md b/packages/pages-shared/CHANGELOG.md index ee6d842f93..2082844bcf 100644 --- a/packages/pages-shared/CHANGELOG.md +++ b/packages/pages-shared/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloudflare/pages-shared +## 0.13.145 + +### Patch Changes + +- Updated dependencies [[`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f), [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe)]: + - miniflare@4.20260611.0 + ## 0.13.144 ### Patch Changes diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index bed2e575fa..b7fce73932 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/pages-shared", - "version": "0.13.144", + "version": "0.13.145", "repository": { "type": "git", "url": "https://github.com/cloudflare/workers-sdk.git", diff --git a/packages/vite-plugin-cloudflare/CHANGELOG.md b/packages/vite-plugin-cloudflare/CHANGELOG.md index 48a205e35d..7658c6a2f9 100644 --- a/packages/vite-plugin-cloudflare/CHANGELOG.md +++ b/packages/vite-plugin-cloudflare/CHANGELOG.md @@ -1,5 +1,17 @@ # @cloudflare/vite-plugin +## 1.40.2 + +### Patch Changes + +- [#14184](https://github.com/cloudflare/workers-sdk/pull/14184) [`e305126`](https://github.com/cloudflare/workers-sdk/commit/e30512641a194a628767ca9c44ff0499a4b326c1) Thanks [@penalosa](https://github.com/penalosa)! - Drop the `--config` flag from the experimental internal `cf-vite` delegate binary. + + The wrangler config file is now discovered by `cloudflare()` itself rather than being passed through, keeping `cf-vite`'s flag surface (`--mode`, `--port`, `--host`, `--local`) in sync with the sibling `cf-wrangler` delegate. `cf-vite` is an internal integration point spawned by Cloudflare tooling and is not intended to be run directly by users. + +- Updated dependencies [[`98c9afe`](https://github.com/cloudflare/workers-sdk/commit/98c9afe2e3bb6cbed6d56d8ad781d50e9a604926), [`e305126`](https://github.com/cloudflare/workers-sdk/commit/e30512641a194a628767ca9c44ff0499a4b326c1), [`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f), [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1), [`25722ac`](https://github.com/cloudflare/workers-sdk/commit/25722acff7a195cffb858791cfcd43c79a70e217), [`41f75c0`](https://github.com/cloudflare/workers-sdk/commit/41f75c0b2ba3f0f4a88ca792c1b5c8914374d61d), [`10b5538`](https://github.com/cloudflare/workers-sdk/commit/10b553819addbcd1224f66d5b52bb7c7f7c8e602), [`818c105`](https://github.com/cloudflare/workers-sdk/commit/818c105522e6d198f92cc31fd465477774c1bcf2), [`2ae6099`](https://github.com/cloudflare/workers-sdk/commit/2ae6099db77c076fb7e6e782d2f0ebd7ba86dbbb), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe), [`e8561c2`](https://github.com/cloudflare/workers-sdk/commit/e8561c2621ebc5e0c28848fb5a87a982dc77647f)]: + - wrangler@4.100.0 + - miniflare@4.20260611.0 + ## 1.40.1 ### Patch Changes diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index 4fb16f2122..2d89870edb 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/vite-plugin", - "version": "1.40.1", + "version": "1.40.2", "description": "Cloudflare plugin for Vite", "keywords": [ "cloudflare", diff --git a/packages/vitest-pool-workers/CHANGELOG.md b/packages/vitest-pool-workers/CHANGELOG.md index 2696c94bac..ae037d84ae 100644 --- a/packages/vitest-pool-workers/CHANGELOG.md +++ b/packages/vitest-pool-workers/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloudflare/vitest-pool-workers +## 0.16.15 + +### Patch Changes + +- Updated dependencies [[`98c9afe`](https://github.com/cloudflare/workers-sdk/commit/98c9afe2e3bb6cbed6d56d8ad781d50e9a604926), [`e305126`](https://github.com/cloudflare/workers-sdk/commit/e30512641a194a628767ca9c44ff0499a4b326c1), [`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f), [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1), [`25722ac`](https://github.com/cloudflare/workers-sdk/commit/25722acff7a195cffb858791cfcd43c79a70e217), [`41f75c0`](https://github.com/cloudflare/workers-sdk/commit/41f75c0b2ba3f0f4a88ca792c1b5c8914374d61d), [`10b5538`](https://github.com/cloudflare/workers-sdk/commit/10b553819addbcd1224f66d5b52bb7c7f7c8e602), [`818c105`](https://github.com/cloudflare/workers-sdk/commit/818c105522e6d198f92cc31fd465477774c1bcf2), [`2ae6099`](https://github.com/cloudflare/workers-sdk/commit/2ae6099db77c076fb7e6e782d2f0ebd7ba86dbbb), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe), [`e8561c2`](https://github.com/cloudflare/workers-sdk/commit/e8561c2621ebc5e0c28848fb5a87a982dc77647f)]: + - wrangler@4.100.0 + - miniflare@4.20260611.0 + ## 0.16.14 ### Patch Changes diff --git a/packages/vitest-pool-workers/package.json b/packages/vitest-pool-workers/package.json index 13fc799454..2e0e345f54 100644 --- a/packages/vitest-pool-workers/package.json +++ b/packages/vitest-pool-workers/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/vitest-pool-workers", - "version": "0.16.14", + "version": "0.16.15", "description": "Workers Vitest integration for writing Vitest unit and integration tests that run inside the Workers runtime", "keywords": [ "cloudflare", diff --git a/packages/workers-auth/CHANGELOG.md b/packages/workers-auth/CHANGELOG.md index 8620a99e88..85bc9d46f4 100644 --- a/packages/workers-auth/CHANGELOG.md +++ b/packages/workers-auth/CHANGELOG.md @@ -1,5 +1,21 @@ # @cloudflare/workers-auth +## 0.2.0 + +### Minor Changes + +- [#14185](https://github.com/cloudflare/workers-sdk/pull/14185) [`98c9afe`](https://github.com/cloudflare/workers-sdk/commit/98c9afe2e3bb6cbed6d56d8ad781d50e9a604926) Thanks [@penalosa](https://github.com/penalosa)! - Make the OAuth identity and token storage injectable, and add a shared env-credential resolver + + `createOAuthFlow` now takes the consumer's OAuth identity (`clientId`, `consent`, `redirectUri`) and token `storage` on its context, so other Cloudflare CLIs can reuse the flow under their own OAuth app and store tokens in their own location/format. Also adds a shared env→credential resolver (`getAuthFromEnv`, `getAPIToken`, `requireApiToken`). + +### Patch Changes + +- [#14213](https://github.com/cloudflare/workers-sdk/pull/14213) [`10b5538`](https://github.com/cloudflare/workers-sdk/commit/10b553819addbcd1224f66d5b52bb7c7f7c8e602) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - Improve authentication error messages with specific failure reasons + + When authentication fails (e.g. during `wrangler dev --remote` or when using remote bindings), the error message now explains exactly what went wrong -- whether no credentials were found, the token expired, or the environment is non-interactive -- and lists actionable steps to fix it, including a `wrangler whoami` tip. + + Previously, auth failures could produce multiple confusing errors (e.g. "Failed to fetch auth token: 400 Bad Request" followed by "Failed to start the remote proxy session"). Now a single, clear error is shown. + ## 0.1.1 ### Patch Changes diff --git a/packages/workers-auth/package.json b/packages/workers-auth/package.json index c92407768e..67781f4d81 100644 --- a/packages/workers-auth/package.json +++ b/packages/workers-auth/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/workers-auth", - "version": "0.1.1", + "version": "0.2.0", "description": "Internal OAuth 2.0 + PKCE flow for Cloudflare CLIs. Not intended for external use — APIs may change without notice.", "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/workers-auth#readme", "bugs": { diff --git a/packages/workers-utils/src/types.ts b/packages/workers-utils/src/types.ts index 203e6711c5..e9f957724b 100644 --- a/packages/workers-utils/src/types.ts +++ b/packages/workers-utils/src/types.ts @@ -579,6 +579,12 @@ export interface StartDevWorkerInput { /** Re-generate your worker types when your Wrangler configuration file changes */ generateTypes?: boolean; + /** + * Experimental: Use `cloudflare.config.ts` + optional `wrangler.config.ts` + * instead of `wrangler.json[c]` / `wrangler.toml`. + */ + experimentalNewConfig?: boolean; + /** Tunnel configuration for this dev session. */ tunnel?: { enabled: boolean; diff --git a/packages/wrangler/CHANGELOG.md b/packages/wrangler/CHANGELOG.md index 307ee935c4..e4e24ea116 100644 --- a/packages/wrangler/CHANGELOG.md +++ b/packages/wrangler/CHANGELOG.md @@ -1,5 +1,105 @@ # wrangler +## 4.100.0 + +### Minor Changes + +- [#14119](https://github.com/cloudflare/workers-sdk/pull/14119) [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe) Thanks [@tahmid-23](https://github.com/tahmid-23)! - Serve local R2 bucket objects publicly via the dev server + + When running `wrangler dev` locally, objects in each local R2 binding are now reachable under `/cdn-cgi/local/r2/public//` on the existing dev server, simulating a public bucket. The `` is the bucket's `bucket_name` when set, otherwise its `binding`. Bindings configured with `remote: true` are not exposed. + +- [#14202](https://github.com/cloudflare/workers-sdk/pull/14202) [`e8561c2`](https://github.com/cloudflare/workers-sdk/commit/e8561c2621ebc5e0c28848fb5a87a982dc77647f) Thanks [@jamesopstad](https://github.com/jamesopstad)! - Add experimental `--x-new-config` flag for authoring config in TypeScript + + This is an experimental, opt-in feature. When enabled, `wrangler dev`, `wrangler build`, `wrangler deploy`, `wrangler versions upload`, and `wrangler versions deploy` load the Worker's configuration from a `cloudflare.config.ts` file instead of `wrangler.json` / `wrangler.jsonc` / `wrangler.toml`. Additionally, an optional `wrangler.config.ts` file can be provided for Wrangler-specific dev/build configuration. + + - **`cloudflare.config.ts`** (required) — Worker runtime configuration (bindings, triggers, observability, placement, limits, compatibility, routes, etc.). Authored via `defineWorker` from `wrangler/experimental-config`. + - **`wrangler.config.ts`** (optional) — Tooling / bundling / dev-server configuration (`noBundle`, `minify`, `alias`, `define`, `rules`, `tsconfig`, `build`, `dev`, `assetsDirectory`, etc.). Authored via `defineWranglerConfig` from `wrangler/experimental-config`. + + Per-environment configuration is via `ctx.mode` branching inside the function form of either file. + + Example `cloudflare.config.ts`: + + ```ts + import { defineWorker, bindings } from "wrangler/experimental-config"; + import * as entrypoint from "./src/index.ts" with { type: "cf-worker" }; + + export default defineWorker((ctx) => ({ + name: "my-worker", + entrypoint, + compatibilityDate: "2026-05-18", + env: { + MY_KV: bindings.kv(), + MY_TEXT: bindings.text(`The mode is ${ctx.mode}`), + }, + })); + ``` + + Example `wrangler.config.ts`: + + ```ts + import { defineWranglerConfig } from "wrangler/experimental-config"; + + export default defineWranglerConfig({ + minify: true, + assetsDirectory: "./public", + }); + ``` + + Because this is experimental, the flag, the config formats, and the `wrangler/experimental-config` exports may change in any release. + +### Patch Changes + +- [#14185](https://github.com/cloudflare/workers-sdk/pull/14185) [`98c9afe`](https://github.com/cloudflare/workers-sdk/commit/98c9afe2e3bb6cbed6d56d8ad781d50e9a604926) Thanks [@penalosa](https://github.com/penalosa)! - Use the shared env-credential resolver from `@cloudflare/workers-auth` + + No user-facing behaviour change. Credential resolution order (global API key + email → `CLOUDFLARE_API_TOKEN` → stored OAuth token) is preserved. + +- [#14184](https://github.com/cloudflare/workers-sdk/pull/14184) [`e305126`](https://github.com/cloudflare/workers-sdk/commit/e30512641a194a628767ca9c44ff0499a4b326c1) Thanks [@penalosa](https://github.com/penalosa)! - Add an experimental `cf-wrangler` delegate entrypoint for projects that can't use `@cloudflare/vite-plugin` (service workers, old compatibility dates, Python, Rust, etc.). + + `cf-wrangler dev` starts the same local dev server as `wrangler dev` — it sits directly on wrangler's internal dev server, so the bundling and runtime behaviour are identical — but exposes a deliberately narrow CLI surface (`--mode`, `--port`, `--host`, `--local`) for a parent CLI to delegate to, and other dev server config options are read from the wrangler config file. + + This replaces the separate `@cloudflare/wrangler-bundler` package. This is an internal integration point and is not intended to be run directly by users. + +- [#14246](https://github.com/cloudflare/workers-sdk/pull/14246) [`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ---------- | ------------ | ------------ | + | workerd | 1.20260609.1 | 1.20260610.1 | + +- [#14256](https://github.com/cloudflare/workers-sdk/pull/14256) [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1) Thanks [@dependabot](https://github.com/apps/dependabot)! - Update dependencies of "miniflare", "wrangler" + + The following dependency versions have been updated: + + | Dependency | From | To | + | ---------- | ------------ | ------------ | + | workerd | 1.20260610.1 | 1.20260611.1 | + +- [#14243](https://github.com/cloudflare/workers-sdk/pull/14243) [`25722ac`](https://github.com/cloudflare/workers-sdk/commit/25722acff7a195cffb858791cfcd43c79a70e217) Thanks [@com6056](https://github.com/com6056)! - Fix a memory leak that could make long-running headless `wrangler dev` sessions unresponsive + + Long-running `wrangler dev` sessions with no DevTools attached (for example using the containers feature under sustained traffic) could gradually consume unbounded memory and eventually stop accepting connections. The inspector proxy now only enables network tracking while a DevTools client is connected, so the buildup no longer happens. Interactive debugging is unaffected. Fixes #14191. + +- [#14230](https://github.com/cloudflare/workers-sdk/pull/14230) [`41f75c0`](https://github.com/cloudflare/workers-sdk/commit/41f75c0b2ba3f0f4a88ca792c1b5c8914374d61d) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - Improve D1 error messages for missing or conflicting options + + Error messages for `d1 execute`, `d1 export`, `d1 time-travel restore`, and `d1 insights` now clearly state which option is missing or conflicting, explain why the combination is invalid, and suggest how to fix the issue. + + Additionally, duration validation errors in `d1 insights` are now thrown as `UserError` instead of plain `Error`, ensuring they are displayed cleanly to users rather than as unexpected crashes. + +- [#14213](https://github.com/cloudflare/workers-sdk/pull/14213) [`10b5538`](https://github.com/cloudflare/workers-sdk/commit/10b553819addbcd1224f66d5b52bb7c7f7c8e602) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - Improve authentication error messages with specific failure reasons + + When authentication fails (e.g. during `wrangler dev --remote` or when using remote bindings), the error message now explains exactly what went wrong -- whether no credentials were found, the token expired, or the environment is non-interactive -- and lists actionable steps to fix it, including a `wrangler whoami` tip. + + Previously, auth failures could produce multiple confusing errors (e.g. "Failed to fetch auth token: 400 Bad Request" followed by "Failed to start the remote proxy session"). Now a single, clear error is shown. + +- [#14233](https://github.com/cloudflare/workers-sdk/pull/14233) [`818c105`](https://github.com/cloudflare/workers-sdk/commit/818c105522e6d198f92cc31fd465477774c1bcf2) Thanks [@dario-piotrowicz](https://github.com/dario-piotrowicz)! - Improve R2 Sippy error messages + + Now error messages in `wrangler r2 bucket sippy` follow a consistent pattern: they describe what is missing, name the exact `--flag` to use, and provide context (e.g. example values, links to the dashboard). Previously, many errors said only "Error: must provide --flag." with no guidance on what the flag does or how to obtain the value. + +- [#14259](https://github.com/cloudflare/workers-sdk/pull/14259) [`2ae6099`](https://github.com/cloudflare/workers-sdk/commit/2ae6099db77c076fb7e6e782d2f0ebd7ba86dbbb) Thanks [@emily-shen](https://github.com/emily-shen)! - Move worker build step earlier in deploy/upload step, before upload specific config validation + +- Updated dependencies [[`f3990b2`](https://github.com/cloudflare/workers-sdk/commit/f3990b2358ef49cd6e1ab16de27e25dcd949896f), [`4597f08`](https://github.com/cloudflare/workers-sdk/commit/4597f085d25c7d066ecf056de313e194f41094d1), [`2047a32`](https://github.com/cloudflare/workers-sdk/commit/2047a32cf78886b71b794a3dfac946a146ab3ffe)]: + - miniflare@4.20260611.0 + ## 4.99.0 ### Minor Changes diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index a459c40ebb..ad05dc847a 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -1,6 +1,6 @@ { "name": "wrangler", - "version": "4.99.0", + "version": "4.100.0", "description": "Command-line interface for all things Cloudflare Workers", "keywords": [ "assembly", @@ -50,9 +50,20 @@ ], "main": "wrangler-dist/cli.js", "types": "wrangler-dist/cli.d.ts", + "exports": { + ".": { + "types": "./wrangler-dist/cli.d.ts", + "default": "./wrangler-dist/cli.js" + }, + "./experimental-config": { + "types": "./wrangler-dist/experimental-config.d.mts", + "import": "./wrangler-dist/experimental-config.mjs" + }, + "./package.json": "./package.json" + }, "scripts": { "assert-git-version": "node -r esbuild-register scripts/assert-git-version.ts", - "build": "pnpm run clean && pnpm tsup && pnpm run generate-json-schema", + "build": "pnpm run clean && pnpm tsup && pnpm tsdown && pnpm run generate-json-schema", "check:type": "tsc -p ./tsconfig.json && tsc -p ./templates/tsconfig.json", "clean": "node -r esbuild-register ../../tools/clean/clean.ts wrangler-dist miniflare-dist emitted-types", "dev": "pnpm run clean && concurrently -c black,blue --kill-others-on-fail false \"pnpm tsup --watch src --watch ../containers-shared/src --watch ../cli\" \"pnpm run check:type --watch --preserveWatchOutput\"", @@ -80,6 +91,7 @@ "@bomb.sh/tab": "^0.0.12", "@cloudflare/cli-shared-helpers": "workspace:*", "@cloudflare/codemod": "workspace:*", + "@cloudflare/config": "workspace:*", "@cloudflare/containers-shared": "workspace:*", "@cloudflare/deploy-helpers": "workspace:*", "@cloudflare/pages-shared": "workspace:^", @@ -159,6 +171,7 @@ "tree-kill": "catalog:default", "ts-dedent": "^2.2.0", "ts-json-schema-generator": "^1.5.0", + "tsdown": "0.16.3", "tsup": "8.3.0", "typescript": "catalog:default", "undici": "catalog:default", @@ -167,7 +180,8 @@ "ws": "catalog:default", "xxhash-wasm": "^1.0.1", "yaml": "^2.8.1", - "yargs": "^17.7.2" + "yargs": "^17.7.2", + "zod": "^4.4.3" }, "peerDependencies": { "@cloudflare/workers-types": "catalog:default" diff --git a/packages/wrangler/src/__tests__/experimental-config/convert.test.ts b/packages/wrangler/src/__tests__/experimental-config/convert.test.ts new file mode 100644 index 0000000000..d5072bc9d9 --- /dev/null +++ b/packages/wrangler/src/__tests__/experimental-config/convert.test.ts @@ -0,0 +1,340 @@ +import { describe, it } from "vitest"; +import { convertToolingConfig } from "../../experimental-config/convert"; + +describe("convertToolingConfig", () => { + describe("empty input", () => { + it("returns an empty object for an empty config", ({ expect }) => { + expect(convertToolingConfig({})).toEqual({}); + }); + + it("omits keys for undefined input fields rather than emitting `undefined` values", ({ + expect, + }) => { + const result = convertToolingConfig({}); + expect(Object.keys(result)).toEqual([]); + }); + }); + + describe("camelCase → snake_case top-level mappings", () => { + it("maps noBundle to no_bundle", ({ expect }) => { + expect(convertToolingConfig({ noBundle: true })).toEqual({ + no_bundle: true, + }); + }); + + it("passes minify through unchanged", ({ expect }) => { + expect(convertToolingConfig({ minify: true })).toEqual({ + minify: true, + }); + }); + + it("maps keepNames to keep_names", ({ expect }) => { + expect(convertToolingConfig({ keepNames: false })).toEqual({ + keep_names: false, + }); + }); + + it("passes alias through unchanged", ({ expect }) => { + expect( + convertToolingConfig({ alias: { "node:fs": "memfs", foo: "bar" } }) + ).toEqual({ + alias: { "node:fs": "memfs", foo: "bar" }, + }); + }); + + it("passes define through unchanged", ({ expect }) => { + expect( + convertToolingConfig({ + define: { "process.env.NODE_ENV": '"production"' }, + }) + ).toEqual({ + define: { "process.env.NODE_ENV": '"production"' }, + }); + }); + + it("maps findAdditionalModules to find_additional_modules", ({ + expect, + }) => { + expect(convertToolingConfig({ findAdditionalModules: true })).toEqual({ + find_additional_modules: true, + }); + }); + + it("maps preserveFileNames to preserve_file_names", ({ expect }) => { + expect(convertToolingConfig({ preserveFileNames: true })).toEqual({ + preserve_file_names: true, + }); + }); + + it("maps baseDir to base_dir", ({ expect }) => { + expect(convertToolingConfig({ baseDir: "./src" })).toEqual({ + base_dir: "./src", + }); + }); + + it("passes rules through unchanged", ({ expect }) => { + const rules = [ + { type: "Text" as const, globs: ["**/*.txt"], fallthrough: true }, + { type: "CompiledWasm" as const, globs: ["**/*.wasm"] }, + ]; + expect(convertToolingConfig({ rules })).toEqual({ rules }); + }); + + it("maps wasmModules to wasm_modules", ({ expect }) => { + expect( + convertToolingConfig({ wasmModules: { foo: "./foo.wasm" } }) + ).toEqual({ + wasm_modules: { foo: "./foo.wasm" }, + }); + }); + + it("maps textBlobs to text_blobs", ({ expect }) => { + expect(convertToolingConfig({ textBlobs: { foo: "./foo.txt" } })).toEqual( + { + text_blobs: { foo: "./foo.txt" }, + } + ); + }); + + it("maps dataBlobs to data_blobs", ({ expect }) => { + expect(convertToolingConfig({ dataBlobs: { foo: "./foo.bin" } })).toEqual( + { + data_blobs: { foo: "./foo.bin" }, + } + ); + }); + + it("passes tsconfig through unchanged", ({ expect }) => { + expect(convertToolingConfig({ tsconfig: "./tsconfig.json" })).toEqual({ + tsconfig: "./tsconfig.json", + }); + }); + + it("maps jsxFactory to jsx_factory", ({ expect }) => { + expect(convertToolingConfig({ jsxFactory: "h" })).toEqual({ + jsx_factory: "h", + }); + }); + + it("maps jsxFragment to jsx_fragment", ({ expect }) => { + expect(convertToolingConfig({ jsxFragment: "Fragment" })).toEqual({ + jsx_fragment: "Fragment", + }); + }); + + it("maps uploadSourceMaps to upload_source_maps", ({ expect }) => { + expect(convertToolingConfig({ uploadSourceMaps: true })).toEqual({ + upload_source_maps: true, + }); + }); + + it("maps sendMetrics to send_metrics", ({ expect }) => { + expect(convertToolingConfig({ sendMetrics: false })).toEqual({ + send_metrics: false, + }); + }); + }); + + describe("pythonModules", () => { + it("maps to python_modules with exclude preserved", ({ expect }) => { + expect( + convertToolingConfig({ pythonModules: { exclude: ["a", "b"] } }) + ).toEqual({ + python_modules: { exclude: ["a", "b"] }, + }); + }); + + it("passes an empty pythonModules object through as python_modules: {}", ({ + expect, + }) => { + expect(convertToolingConfig({ pythonModules: {} })).toEqual({ + python_modules: {}, + }); + }); + + it("preserves an explicit empty exclude array", ({ expect }) => { + expect(convertToolingConfig({ pythonModules: { exclude: [] } })).toEqual({ + python_modules: { exclude: [] }, + }); + }); + }); + + describe("build", () => { + it("maps watchDir to watch_dir (string form)", ({ expect }) => { + expect( + convertToolingConfig({ + build: { command: "npm run build", cwd: ".", watchDir: "./src" }, + }) + ).toEqual({ + build: { + command: "npm run build", + cwd: ".", + watch_dir: "./src", + }, + }); + }); + + it("maps watchDir to watch_dir (array form)", ({ expect }) => { + expect( + convertToolingConfig({ + build: { watchDir: ["./src", "./vendor"] }, + }) + ).toEqual({ + build: { + command: undefined, + cwd: undefined, + watch_dir: ["./src", "./vendor"], + }, + }); + }); + + it("emits build with undefined sub-fields when only watchDir is provided", ({ + expect, + }) => { + // The conversion function unconditionally lays out the build object + // when `parsed.build !== undefined`, leaving missing sub-fields as + // `undefined`. This is intentional and matches downstream consumers. + const result = convertToolingConfig({ build: { command: "x" } }); + expect(result.build).toEqual({ + command: "x", + cwd: undefined, + watch_dir: undefined, + }); + }); + }); + + describe("dev", () => { + it("maps every renamed sub-field", ({ expect }) => { + expect( + convertToolingConfig({ + dev: { + ip: "0.0.0.0", + port: 8787, + inspectorPort: 9229, + inspectorIp: "127.0.0.1", + localProtocol: "https", + upstreamProtocol: "http", + host: "example.com", + enableContainers: true, + containerEngine: "unix:///var/run/docker.sock", + }, + }) + ).toEqual({ + dev: { + ip: "0.0.0.0", + port: 8787, + inspector_port: 9229, + inspector_ip: "127.0.0.1", + local_protocol: "https", + upstream_protocol: "http", + host: "example.com", + enable_containers: true, + container_engine: "unix:///var/run/docker.sock", + }, + }); + }); + + it("does NOT map dev.types into the output (consumed separately)", ({ + expect, + }) => { + const result = convertToolingConfig({ + dev: { port: 8787, types: { generate: false } }, + }); + // `dev.types` is intentionally not threaded through `RawConfig` — + // it is consumed via `NormalizedTypes` on `LoadNewConfigResult`. + expect(result.dev).toBeDefined(); + expect((result.dev as Record).types).toBeUndefined(); + expect(JSON.stringify(result.dev)).not.toContain("generate"); + }); + + it("emits dev with undefined sub-fields for absent options", ({ + expect, + }) => { + const result = convertToolingConfig({ dev: { port: 1234 } }); + expect(result.dev).toEqual({ + ip: undefined, + port: 1234, + inspector_port: undefined, + inspector_ip: undefined, + local_protocol: undefined, + upstream_protocol: undefined, + host: undefined, + enable_containers: undefined, + container_engine: undefined, + }); + }); + }); + + describe("assetsDirectory", () => { + it("maps to assets.directory", ({ expect }) => { + expect(convertToolingConfig({ assetsDirectory: "./public" })).toEqual({ + assets: { directory: "./public" }, + }); + }); + + it("does not emit `assets` when assetsDirectory is absent", ({ + expect, + }) => { + expect(convertToolingConfig({ minify: true })).toEqual({ minify: true }); + }); + }); + + describe("composite", () => { + it("maps a fully populated config in a single pass", ({ expect }) => { + const result = convertToolingConfig({ + noBundle: false, + minify: true, + keepNames: true, + alias: { foo: "bar" }, + define: { FOO: "1" }, + findAdditionalModules: true, + preserveFileNames: false, + baseDir: "./src", + rules: [{ type: "Text", globs: ["**/*.txt"] }], + wasmModules: { w: "./w.wasm" }, + textBlobs: { t: "./t.txt" }, + dataBlobs: { d: "./d.bin" }, + tsconfig: "./tsconfig.json", + jsxFactory: "h", + jsxFragment: "Fragment", + pythonModules: { exclude: ["py"] }, + uploadSourceMaps: true, + build: { command: "build", cwd: ".", watchDir: "./src" }, + assetsDirectory: "./public", + dev: { port: 8787 }, + sendMetrics: true, + }); + + expect(new Set(Object.keys(result))).toEqual( + new Set([ + "no_bundle", + "minify", + "keep_names", + "alias", + "define", + "find_additional_modules", + "preserve_file_names", + "base_dir", + "rules", + "wasm_modules", + "text_blobs", + "data_blobs", + "tsconfig", + "jsx_factory", + "jsx_fragment", + "python_modules", + "upload_source_maps", + "build", + "assets", + "dev", + "send_metrics", + ]) + ); + + expect(result.assets).toEqual({ directory: "./public" }); + expect(result.python_modules).toEqual({ exclude: ["py"] }); + expect(result.build).toMatchObject({ watch_dir: "./src" }); + expect(result.dev).toMatchObject({ port: 8787 }); + }); + }); +}); diff --git a/packages/wrangler/src/__tests__/experimental-config/load.test.ts b/packages/wrangler/src/__tests__/experimental-config/load.test.ts new file mode 100644 index 0000000000..64bc677904 --- /dev/null +++ b/packages/wrangler/src/__tests__/experimental-config/load.test.ts @@ -0,0 +1,412 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { runInTempDir, seed } from "@cloudflare/workers-utils/test-helpers"; +import { describe, it, vi } from "vitest"; +import { loadNewConfig } from "../../experimental-config/load"; + +// ───────────────────────────────────────────────────────────────────────────── +// Mock `@cloudflare/config` +// ───────────────────────────────────────────────────────────────────────────── +// +// `loadNewConfig` calls into `@cloudflare/config`'s `loadConfig`, which uses +// `module.registerHooks` to register hooks for `.ts` files and `cf-worker` +// import attributes. That mechanism does not run inside vitest's module +// runner, so we cannot invoke the real loader here. +// +// Mocking `loadConfig` lets us "load" arbitrary seeded files inside the temp +// dir without going through Node's module hooks. We use `import("data:...")` +// to evaluate the file as ESM so the function-form configs work naturally. +// ───────────────────────────────────────────────────────────────────────────── + +vi.mock("@cloudflare/config", async (importOriginal) => { + const actual = (await importOriginal()) as Record; + + async function loadConfig(configPath: string) { + const source = await fs.promises.readFile(configPath, "utf8"); + // Evaluate via a data URL — preserves ESM semantics (top-level await, + // default export, etc.) without triggering Node's `.ts` hooks. + const mod = (await import( + `data:text/javascript;base64,${Buffer.from(source).toString("base64")}` + )) as { default: unknown }; + return { + config: mod.default, + dependencies: new Set([path.resolve(configPath)]), + }; + } + + return { + ...actual, + loadConfig, + }; +}); + +describe("loadNewConfig", () => { + runInTempDir(); + + describe("file presence", () => { + it("throws a UserError when cloudflare.config.ts is missing", async ({ + expect, + }) => { + await expect( + loadNewConfig({ cwd: process.cwd(), args: {} }) + ).rejects.toMatchObject({ + message: expect.stringContaining( + "cloudflare.config.ts is required when --experimental-new-config is enabled." + ), + telemetryMessage: "new-config worker config file missing", + }); + }); + + it("loads cloudflare.config.ts alone (no wrangler.config.ts)", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "my-worker", compatibilityDate: "2026-05-18" };', + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.rawConfig).toMatchObject({ + name: "my-worker", + compatibility_date: "2026-05-18", + }); + expect(result.cloudflareConfigPath).toBe( + path.resolve("cloudflare.config.ts") + ); + expect(result.wranglerConfigPath).toBeUndefined(); + expect(result.types).toEqual({ generate: true }); + expect( + result.dependencies.has(path.resolve("cloudflare.config.ts")) + ).toBe(true); + }); + + it("loads both cloudflare.config.ts and wrangler.config.ts and merges them", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "merged-worker", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": + 'export default { minify: true, assetsDirectory: "./public" };', + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.rawConfig).toMatchObject({ + name: "merged-worker", + compatibility_date: "2026-05-18", + minify: true, + assets: { directory: "./public" }, + }); + expect(result.wranglerConfigPath).toBe( + path.resolve("wrangler.config.ts") + ); + }); + }); + + describe("ctx.mode propagation", () => { + it("passes args.env into the function-form cloudflare.config.ts", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": ` + export default (ctx) => ({ + name: \`worker-\${ctx.mode}\`, + compatibilityDate: "2026-05-18", + }); + `, + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: { env: "staging" }, + }); + + expect(result.rawConfig.name).toBe("worker-staging"); + }); + + it("falls back to CLOUDFLARE_ENV when args.env is not provided", async ({ + expect, + }) => { + vi.stubEnv("CLOUDFLARE_ENV", "preview"); + await seed({ + "cloudflare.config.ts": ` + export default (ctx) => ({ + name: \`worker-\${ctx.mode}\`, + compatibilityDate: "2026-05-18", + }); + `, + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.rawConfig.name).toBe("worker-preview"); + }); + + it("uses undefined when neither args.env nor CLOUDFLARE_ENV is set", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": ` + export default (ctx) => ({ + name: \`worker[\${ctx.mode}]\`, + compatibilityDate: "2026-05-18", + }); + `, + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.rawConfig.name).toBe("worker[undefined]"); + }); + + it("passes ctx.mode into the function-form wrangler.config.ts", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": ` + export default (ctx) => ({ + assetsDirectory: \`./\${ctx.mode}-public\`, + }); + `, + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: { env: "preview" }, + }); + + expect(result.rawConfig.assets).toEqual({ + directory: "./preview-public", + }); + }); + }); + + describe("worker schema validation", () => { + it("throws when cloudflare.config.ts has invalid types", async ({ + expect, + }) => { + // `compatibilityDate` must be a string — number triggers a Zod error. + await seed({ + "cloudflare.config.ts": + 'export default { name: "bad", compatibilityDate: 12345 };', + }); + + await expect( + loadNewConfig({ cwd: process.cwd(), args: {} }) + ).rejects.toMatchObject({ + message: expect.stringContaining("Invalid `cloudflare.config.ts`"), + telemetryMessage: "new-config worker validation failed", + }); + }); + + it("formats Zod errors as a bulleted list with dotted paths", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: 42, compatibilityDate: "2026-05-18" };', + }); + + await expect( + loadNewConfig({ cwd: process.cwd(), args: {} }) + ).rejects.toThrow(/\s*•\s+name:/); + }); + }); + + describe("wrangler.config.ts schema validation", () => { + it("throws when a Worker config field is used at the top level", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + // `name` is a worker-runtime field, not a tooling field; the + // `WORKER_CONFIG_FIELD_HINTS` set turns this into a hint. + "wrangler.config.ts": + 'export default { name: "should-be-in-cloudflare-config" };', + }); + + await expect( + loadNewConfig({ cwd: process.cwd(), args: {} }) + ).rejects.toMatchObject({ + message: expect.stringMatching( + /Invalid `wrangler\.config\.ts`[\s\S]*Move it to cloudflare\.config\.ts/ + ), + telemetryMessage: "new-config tooling validation failed", + }); + }); + + it("throws and lists supported keys for an unknown top-level field", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": "export default { bogusField: true };", + }); + + await expect( + loadNewConfig({ cwd: process.cwd(), args: {} }) + ).rejects.toThrow( + /bogusField is not a supported field[\s\S]*Supported top-level fields are:[\s\S]*minify/ + ); + }); + + it("rejects wrong types for tooling fields", async ({ expect }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": 'export default { minify: "yes-please" };', + }); + + await expect( + loadNewConfig({ cwd: process.cwd(), args: {} }) + ).rejects.toMatchObject({ + message: expect.stringContaining("Invalid `wrangler.config.ts`"), + telemetryMessage: "new-config tooling validation failed", + }); + }); + }); + + describe("assets merging", () => { + it("merges worker-side asset binding/handling with tooling-side directory", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": ` + export default { + name: "w", + compatibilityDate: "2026-05-18", + env: { ASSETS: { type: "assets" } }, + assets: { + htmlHandling: "force-trailing-slash", + notFoundHandling: "404-page", + }, + }; + `, + "wrangler.config.ts": 'export default { assetsDirectory: "./public" };', + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.rawConfig.assets).toEqual({ + binding: "ASSETS", + html_handling: "force-trailing-slash", + not_found_handling: "404-page", + directory: "./public", + }); + }); + }); + + describe("types.generate", () => { + it("defaults to true when wrangler.config.ts is absent", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.types).toEqual({ generate: true }); + }); + + it("defaults to true when wrangler.config.ts is present but does not set it", async ({ + expect, + }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": "export default { minify: true };", + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.types).toEqual({ generate: true }); + }); + + it("honors `dev.types.generate: false`", async ({ expect }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": + "export default { dev: { types: { generate: false } } };", + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect(result.types).toEqual({ generate: false }); + }); + + it("is not threaded into the merged rawConfig.dev", async ({ expect }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": + "export default { dev: { types: { generate: false }, port: 1234 } };", + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + // `dev.port` is mapped through; `dev.types` is intentionally not. + expect(result.rawConfig.dev).toMatchObject({ port: 1234 }); + expect( + (result.rawConfig.dev as Record).types + ).toBeUndefined(); + }); + }); + + describe("dependencies", () => { + it("is the union of dependencies from both files", async ({ expect }) => { + await seed({ + "cloudflare.config.ts": + 'export default { name: "w", compatibilityDate: "2026-05-18" };', + "wrangler.config.ts": "export default { minify: true };", + }); + + const result = await loadNewConfig({ + cwd: process.cwd(), + args: {}, + }); + + expect( + result.dependencies.has(path.resolve("cloudflare.config.ts")) + ).toBe(true); + expect(result.dependencies.has(path.resolve("wrangler.config.ts"))).toBe( + true + ); + }); + }); +}); diff --git a/packages/wrangler/src/api/dev.ts b/packages/wrangler/src/api/dev.ts index 848c79accc..f9842e0ae0 100644 --- a/packages/wrangler/src/api/dev.ts +++ b/packages/wrangler/src/api/dev.ts @@ -221,6 +221,7 @@ export async function unstable_dev( types: false, tunnel: undefined, tunnelName: undefined, + experimentalNewConfig: false, }; //outside of test mode, rebuilds work fine, but only one instance of wrangler will work at a time diff --git a/packages/wrangler/src/api/startDevWorker/ConfigController.ts b/packages/wrangler/src/api/startDevWorker/ConfigController.ts index fbca20ef2b..5f82969da3 100644 --- a/packages/wrangler/src/api/startDevWorker/ConfigController.ts +++ b/packages/wrangler/src/api/startDevWorker/ConfigController.ts @@ -14,7 +14,7 @@ import { watch } from "chokidar"; import { getWorkerRegistry } from "miniflare"; import { getAssetsOptions, validateAssetsArgsAndConfig } from "../../assets"; import { fillOpenAPIConfiguration } from "../../cloudchamber/common"; -import { readConfig } from "../../config"; +import { readConfig, readNewConfig } from "../../config"; import { containersScope } from "../../containers"; import { getNormalizedContainerOptions } from "../../containers/config"; import { getEntry } from "../../deployment-bundle/entry"; @@ -24,6 +24,7 @@ import { getLocalPersistencePath } from "../../dev/get-local-persistence-path"; import { getFlag } from "../../experimental-flags"; import { logger, runWithLogLevel } from "../../logger"; import { checkTypesDiff } from "../../type-generation/helpers"; +import { regenerateNewConfigTypes } from "../../type-generation/new-config"; import { loginOrRefreshIfRequired, requireApiToken, @@ -42,6 +43,7 @@ import { getZoneIdForPreview } from "../../zones"; import { Controller } from "./BaseController"; import { castErrorCause } from "./events"; import { unwrapHook } from "./utils"; +import type { NewConfig, ReadConfigCommandArgs } from "../../config"; import type { DevRegistryUpdateEvent } from "./events"; import type { StartDevWorkerInput, @@ -333,7 +335,8 @@ async function resolveConfig( config: Config, input: StartDevWorkerInput, // If the worker name was previously autogenerated, keep the same one - previousName: string | undefined + previousName: string | undefined, + newConfigEnabled: boolean ): Promise<{ config: StartDevWorkerOptions; printCurrentBindings: (registry: WorkerRegistry | null) => void; @@ -527,7 +530,13 @@ async function resolveConfig( } } - await checkTypesDiff(config, entry); + // Skip the legacy `checkTypesDiff` call when `--experimental-new-config` is on. + // The new-config equivalent (`regenerateNewConfigTypes`) is invoked from + // `#updateConfig` directly using the structured `types` object returned + // by `loadNewConfig`. + if (!newConfigEnabled) { + await checkTypesDiff(config, entry); + } return { config: resolved, printCurrentBindings }; } @@ -567,33 +576,38 @@ export class ConfigController extends Controller { #configWatcher?: ReturnType; #abortController?: AbortController; - async #ensureWatchingConfig(configPath: string | undefined) { + async #ensureWatchingConfig(configPaths: string | string[] | undefined) { await this.#configWatcher?.close(); - if (configPath) { - this.#configWatcher = watch(configPath, { - persistent: true, - ignoreInitial: true, - }).on("change", async (_event) => { - if (this.#configWatcher?.closed) { - return; - } - logger.debug(`${path.basename(configPath)} changed...`); - assert( - this.latestInput, - "Cannot be watching config without having first set an input" - ); - logger.debug("config file changed", configPath); - this.#updateConfig(this.latestInput).catch((err) => { - this.emitErrorEvent({ - type: "error", - reason: "Error resolving config after change", - cause: castErrorCause(err), - source: "ConfigController", - data: undefined, - }); + if (configPaths === undefined) { + return; + } + const paths = typeof configPaths === "string" ? [configPaths] : configPaths; + if (paths.length === 0) { + return; + } + this.#configWatcher = watch(paths, { + persistent: true, + ignoreInitial: true, + }).on("change", async (changedPath) => { + if (this.#configWatcher?.closed) { + return; + } + logger.debug(`${path.basename(changedPath)} changed...`); + assert( + this.latestInput, + "Cannot be watching config without having first set an input" + ); + logger.debug("config file changed", changedPath); + this.#updateConfig(this.latestInput).catch((err) => { + this.emitErrorEvent({ + type: "error", + reason: "Error resolving config after change", + cause: castErrorCause(err), + source: "ConfigController", + data: undefined, }); }); - } + }); } public set(input: StartDevWorkerInput, throwErrors = false) { @@ -630,43 +644,75 @@ export class ConfigController extends Controller { const signal = this.#abortController.signal; this.latestInput = input; try { - const fileConfig = - typeof input.config === "object" - ? input.config - : readConfig( - { - script: input.entrypoint, - config: input.config, - env: input.env, - "dispatch-namespace": undefined, - "legacy-env": !input.legacy?.useServiceEnvironments, - remote: !!input.dev?.remote, - upstreamProtocol: - input.dev?.origin?.secure === undefined - ? undefined - : input.dev?.origin?.secure - ? "https" - : "http", - localProtocol: - input.dev?.server?.secure === undefined - ? undefined - : input.dev?.server?.secure - ? "https" - : "http", - generateTypes: input.dev?.generateTypes, - }, - { useRedirectIfAvailable: true } - ); + const newConfigEnabled = input.dev?.experimentalNewConfig === true; + + let newConfig: NewConfig | undefined; + let fileConfig: Config; + if (typeof input.config === "object") { + fileConfig = input.config; + } else { + const readConfigArgs: ReadConfigCommandArgs = { + script: input.entrypoint, + config: input.config, + env: input.env, + "dispatch-namespace": undefined, + "legacy-env": !input.legacy?.useServiceEnvironments, + remote: !!input.dev?.remote, + upstreamProtocol: + input.dev?.origin?.secure === undefined + ? undefined + : input.dev?.origin?.secure + ? "https" + : "http", + localProtocol: + input.dev?.server?.secure === undefined + ? undefined + : input.dev?.server?.secure + ? "https" + : "http", + generateTypes: input.dev?.generateTypes, + }; + + if (newConfigEnabled) { + newConfig = await readNewConfig(readConfigArgs); + fileConfig = newConfig.config; + } else { + fileConfig = readConfig(readConfigArgs, { + useRedirectIfAvailable: true, + }); + } + } if (!getDisableConfigWatching() && input.dev?.watch !== false) { - await this.#ensureWatchingConfig(fileConfig.configPath); + // Under `--experimental-new-config`, watch the transitive deps of both + // `cloudflare.config.ts` and `wrangler.config.ts` (deduped, with + // `node_modules` excluded by `@cloudflare/config`'s loader). + // Otherwise fall back to the legacy single-file watch. + const watchPaths = newConfig + ? Array.from(newConfig.dependencies) + : fileConfig.configPath; + await this.#ensureWatchingConfig(watchPaths); } else { await this.#configWatcher?.close(); this.#configWatcher = undefined; } + // Under `--experimental-new-config`, run the new-config type-gen path + // instead of the legacy `checkTypesDiff`. + if (newConfig && fileConfig.configPath) { + await regenerateNewConfigTypes({ + cloudflareConfigPath: fileConfig.configPath, + types: newConfig.types, + }); + } + const { config: resolvedConfig, printCurrentBindings } = - await resolveConfig(fileConfig, input, this.latestConfig?.name); + await resolveConfig( + fileConfig, + input, + this.latestConfig?.name, + newConfigEnabled + ); if (signal.aborted) { return; diff --git a/packages/wrangler/src/build/index.ts b/packages/wrangler/src/build/index.ts index c94c2aeb9a..0409aa4017 100644 --- a/packages/wrangler/src/build/index.ts +++ b/packages/wrangler/src/build/index.ts @@ -1,4 +1,5 @@ import { createCommand } from "../core/create-command"; +import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import { createCLIParser } from "../index"; export const buildCommand = createCommand({ @@ -12,6 +13,9 @@ export const buildCommand = createCommand({ printBanner: false, provideConfig: false, }, + args: { + ...experimentalNewConfigArg, + }, async handler(buildArgs) { const { wrangler } = createCLIParser([ "deploy", @@ -19,6 +23,7 @@ export const buildCommand = createCommand({ "--outdir=dist", ...(buildArgs.env ? ["--env", buildArgs.env] : []), ...(buildArgs.config ? ["--config", buildArgs.config] : []), + ...(buildArgs.experimentalNewConfig ? ["--experimental-new-config"] : []), ]); await wrangler.parse(); }, diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index e62e442a38..0ce740724d 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -11,9 +11,11 @@ import { } from "@cloudflare/workers-utils"; import dedent from "ts-dedent"; import { version as wranglerVersion } from "../../package.json"; +import { loadNewConfig } from "../experimental-config/load"; import { logger } from "../logger"; import { EXIT_CODE_INVALID_PAGES_CONFIG } from "../pages/errors"; import { updateCheck } from "../update-check"; +import type { NormalizedTypes } from "../experimental-config/load"; import type { Config, ConfigBindingOptions, @@ -62,6 +64,72 @@ async function logWarningsWithUpgradeHint( } } +/** + * Carries the validated `Config` alongside the + * watcher dependency set and the normalised type-generation settings. + */ +export interface NewConfig { + config: Config; + dependencies: Set; + types: NormalizedTypes; +} + +/** + * Load the experimental TypeScript-based configuration (`cloudflare.config.ts` + * + optional `wrangler.config.ts`) used by `--experimental-new-config`. + * + * Steps: + * 1. Hard error if `args.config` is set (no `--config` override under + * `--experimental-new-config`). + * 2. Call `loadNewConfig(...)` to load + merge both files. + * 3. Pass the merged `RawConfig` through the existing + * `normalizeAndValidateConfig` pipeline with `env` explicitly cleared. + * 4. Return the validated `Config` plus `dependencies` and `types`. + */ +export async function readNewConfig( + args: ReadConfigCommandArgs, + options: ReadConfigOptions = {} +): Promise { + if (args.config !== undefined) { + throw new UserError( + `--config is not supported with --experimental-new-config. cloudflare.config.ts and wrangler.config.ts are loaded from the project root.`, + { telemetryMessage: "new-config config flag not supported" } + ); + } + + const cwd = process.cwd(); + const loaded = await loadNewConfig({ cwd, args }); + + // Construct a fresh `NormalizeAndValidateConfigArgs` with `env: undefined`. + // `args.env` is consumed only by `loadNewConfig` (to compute `ctx.mode`); + // it is not forwarded to the validator. + const validationArgs: NormalizeAndValidateConfigArgs = { + ...args, + env: undefined, + }; + + const { config, diagnostics } = normalizeAndValidateConfig( + loaded.rawConfig, + loaded.cloudflareConfigPath, + loaded.cloudflareConfigPath, + validationArgs, + options.preserveOriginalMain + ); + + void logWarningsWithUpgradeHint(diagnostics, options.hideWarnings); + if (diagnostics.hasErrors()) { + throw new UserError(diagnostics.renderErrors(), { + telemetryMessage: "new-config worker validation failed", + }); + } + + return { + config, + dependencies: loaded.dependencies, + types: loaded.types, + }; +} + /** * Get the Wrangler configuration; read it from the give `configPath` if available. */ diff --git a/packages/wrangler/src/core/register-yargs-command.ts b/packages/wrangler/src/core/register-yargs-command.ts index 690b762dd4..1580d3c44d 100644 --- a/packages/wrangler/src/core/register-yargs-command.ts +++ b/packages/wrangler/src/core/register-yargs-command.ts @@ -16,7 +16,7 @@ import { fetchPagedListResult, } from "../cfetch"; import { createCloudflareClient } from "../cfetch/internal"; -import { readConfig } from "../config"; +import { readConfig, readNewConfig } from "../config"; import { confirm, prompt } from "../dialogs"; import { run } from "../experimental-flags"; import { isNonInteractiveOrCI } from "../is-interactive"; @@ -135,6 +135,9 @@ function createHandler(def: InternalCommandDefinition, argv: string[]) { // Sentry breadcrumbs expect the `wrangler` prefix. addBreadcrumb(def.command); + const newConfigEnabled = + "experimentalNewConfig" in args && args.experimentalNewConfig === true; + try { const shouldPrintBanner = def.behaviour?.printBanner ?? true; const bannerEnabled = @@ -206,11 +209,17 @@ function createHandler(def: InternalCommandDefinition, argv: string[]) { await run(experimentalFlags, async () => { const config = (def.behaviour?.provideConfig ?? true) - ? readConfig(args, { - hideWarnings: !(def.behaviour?.printConfigWarnings ?? true), - useRedirectIfAvailable: - def.behaviour?.useConfigRedirectIfAvailable, - }) + ? newConfigEnabled + ? ( + await readNewConfig(args, { + hideWarnings: !(def.behaviour?.printConfigWarnings ?? true), + }) + ).config + : readConfig(args, { + hideWarnings: !(def.behaviour?.printConfigWarnings ?? true), + useRedirectIfAvailable: + def.behaviour?.useConfigRedirectIfAvailable, + }) : defaultWranglerConfig; const dispatcher = getMetricsDispatcher({ @@ -220,7 +229,13 @@ function createHandler(def: InternalCommandDefinition, argv: string[]) { argv, }); - if (def.behaviour?.warnIfMultipleEnvsConfiguredButNoneSpecified) { + // Skip the multi-envs warning under `--experimental-new-config`: it re-reads + // `wrangler.json[c]` to enumerate envs, which is not applicable + // when the flag is on + if ( + def.behaviour?.warnIfMultipleEnvsConfiguredButNoneSpecified && + !newConfigEnabled + ) { if ( !("env" in args) && getCloudflareEnv() === undefined && diff --git a/packages/wrangler/src/deploy/index.ts b/packages/wrangler/src/deploy/index.ts index 93b21f183f..d9dbb530e4 100644 --- a/packages/wrangler/src/deploy/index.ts +++ b/packages/wrangler/src/deploy/index.ts @@ -14,6 +14,7 @@ import { cleanupDestination, mergeDeployConfigArgs, } from "../deployment-bundle/merge-config-args"; +import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import * as metrics from "../metrics"; import { writeOutput } from "../output"; import { syncWorkersSite } from "../sites"; @@ -30,6 +31,7 @@ export const deployCommand = createCommand({ }, positionalArgs: ["path"], args: { + ...experimentalNewConfigArg, ...sharedDeployVersionsArgs, triggers: { describe: "cron schedules to attach", diff --git a/packages/wrangler/src/dev.ts b/packages/wrangler/src/dev.ts index 0c39d33fc5..47c22075d6 100644 --- a/packages/wrangler/src/dev.ts +++ b/packages/wrangler/src/dev.ts @@ -13,6 +13,7 @@ import { createCommand } from "./core/create-command"; import { validateRoutes } from "./deployment-bundle/resolve-config-args"; import { getVarsForDev } from "./dev/dev-vars"; import { startDev } from "./dev/start-dev"; +import { experimentalNewConfigArg } from "./experimental-config/cli-flag"; import { logger } from "./logger"; import type { StartDevWorkerInput, Trigger } from "./api"; import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli/types"; @@ -45,6 +46,7 @@ export const dev = createCommand({ }, positionalArgs: ["script"], args: { + ...experimentalNewConfigArg, script: { describe: "The path to an entry point for your Worker", type: "string", diff --git a/packages/wrangler/src/dev/start-dev.ts b/packages/wrangler/src/dev/start-dev.ts index 31bfcea842..9946b86042 100644 --- a/packages/wrangler/src/dev/start-dev.ts +++ b/packages/wrangler/src/dev/start-dev.ts @@ -270,6 +270,7 @@ async function setupDevEnv( // initialise with a random id containerBuildId: generateContainerBuildId(), generateTypes: args.types, + experimentalNewConfig: args.experimentalNewConfig, tunnel: { enabled: args.tunnel ?? false, name: args.tunnelName, diff --git a/packages/wrangler/src/experimental-config.ts b/packages/wrangler/src/experimental-config.ts new file mode 100644 index 0000000000..bcd0928a4f --- /dev/null +++ b/packages/wrangler/src/experimental-config.ts @@ -0,0 +1,21 @@ +/** + * Experimental subpath entry — `wrangler/experimental-config`. + * + * Re-exports the Worker config API from `@cloudflare/config/public` (used by + * `cloudflare.config.ts`), and the new tooling config API + * (`defineWranglerConfig`) used by `wrangler.config.ts`. + * + * Importing example: + * + * ```ts + * import { + * defineWorker, + * defineWranglerConfig, + * bindings, + * triggers, + * } from "wrangler/experimental-config"; + * ``` + */ + +export * from "@cloudflare/config/public"; +export * from "./experimental-config/public"; diff --git a/packages/wrangler/src/experimental-config/cli-flag.ts b/packages/wrangler/src/experimental-config/cli-flag.ts new file mode 100644 index 0000000000..00f84a8abe --- /dev/null +++ b/packages/wrangler/src/experimental-config/cli-flag.ts @@ -0,0 +1,12 @@ +/** + * Shared yargs definition for the `--experimental-new-config` flag. + */ +export const experimentalNewConfigArg = { + "experimental-new-config": { + describe: "[experimental] Use cloudflare.config.ts and wrangler.config.ts", + type: "boolean", + default: false, + hidden: true, + alias: "x-new-config", + }, +} as const; diff --git a/packages/wrangler/src/experimental-config/convert.ts b/packages/wrangler/src/experimental-config/convert.ts new file mode 100644 index 0000000000..c28045fa7b --- /dev/null +++ b/packages/wrangler/src/experimental-config/convert.ts @@ -0,0 +1,106 @@ +import type { ParsedWranglerConfig } from "./schema"; +import type { RawConfig } from "@cloudflare/workers-utils"; + +/** + * Convert the validated camelCase `wrangler.config.ts` shape into the + * snake_case `Partial` shape consumed by Wrangler's existing + * `normalizeAndValidateConfig`. + */ +export function convertToolingConfig( + parsed: ParsedWranglerConfig +): Partial { + const result: Partial = {}; + + if (parsed.noBundle !== undefined) { + result.no_bundle = parsed.noBundle; + } + if (parsed.minify !== undefined) { + result.minify = parsed.minify; + } + if (parsed.keepNames !== undefined) { + result.keep_names = parsed.keepNames; + } + if (parsed.alias !== undefined) { + result.alias = parsed.alias; + } + if (parsed.define !== undefined) { + result.define = parsed.define; + } + if (parsed.findAdditionalModules !== undefined) { + result.find_additional_modules = parsed.findAdditionalModules; + } + if (parsed.preserveFileNames !== undefined) { + result.preserve_file_names = parsed.preserveFileNames; + } + if (parsed.baseDir !== undefined) { + result.base_dir = parsed.baseDir; + } + if (parsed.rules !== undefined) { + result.rules = parsed.rules; + } + if (parsed.wasmModules !== undefined) { + result.wasm_modules = parsed.wasmModules; + } + if (parsed.textBlobs !== undefined) { + result.text_blobs = parsed.textBlobs; + } + if (parsed.dataBlobs !== undefined) { + result.data_blobs = parsed.dataBlobs; + } + if (parsed.tsconfig !== undefined) { + result.tsconfig = parsed.tsconfig; + } + if (parsed.jsxFactory !== undefined) { + result.jsx_factory = parsed.jsxFactory; + } + if (parsed.jsxFragment !== undefined) { + result.jsx_fragment = parsed.jsxFragment; + } + if (parsed.pythonModules !== undefined) { + // `convertToolingConfig` is a thin name/shape translator — it does not + // apply defaults. `pythonModules: {}` (no `exclude`) is passed through + // as `python_modules: {}`, even though `RawConfig.python_modules.exclude` + // is typed as required; downstream `normalizeAndValidateConfig` is + // responsible for resolving / defaulting / validating the shape. + result.python_modules = ( + parsed.pythonModules.exclude !== undefined + ? { exclude: parsed.pythonModules.exclude } + : {} + ) as RawConfig["python_modules"]; + } + if (parsed.uploadSourceMaps !== undefined) { + result.upload_source_maps = parsed.uploadSourceMaps; + } + if (parsed.build !== undefined) { + result.build = { + command: parsed.build.command, + cwd: parsed.build.cwd, + watch_dir: parsed.build.watchDir, + }; + } + if (parsed.dev !== undefined) { + result.dev = { + ip: parsed.dev.ip, + port: parsed.dev.port, + inspector_port: parsed.dev.inspectorPort, + inspector_ip: parsed.dev.inspectorIp, + local_protocol: parsed.dev.localProtocol, + upstream_protocol: parsed.dev.upstreamProtocol, + host: parsed.dev.host, + // `dev.types` is intentionally NOT mapped — it is consumed + // separately via the `types` field on `LoadNewConfigResult`, not + // threaded through `RawConfig`. The legacy `config.dev.generate_types` + // gate is not used when `--experimental-new-config` is on. + enable_containers: parsed.dev.enableContainers, + container_engine: parsed.dev.containerEngine, + }; + } + if (parsed.sendMetrics !== undefined) { + result.send_metrics = parsed.sendMetrics; + } + if (parsed.assetsDirectory !== undefined) { + result.assets = { directory: parsed.assetsDirectory }; + } + + return result; +} diff --git a/packages/wrangler/src/experimental-config/load.ts b/packages/wrangler/src/experimental-config/load.ts new file mode 100644 index 0000000000..5ed655b6d2 --- /dev/null +++ b/packages/wrangler/src/experimental-config/load.ts @@ -0,0 +1,225 @@ +import { existsSync } from "node:fs"; +import path from "node:path"; +import { + ConfigSchema, + convertToWranglerConfig, + loadConfig, + resolveWorkerDefinition, +} from "@cloudflare/config"; +import { getCloudflareEnv, UserError } from "@cloudflare/workers-utils"; +import { convertToolingConfig } from "./convert"; +import { + WORKER_CONFIG_FIELD_HINTS, + WRANGLER_CONFIG_SUPPORTED_KEYS, + WranglerConfigSchema, +} from "./schema"; +import { resolveWranglerConfig } from "./wrangler-definition"; +import type { ParsedWranglerConfig } from "./schema"; +import type { RawConfig } from "@cloudflare/workers-utils"; + +export const CLOUDFLARE_CONFIG_FILENAME = "cloudflare.config.ts"; +export const WRANGLER_CONFIG_FILENAME = "wrangler.config.ts"; + +export interface NormalizedTypes { + generate: boolean; +} + +export interface LoadNewConfigResult { + /** Merged result: `cloudflare.config.ts` runtime + `wrangler.config.ts` tooling. */ + rawConfig: Omit; + /** Resolved absolute path to `cloudflare.config.ts`. */ + cloudflareConfigPath: string; + /** Resolved absolute path to `wrangler.config.ts`, if present. */ + wranglerConfigPath: string | undefined; + /** Transitive deps from BOTH files (node_modules excluded). */ + dependencies: Set; + /** Normalized type-generation settings. */ + types: NormalizedTypes; +} + +/** + * Load and validate the new TypeScript-based configuration files. + * + * - `cloudflare.config.ts` is required. + * - `wrangler.config.ts` is optional (defaults apply when missing). + */ +export async function loadNewConfig(options: { + cwd: string; + args: { env?: string }; +}): Promise { + const cwd = options.cwd; + const cloudflareConfigPath = path.resolve(cwd, CLOUDFLARE_CONFIG_FILENAME); + if (!existsSync(cloudflareConfigPath)) { + throw new UserError( + `${CLOUDFLARE_CONFIG_FILENAME} is required when --experimental-new-config is enabled.`, + { telemetryMessage: "new-config worker config file missing" } + ); + } + + const candidateWranglerConfigPath = path.resolve( + cwd, + WRANGLER_CONFIG_FILENAME + ); + const wranglerConfigPath = existsSync(candidateWranglerConfigPath) + ? candidateWranglerConfigPath + : undefined; + + const mode = options.args.env ?? getCloudflareEnv(); + + // ── Worker config ─────────────────────────────────────────────────── + const workerConfigResult = await loadConfig(cloudflareConfigPath); + + const resolvedWorkerConfig = await resolveWorkerDefinition( + workerConfigResult.config, + { mode } + ); + + const parsedWorkerConfig = ConfigSchema.safeParse(resolvedWorkerConfig); + if (!parsedWorkerConfig.success) { + throw new UserError( + `Invalid \`${CLOUDFLARE_CONFIG_FILENAME}\`:\n${formatZodError(parsedWorkerConfig.error)}`, + { telemetryMessage: "new-config worker validation failed" } + ); + } + + // ── Wrangler (tooling) config ─────────────────────────────────────── + let wranglerConfigResult: + | { config: unknown; dependencies: Set } + | undefined; + let parsedWranglerConfig: { data: ParsedWranglerConfig } | undefined; + + if (wranglerConfigPath !== undefined) { + wranglerConfigResult = await loadConfig(wranglerConfigPath); + + const resolvedWranglerConfig = await resolveWranglerConfig( + wranglerConfigResult.config, + { mode } + ); + + const parsed = WranglerConfigSchema.safeParse(resolvedWranglerConfig); + if (!parsed.success) { + throw new UserError( + `Invalid \`${WRANGLER_CONFIG_FILENAME}\`:\n${formatWranglerConfigZodError(parsed.error)}`, + { telemetryMessage: "new-config tooling validation failed" } + ); + } + parsedWranglerConfig = { data: parsed.data }; + } + + // ── Conversion + merge ────────────────────────────────────────────── + const rawWorkerConfig = convertToWranglerConfig(parsedWorkerConfig.data); + + const rawWranglerConfig = convertToolingConfig( + parsedWranglerConfig?.data ?? {} + ); + + const rawConfig = mergeRawConfigs(rawWorkerConfig, rawWranglerConfig); + + // ── Normalised types ──────────────────────────────────────────────── + const types: NormalizedTypes = { + generate: parsedWranglerConfig?.data.dev?.types?.generate ?? true, + }; + + // ── Dependencies (union of both files) ────────────────────────────── + const dependencies = new Set(workerConfigResult.dependencies); + if (wranglerConfigResult) { + for (const dep of wranglerConfigResult.dependencies) { + dependencies.add(dep); + } + } + + return { + rawConfig, + cloudflareConfigPath, + wranglerConfigPath, + dependencies, + types, + }; +} + +/** + * Merge the converted Worker `RawConfig` (from `convertToWranglerConfig`) + * with the converted tooling `Partial` (from + * `convertToolingConfig`). + * + * Worker fields cannot appear in tooling (rejected by `WranglerConfigSchema`). + * Tooling fields cannot appear in worker (rejected by `@cloudflare/config`'s + * `ConfigSchema.strictObject`). The only overlap is `assets`, where worker + * carries `binding`/`html_handling`/`not_found_handling`/`run_worker_first` + * and tooling carries `directory` (sourced from the flat top-level + * `assetsDirectory` field on `wrangler.config.ts`). + */ +export function mergeRawConfigs( + worker: RawConfig, + tooling: Partial +): RawConfig { + const { assets: workerAssets, ...workerRest } = worker; + const { assets: toolingAssets, ...toolingRest } = tooling; + + const assets = + workerAssets || toolingAssets + ? { ...workerAssets, ...toolingAssets } + : undefined; + + return { + ...workerRest, + ...toolingRest, + ...(assets !== undefined ? { assets } : {}), + }; +} + +interface ZodLikeIssue { + path: PropertyKey[]; + message: string; + code?: string; + keys?: string[]; +} +interface ZodLikeError { + issues: ZodLikeIssue[]; + message?: string; +} + +function dottedPath(issuePath: PropertyKey[]): string { + return issuePath.filter((p) => typeof p !== "symbol").join("."); +} + +function formatZodError(err: ZodLikeError): string { + if (!err.issues || err.issues.length === 0) { + return err.message ?? "Unknown validation error"; + } + return err.issues + .map((issue) => { + const dotted = dottedPath(issue.path); + return dotted + ? ` • ${dotted}: ${issue.message}` + : ` • ${issue.message}`; + }) + .join("\n"); +} + +function formatWranglerConfigZodError(err: ZodLikeError): string { + if (!err.issues || err.issues.length === 0) { + return err.message ?? "Unknown validation error"; + } + return err.issues + .map((issue) => { + const dotted = dottedPath(issue.path); + // Augment "unrecognized key" issues with a hint pointing at + // cloudflare.config.ts when the offending key is a Worker-runtime field. + if (issue.code === "unrecognized_keys" && Array.isArray(issue.keys)) { + return issue.keys + .map((key) => { + const fullPath = dotted ? `${dotted}.${key}` : key; + if (WORKER_CONFIG_FIELD_HINTS.has(key)) { + return ` • ${fullPath} is not a supported field in ${WRANGLER_CONFIG_FILENAME}. Move it to ${CLOUDFLARE_CONFIG_FILENAME}.`; + } + return ` • ${fullPath} is not a supported field. Supported top-level fields are: ${WRANGLER_CONFIG_SUPPORTED_KEYS.join(", ")}.`; + }) + .join("\n"); + } + return dotted + ? ` • ${dotted}: ${issue.message}` + : ` • ${issue.message}`; + }) + .join("\n"); +} diff --git a/packages/wrangler/src/experimental-config/public.ts b/packages/wrangler/src/experimental-config/public.ts new file mode 100644 index 0000000000..770fcda9a6 --- /dev/null +++ b/packages/wrangler/src/experimental-config/public.ts @@ -0,0 +1,3 @@ +export { defineWranglerConfig } from "./wrangler-definition"; +export type { WranglerConfig } from "./types"; +export type { WranglerConfigExport } from "./wrangler-definition"; diff --git a/packages/wrangler/src/experimental-config/schema.ts b/packages/wrangler/src/experimental-config/schema.ts new file mode 100644 index 0000000000..ab9c2c8dc9 --- /dev/null +++ b/packages/wrangler/src/experimental-config/schema.ts @@ -0,0 +1,125 @@ +import * as z from "zod"; +import type { WranglerConfig } from "./types"; + +const RuleSchema = z.strictObject({ + type: z.enum([ + "ESModule", + "CommonJS", + "CompiledWasm", + "Text", + "Data", + "PythonModule", + "PythonRequirement", + ]), + globs: z.array(z.string()), + fallthrough: z.boolean().optional(), +}); + +const BuildSchema = z.strictObject({ + command: z.string().optional(), + cwd: z.string().optional(), + watchDir: z.union([z.string(), z.array(z.string())]).optional(), +}); + +const PythonModulesSchema = z.strictObject({ + exclude: z.array(z.string()).optional(), +}); + +const DevSchema = z.strictObject({ + ip: z.string().optional(), + port: z.number().optional(), + inspectorPort: z.number().optional(), + inspectorIp: z.string().optional(), + localProtocol: z.enum(["http", "https"]).optional(), + upstreamProtocol: z.enum(["http", "https"]).optional(), + host: z.string().optional(), + types: z + .strictObject({ + generate: z.boolean().optional(), + }) + .optional(), + enableContainers: z.boolean().optional(), + containerEngine: z.string().optional(), +}); + +/** + * Strict schema for `wrangler.config.ts`. Unknown keys produce a Zod + * "unrecognized key" error — `loadNewConfig` augments these with a hint when + * the key looks like a Worker-runtime field that should be in `cloudflare.config.ts`. + */ +export const WranglerConfigSchema = z.strictObject({ + noBundle: z.boolean().optional(), + minify: z.boolean().optional(), + keepNames: z.boolean().optional(), + alias: z.record(z.string(), z.string()).optional(), + define: z.record(z.string(), z.string()).optional(), + findAdditionalModules: z.boolean().optional(), + preserveFileNames: z.boolean().optional(), + baseDir: z.string().optional(), + rules: z.array(RuleSchema).optional(), + wasmModules: z.record(z.string(), z.string()).optional(), + textBlobs: z.record(z.string(), z.string()).optional(), + dataBlobs: z.record(z.string(), z.string()).optional(), + tsconfig: z.string().optional(), + jsxFactory: z.string().optional(), + jsxFragment: z.string().optional(), + pythonModules: PythonModulesSchema.optional(), + uploadSourceMaps: z.boolean().optional(), + build: BuildSchema.optional(), + assetsDirectory: z.string().optional(), + dev: DevSchema.optional(), + sendMetrics: z.boolean().optional(), +}); + +export type ParsedWranglerConfig = z.output; + +/** + * The list of supported top-level keys in `wrangler.config.ts`. Used to + * generate helpful error messages. + */ +export const WRANGLER_CONFIG_SUPPORTED_KEYS = Object.keys( + WranglerConfigSchema.shape +); + +/** + * Worker-runtime field names that, if found at the top level of + * `wrangler.config.ts`, should produce a hint to move them to + * `cloudflare.config.ts`. Used by the error wrapper in `loadNewConfig`. + */ +export const WORKER_CONFIG_FIELD_HINTS = new Set([ + "name", + "accountId", + "compatibilityDate", + "compatibilityFlags", + "entrypoint", + "assets", + "domains", + "triggers", + "tailConsumers", + "cache", + "placement", + "limits", + "logpush", + "observability", + "workersDev", + "previewUrls", + "complianceRegion", + "firstPartyWorker", + "unsafe", + "env", + "exports", +]); + +/** + * Bidirectional drift check between {@link WranglerConfigSchema} and the + * public {@link WranglerConfig} interface. + */ +type _AssertWranglerSchemaMatchesType = [ + z.input extends WranglerConfig ? true : false, + WranglerConfig extends z.input ? true : false, +]; +const _assertWranglerSchemaMatchesType: _AssertWranglerSchemaMatchesType = [ + true, + true, +]; +void _assertWranglerSchemaMatchesType; diff --git a/packages/wrangler/src/experimental-config/types.ts b/packages/wrangler/src/experimental-config/types.ts new file mode 100644 index 0000000000..3b894b5f22 --- /dev/null +++ b/packages/wrangler/src/experimental-config/types.ts @@ -0,0 +1,75 @@ +/** + * The shape of `wrangler.config.ts` — tooling / bundling / dev-server + * configuration that complements the Worker configuration authored in + * `cloudflare.config.ts` via `defineWorker`. + */ +export interface WranglerConfig { + // Bundling + noBundle?: boolean; + minify?: boolean; + keepNames?: boolean; + alias?: Record; + define?: Record; + findAdditionalModules?: boolean; + preserveFileNames?: boolean; + baseDir?: string; + rules?: Array<{ + type: + | "ESModule" + | "CommonJS" + | "CompiledWasm" + | "Text" + | "Data" + | "PythonModule" + | "PythonRequirement"; + globs: string[]; + fallthrough?: boolean; + }>; + wasmModules?: Record; + textBlobs?: Record; + dataBlobs?: Record; + tsconfig?: string; + jsxFactory?: string; + jsxFragment?: string; + pythonModules?: { exclude?: string[] }; + uploadSourceMaps?: boolean; + build?: { command?: string; cwd?: string; watchDir?: string | string[] }; + /** + * Assets directory — the only tooling-side asset setting. The runtime + * asset fields (`binding`, `htmlHandling`, `notFoundHandling`, + * `runWorkerFirst`) live in `cloudflare.config.ts` under `assets`. + */ + assetsDirectory?: string; + // Dev/local + dev?: { + ip?: string; + port?: number; + inspectorPort?: number; + inspectorIp?: string; + localProtocol?: "http" | "https"; + upstreamProtocol?: "http" | "https"; + host?: string; + /** + * Type-generation settings. Consumed directly by the new-config + * type-generation path (`regenerateNewConfigTypes`) — NOT threaded + * through the merged `RawConfig`. Default: `{ generate: true }`. + * Structured as an object to allow additional properties in future. + */ + types?: { generate?: boolean }; + /** + * Container-related dev settings. `containers` itself is currently not + * supported under `--experimental-new-config`, but these dev-time settings are + * accepted so users can enable them ahead of `containers` opening up. + * They're no-ops when `containers` is absent. + */ + enableContainers?: boolean; + /** + * Either the Docker unix socket (e.g. `unix:///var/run/docker.sock`) or + * a full configuration. The string form is the common case; the full + * object form (with `localDocker.socketPath` and TLS settings) can be + * added later if needed. + */ + containerEngine?: string; + }; + sendMetrics?: boolean; +} diff --git a/packages/wrangler/src/experimental-config/wrangler-definition.ts b/packages/wrangler/src/experimental-config/wrangler-definition.ts new file mode 100644 index 0000000000..716e45942e --- /dev/null +++ b/packages/wrangler/src/experimental-config/wrangler-definition.ts @@ -0,0 +1,24 @@ +import type { WranglerConfig } from "./types"; +import type { ConfigContext } from "@cloudflare/config/public"; + +export type WranglerConfigExport = + | WranglerConfig + | Promise + | ((ctx: ConfigContext) => WranglerConfig | Promise); + +export function defineWranglerConfig( + config: WranglerConfigExport +): WranglerConfigExport { + return config; +} + +export async function resolveWranglerConfig( + def: unknown, + ctx: ConfigContext +): Promise { + let raw = def; + if (typeof raw === "function") { + raw = (raw as (ctx: ConfigContext) => unknown)(ctx); + } + return await raw; +} diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index c6b56ad373..19de0abe31 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -1036,6 +1036,7 @@ export const pagesDevCommand = createCommand({ enableContainers: false, types: false, tunnel: undefined, + experimentalNewConfig: false, }) ); diff --git a/packages/wrangler/src/type-generation/new-config.ts b/packages/wrangler/src/type-generation/new-config.ts new file mode 100644 index 0000000000..c5e279c257 --- /dev/null +++ b/packages/wrangler/src/type-generation/new-config.ts @@ -0,0 +1,64 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +// `@cloudflare/config` is statically imported here. See new-config.ts for +// documentation of the upstream build warnings this triggers. +import { generateTypes } from "@cloudflare/config"; +import { logger } from "../logger"; +import { + DEFAULT_WORKERS_TYPES_FILE_NAME, + DEFAULT_WORKERS_TYPES_FILE_PATH, +} from "./helpers"; +import type { NormalizedTypes } from "../experimental-config/load"; + +/** + * Re-generate `worker-configuration.d.ts` from `cloudflare.config.ts` under + * `--experimental-new-config`. This is the new-config equivalent of the legacy + * `checkTypesDiff` path — `checkTypesDiff` is NOT invoked when + * `--experimental-new-config` is on. + */ +export async function regenerateNewConfigTypes(options: { + cloudflareConfigPath: string; + types: NormalizedTypes; +}): Promise { + if (!options.types.generate) { + return; + } + + let content: string; + try { + const outputDir = path.dirname( + path.resolve(DEFAULT_WORKERS_TYPES_FILE_PATH) + ); + const relativeConfigPath = + "./" + path.relative(outputDir, options.cloudflareConfigPath); + content = generateTypes({ + configPath: relativeConfigPath, + packageName: "wrangler/experimental-config", + }); + } catch (e) { + logger.error(e); + return; + } + + // Diff against the on-disk file before writing to avoid mtime churn + // (matches the Vite plugin's behaviour for the same `.d.ts`). + let existing: string | undefined; + try { + existing = readFileSync(DEFAULT_WORKERS_TYPES_FILE_PATH, "utf-8"); + } catch { + // File doesn't exist yet — fall through to write. + } + + if (existing === content) { + return; + } + + try { + writeFileSync(DEFAULT_WORKERS_TYPES_FILE_PATH, content); + logger.log( + `📝 Regenerated ${path.relative(process.cwd(), DEFAULT_WORKERS_TYPES_FILE_NAME)} from ${path.relative(process.cwd(), options.cloudflareConfigPath)}.` + ); + } catch (e) { + logger.error(e); + } +} diff --git a/packages/wrangler/src/versions/deploy.ts b/packages/wrangler/src/versions/deploy.ts index 6e55c2b805..5a528046dc 100644 --- a/packages/wrangler/src/versions/deploy.ts +++ b/packages/wrangler/src/versions/deploy.ts @@ -11,6 +11,7 @@ import { type ApiVersion, printVersions } from "@cloudflare/deploy-helpers"; import { UserError } from "@cloudflare/workers-utils"; import { fetchResult } from "../cfetch"; import { createCommand } from "../core/create-command"; +import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import * as metrics from "../metrics"; import { writeOutput } from "../output"; import { requireAuth } from "../user"; @@ -45,6 +46,7 @@ export const versionsDeployCommand = createCommand({ }, args: { + ...experimentalNewConfigArg, name: { describe: "Name of the worker", type: "string", diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index ed513af36c..c346c810bc 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -11,6 +11,7 @@ import { cleanupDestination, mergeVersionsUploadConfigArgs, } from "../deployment-bundle/merge-config-args"; +import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import * as metrics from "../metrics"; import { writeOutput } from "../output"; import { getScriptName } from "../utils/getScriptName"; @@ -23,6 +24,7 @@ export const versionsUploadCommand = createCommand({ }, positionalArgs: ["path"], args: { + ...experimentalNewConfigArg, ...sharedDeployVersionsArgs, "preview-alias": { describe: "Name of an alias for this Worker version", diff --git a/packages/wrangler/tsconfig.experimental-config.json b/packages/wrangler/tsconfig.experimental-config.json new file mode 100644 index 0000000000..a4d8f817f0 --- /dev/null +++ b/packages/wrangler/tsconfig.experimental-config.json @@ -0,0 +1,8 @@ +{ + "extends": "@cloudflare/workers-tsconfig/worker.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true + }, + "include": ["src/experimental-config"] +} diff --git a/packages/wrangler/tsconfig.json b/packages/wrangler/tsconfig.json index cd5e6b8761..dbb4dafcc4 100644 --- a/packages/wrangler/tsconfig.json +++ b/packages/wrangler/tsconfig.json @@ -6,7 +6,7 @@ "jsx": "react-jsx", "tsBuildInfoFile": ".tsbuildinfo" }, - "include": ["**/*.ts", "**/*.tsx", "**/*.js"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "tsdown.config.mts"], "exclude": [ "emitted-types", "node_modules", diff --git a/packages/wrangler/tsdown.config.mts b/packages/wrangler/tsdown.config.mts new file mode 100644 index 0000000000..ebcab161f7 --- /dev/null +++ b/packages/wrangler/tsdown.config.mts @@ -0,0 +1,14 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: { + "experimental-config": "src/experimental-config.ts", + }, + platform: "node", + outDir: "wrangler-dist", + clean: false, + tsconfig: "tsconfig.experimental-config.json", + dts: { + resolve: ["@cloudflare/config"], + }, +}); diff --git a/packages/wrangler/tsup.config.ts b/packages/wrangler/tsup.config.ts index 5e24c43e7a..1f10092c5c 100644 --- a/packages/wrangler/tsup.config.ts +++ b/packages/wrangler/tsup.config.ts @@ -110,5 +110,26 @@ export default defineConfig((options) => [ : {}), }, esbuildPlugins: [embedWorkersPlugin({ isWatch: !!options.watch })], + esbuildOptions(esbuildOptions) { + esbuildOptions.logOverride = { + ...esbuildOptions.logOverride, + // Suppress the warning for the intentional runtime dynamic + // `import()` in `@cloudflare/config`'s `loadConfig` + // (packages/config/src/load.ts). The import is unanalyzable by + // design: the specifier is computed at runtime and the + // `with: { cf: "no-cache" }` attribute is consumed by a Node + // `registerHooks` resolver. esbuild already preserves the call + // verbatim — only the warning is noise. + // tsup's *DTS* pipeline (Rollup) also emits a separate + // `INVALID_IMPORT_ATTRIBUTE` warning for the same call site + // ("…the attribute will be removed"). That warning is also + // cosmetic — the DTS step does not write `cli.js`, and `cli.d.ts` + // contains no runtime import calls — but tsup 8.3 does not + // expose a Rollup `onwarn` hook for the DTS pipeline, so we + // currently cannot silence it. + // Note: this is not necessary if we publish `@cloudflare/config` and include is as a dependency + "unsupported-dynamic-import": "silent", + }; + }, }, ]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26f6198415..eda72d8bd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -376,6 +376,30 @@ importers: specifier: workspace:* version: link:../../packages/wrangler + fixtures/experimental-new-config: + devDependencies: + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../packages/workers-tsconfig + '@cloudflare/workers-types': + specifier: catalog:default + version: 4.20260611.1 + '@fixture/shared': + specifier: workspace:* + version: link:../shared + typescript: + specifier: catalog:default + version: 5.8.3 + undici: + specifier: catalog:default + version: 7.24.8 + vitest: + specifier: catalog:default + version: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@22.15.17)(@vitest/ui@4.1.0)(msw@2.12.4(@types/node@22.15.17)(typescript@5.8.3))(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) + wrangler: + specifier: workspace:* + version: link:../../packages/wrangler + fixtures/get-platform-proxy: devDependencies: '@cloudflare/workers-tsconfig': @@ -1587,7 +1611,7 @@ importers: version: 6.0.2 tsdown: specifier: ^0.15.9 - version: 0.15.9(typescript@5.9.3) + version: 0.15.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(typescript@5.9.3) packages/codemod: devDependencies: @@ -4063,7 +4087,7 @@ importers: version: 2.2.0 tsdown: specifier: ^0.15.9 - version: 0.15.9(typescript@5.8.3) + version: 0.15.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(typescript@5.8.3) tsup: specifier: 8.3.0 version: 8.3.0(@microsoft/api-extractor@7.52.8(@types/node@22.15.17))(jiti@2.6.1)(postcss@8.5.14)(supports-color@9.2.2)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) @@ -4156,6 +4180,9 @@ importers: '@cloudflare/codemod': specifier: workspace:* version: link:../codemod + '@cloudflare/config': + specifier: workspace:* + version: link:../config '@cloudflare/containers-shared': specifier: workspace:* version: link:../containers-shared @@ -4393,6 +4420,9 @@ importers: ts-json-schema-generator: specifier: ^1.5.0 version: 1.5.0 + tsdown: + specifier: 0.16.3 + version: 0.16.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(ms@2.1.3)(synckit@0.11.12)(typescript@5.8.3) tsup: specifier: 8.3.0 version: 8.3.0(@microsoft/api-extractor@7.52.8(@types/node@22.15.17))(jiti@2.6.1)(postcss@8.5.14)(supports-color@9.2.2)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.1) @@ -4420,6 +4450,9 @@ importers: yargs: specifier: ^17.7.2 version: 17.7.2 + zod: + specifier: ^4.4.3 + version: 4.4.3 optionalDependencies: fsevents: specifier: 2.3.3 @@ -6702,9 +6735,6 @@ packages: resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} engines: {node: '>=18'} - '@napi-rs/wasm-runtime@1.1.1': - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -7321,6 +7351,9 @@ packages: '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@radix-ui/number@1.0.1': resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} @@ -9432,6 +9465,10 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -10616,6 +10653,10 @@ packages: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -13384,6 +13425,9 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -13824,6 +13868,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.2: + resolution: {integrity: sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -14328,6 +14377,10 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -14336,6 +14389,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinypool@2.1.0: resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} engines: {node: ^20.0.0 || >=22.0.0} @@ -14688,8 +14745,8 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - unconfig-core@7.4.1: - resolution: {integrity: sha512-Bp/bPZjV2Vl/fofoA2OYLSnw1Z0MOhCX7zHnVCYrazpfZvseBbGhwcNQMxsg185Mqh7VZQqK3C8hFG/Dyng+yA==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} unconfig@7.3.3: resolution: {integrity: sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==} @@ -15791,7 +15848,7 @@ snapshots: '@babel/code-frame@7.26.2': dependencies: - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 optional: true @@ -17835,13 +17892,6 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/wasm-runtime@1.1.1': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.1 - optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -18358,6 +18408,10 @@ snapshots: dependencies: quansync: 0.2.11 + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + '@radix-ui/number@1.0.1': dependencies: '@babel/runtime': 7.28.6 @@ -18568,9 +18622,12 @@ snapshots: '@rolldown/binding-openharmony-arm64@1.0.1': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.44': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@napi-rs/wasm-runtime': 1.1.1 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' optional: true '@rolldown/binding-wasm32-wasi@1.0.0-beta.49(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': @@ -19685,11 +19742,11 @@ snapshots: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 '@babel/types': 7.29.0 - ansis: 4.2.0 + ansis: 4.3.1 babel-dead-code-elimination: 1.0.12 diff: 8.0.3 pathe: 2.0.3 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 transitivePeerDependencies: - supports-color @@ -20255,7 +20312,7 @@ snapshots: flatted: 3.4.0 pathe: 2.0.3 sirv: 3.0.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 tinyrainbow: 3.0.3 vitest: 4.1.0(@opentelemetry/api@1.9.1)(@types/node@22.15.17)(@vitest/ui@4.1.0)(msw@2.12.4(@types/node@22.15.17)(typescript@5.8.3))(vite@8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1)) @@ -20479,6 +20536,8 @@ snapshots: ansis@4.2.0: {} + ansis@4.3.1: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -21522,6 +21581,8 @@ snapshots: empathic@2.0.0: {} + empathic@2.0.1: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -23044,7 +23105,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.3 + semver: 7.8.2 jsprim@2.0.2: dependencies: @@ -23434,8 +23495,8 @@ snapshots: pkg-types: 1.3.1 postcss: 8.5.14 postcss-nested: 7.0.2(postcss@8.5.14) - semver: 7.7.3 - tinyglobby: 0.2.16 + semver: 7.8.2 + tinyglobby: 0.2.17 optionalDependencies: typescript: 5.8.3 vue-tsc: 2.0.29(typescript@5.8.3) @@ -23596,7 +23657,7 @@ snapshots: node-abi@3.62.0: dependencies: - semver: 7.7.3 + semver: 7.8.2 optional: true node-domexception@1.0.0: {} @@ -23632,7 +23693,7 @@ snapshots: normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 - semver: 7.7.3 + semver: 7.8.2 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -23663,7 +23724,7 @@ snapshots: dependencies: citty: 0.2.0 pathe: 2.0.3 - tinyexec: 1.0.2 + tinyexec: 1.2.4 object-assign@4.1.1: {} @@ -24498,6 +24559,8 @@ snapshots: quansync@0.2.11: {} + quansync@1.0.0: {} + queue-microtask@1.2.3: {} queue-tick@1.0.1: {} @@ -24813,7 +24876,7 @@ snapshots: reusify@1.0.4: {} - rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.44)(typescript@5.8.3): + rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.8.3): dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 @@ -24824,14 +24887,14 @@ snapshots: dts-resolver: 2.1.3 get-tsconfig: 4.13.6 magic-string: 0.30.21 - rolldown: 1.0.0-beta.44 + rolldown: 1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: - oxc-resolver - supports-color - rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.44)(typescript@5.9.3): + rolldown-plugin-dts@0.16.12(rolldown@1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.9.3): dependencies: '@babel/generator': 7.29.1 '@babel/parser': 7.29.0 @@ -24842,7 +24905,7 @@ snapshots: dts-resolver: 2.1.3 get-tsconfig: 4.13.6 magic-string: 0.30.21 - rolldown: 1.0.0-beta.44 + rolldown: 1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -24867,7 +24930,7 @@ snapshots: - ms - oxc-resolver - rolldown@1.0.0-beta.44: + rolldown@1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: '@oxc-project/types': 0.95.0 '@rolldown/pluginutils': 1.0.0-beta.44 @@ -24882,10 +24945,13 @@ snapshots: '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.44 '@rolldown/binding-linux-x64-musl': 1.0.0-beta.44 '@rolldown/binding-openharmony-arm64': 1.0.0-beta.44 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.44 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.44 '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.44 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.44 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' rolldown@1.0.0-beta.49(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: @@ -25109,6 +25175,8 @@ snapshots: semver@7.7.3: {} + semver@7.8.2: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -25717,6 +25785,8 @@ snapshots: tinyexec@1.0.2: {} + tinyexec@1.2.4: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -25727,6 +25797,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@2.1.0: {} tinyrainbow@1.2.0: {} @@ -25808,7 +25883,7 @@ snapshots: strip-bom: 3.0.0 strip-json-comments: 2.0.1 - tsdown@0.15.9(typescript@5.8.3): + tsdown@0.15.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(typescript@5.8.3): dependencies: ansis: 4.2.0 cac: 6.7.14 @@ -25817,8 +25892,8 @@ snapshots: diff: 8.0.3 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.44 - rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.44)(typescript@5.8.3) + rolldown: 1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.8.3) semver: 7.7.3 tinyexec: 1.0.2 tinyglobby: 0.2.15 @@ -25827,13 +25902,15 @@ snapshots: optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' - '@ts-macro/tsc' - '@typescript/native-preview' - oxc-resolver - supports-color - vue-tsc - tsdown@0.15.9(typescript@5.9.3): + tsdown@0.15.9(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(typescript@5.9.3): dependencies: ansis: 4.2.0 cac: 6.7.14 @@ -25842,8 +25919,8 @@ snapshots: diff: 8.0.3 empathic: 2.0.0 hookable: 5.5.3 - rolldown: 1.0.0-beta.44 - rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.44)(typescript@5.9.3) + rolldown: 1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + rolldown-plugin-dts: 0.16.12(rolldown@1.0.0-beta.44(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.2 tinyglobby: 0.2.15 @@ -25852,6 +25929,8 @@ snapshots: optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' - '@ts-macro/tsc' - '@typescript/native-preview' - oxc-resolver @@ -25860,20 +25939,20 @@ snapshots: tsdown@0.16.3(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(ms@2.1.3)(synckit@0.11.12)(typescript@5.8.3): dependencies: - ansis: 4.2.0 + ansis: 4.3.1 cac: 6.7.14 chokidar: 4.0.3 diff: 8.0.3 - empathic: 2.0.0 + empathic: 2.0.1 hookable: 5.5.3 obug: 0.1.3(ms@2.1.3) rolldown: 1.0.0-beta.49(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) rolldown-plugin-dts: 0.17.6(ms@2.1.3)(rolldown@1.0.0-beta.49(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.8.3) - semver: 7.7.3 - tinyexec: 1.0.2 - tinyglobby: 0.2.16 + semver: 7.8.2 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tree-kill: 1.2.2 - unconfig-core: 7.4.1 + unconfig-core: 7.5.0 unrun: 0.2.8(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(synckit@0.11.12) optionalDependencies: typescript: 5.8.3 @@ -26160,7 +26239,7 @@ snapshots: rollup: 4.30.1 rollup-plugin-dts: 6.1.1(rollup@4.30.1)(typescript@5.8.3) scule: 1.3.0 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 untyped: 1.5.2 optionalDependencies: typescript: 5.8.3 @@ -26175,10 +26254,10 @@ snapshots: buffer: 5.7.1 through: 2.3.8 - unconfig-core@7.4.1: + unconfig-core@7.5.0: dependencies: - '@quansync/fs': 0.1.5 - quansync: 0.2.11 + '@quansync/fs': 1.0.0 + quansync: 1.0.0 unconfig@7.3.3: dependencies: @@ -26453,7 +26532,7 @@ snapshots: picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.57.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.15.17 fsevents: 2.3.3 @@ -26468,7 +26547,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.14 rolldown: 1.0.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.15.17 esbuild: 0.23.1 @@ -26483,7 +26562,7 @@ snapshots: picomatch: 4.0.4 postcss: 8.5.14 rolldown: 1.0.1 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 optionalDependencies: '@types/node': 22.15.17 esbuild: 0.27.3 @@ -26515,8 +26594,8 @@ snapshots: picomatch: 4.0.4 std-env: 4.0.0 tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.16 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tinyrainbow: 3.0.3 vite: 8.0.13(@types/node@22.15.17)(esbuild@0.23.1)(jiti@2.6.1)(tsx@3.12.10)(yaml@2.8.1) why-is-node-running: 2.3.0 @@ -26544,8 +26623,8 @@ snapshots: picomatch: 4.0.4 std-env: 4.0.0 tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.16 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tinyrainbow: 3.0.3 vite: 8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 @@ -26573,8 +26652,8 @@ snapshots: picomatch: 4.0.4 std-env: 4.0.0 tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.16 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 tinyrainbow: 3.0.3 vite: 8.0.13(@types/node@22.15.17)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 @@ -26591,7 +26670,7 @@ snapshots: dependencies: '@volar/typescript': 2.4.0-alpha.18 '@vue/language-core': 2.0.29(typescript@5.8.3) - semver: 7.7.3 + semver: 7.8.2 typescript: 5.8.3 w3c-keyname@2.2.8: {} diff --git a/tools/github-workflow-helpers/auto-assign-issues.ts b/tools/github-workflow-helpers/auto-assign-issues.ts deleted file mode 100644 index 25e4cf6cbf..0000000000 --- a/tools/github-workflow-helpers/auto-assign-issues.ts +++ /dev/null @@ -1,141 +0,0 @@ -import * as core from "@actions/core"; -import { context, getOctokit } from "@actions/github"; -import dedent from "ts-dedent"; -import type { IssuesLabeledEvent } from "@octokit/webhooks-types"; - -/** - * The mapping of github issue labels to team members for assignment. - */ -const TEAM_ASSIGNMENTS: { [label: string]: { [jobRole: string]: string } } = { - "product:agents": { em: "whoiskatrin", pm: "mikenomitch" }, - "product:analytics-engine": {}, - api: { em: "dcartertwo", pm: "korinne" }, - auth: { em: "dcartertwo", pm: "korinne" }, - "feature:auto-provisioning": { em: "lrapoport-cf", pm: "mattietk" }, - "package:c3": { em: "lrapoport-cf", pm: "mattietk" }, - "product:constellation": {}, - "product:containers": { em: "th0m", pm: "mikenomitch" }, - "product:d1": { em: "joshthoward", pm: "vy-ton" }, - documentation: {}, - "product:durable-objects": { em: "joshthoward", pm: "vy-ton" }, - "product:email": { pm: "thomasgauvin" }, - "product:hyperdrive": { em: "sejoker", pm: "thomasgauvin" }, - "package:kv-asset-handler": { em: "lrapoport-cf", pm: "mattietk" }, - "package:miniflare": { em: "lrapoport-cf", pm: "mattietk" }, - "feature:multiworker": { em: "lrapoport-cf", pm: "mattietk" }, - "node-compat": { em: "lrapoport-cf", pm: "mattietk" }, - "nodejs-compat": { em: "lrapoport-cf", pm: "mattietk" }, - "feature:observability": { em: "boristane", pm: "nevikashah" }, - opennext: { em: "lrapoport-cf", pm: "mattietk", tl: "vicb" }, - "product:pages": { em: "dcartertwo", pm: "irvinebroque" }, - "product:pipelines": {}, - "feature:playground-worker": { em: "lrapoport-cf", pm: "mattietk" }, - python: { em: "danlapid", pm: "mikenomitch" }, - "product:queues": {}, - "product:r2": { em: "annibread", pm: "aninibread" }, - "feature:remote-bindings": { em: "lrapoport-cf", pm: "mattietk" }, - "product:secrets-store": {}, - "start-dev-worker": { em: "lrapoport-cf", pm: "mattietk" }, - "feature:templates": { em: "lrapoport-cf", pm: "mattietk" }, - "feature:types": { em: "lrapoport-cf", pm: "mattietk" }, - "feature:types-ai": { em: "jkipp-cloudflare" }, - "package:unenv": { em: "lrapoport-cf", pm: "mattietk" }, - "product:vectorize": { em: "sejoker", pm: "jonesphillip" }, - "package:vite-plugin": { em: "lrapoport-cf", pm: "mattietk" }, - "package:vitest": { em: "lrapoport-cf", pm: "mattietk" }, - "product:vpc": { em: "efalcao", pm: "thomasgauvin" }, - "feature:workers-assets": { em: "dcartertwo", pm: "irvinebroque" }, - "product:workers-ai": { em: "jkipp-cloudflare" }, - "feature:workers-builds": { - director: "fredkschott", - pm: "yomna-shousha", - em: "ericclemmons", - }, - "product:workflows": { em: "bruxodasilva", pm: "jonesphillip" }, - "package:wrangler": { em: "lrapoport-cf", pm: "mattietk" }, -}; - -if (require.main === module) { - run().catch((error) => { - core.setFailed(error); - }); -} - -async function run() { - core.info(dedent` - Auto Assign Issues - ================== - `); - - if (isIssuesLabeledEvent(context.payload)) { - const { - issue, - label: { name: labelName }, - } = context.payload; - - core.info(`Processing new label: ${labelName} for issue #${issue.number}`); - - const teamConfig = TEAM_ASSIGNMENTS[labelName]; - if (!teamConfig) { - core.info(`No team assignment found for label: ${labelName}`); - return; - } - - const teamAssignees = new Set(Object.values(teamConfig)); - const currentAssignees = new Set(issue.assignees?.map((a) => a.login)); - const toBeAssigned = teamAssignees.difference(currentAssignees); - - if (toBeAssigned.size === 0) { - core.info( - `All potential assignees are already assigned to issue. Skipping auto-assignment.` - ); - return; - } - - core.info( - `Assigning to: ${new Intl.ListFormat("en").format(toBeAssigned)}` - ); - - const token = core.getInput("github_token", { required: true }); - const octokit = getOctokit(token); - const result = await octokit.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: Array.from(toBeAssigned), - }); - - const assigned = new Set(result.data.assignees?.map((a) => a.login)); - const missing = toBeAssigned.difference(assigned); - - if (missing.size > 0) { - core.warning( - dedent` - Not all assignees were added to the issue. They may not be collaborators on the repository. - Missing assignees: ${new Intl.ListFormat("en").format(missing)} - ` - ); - } - - core.info( - `Issue assigned to ${new Intl.ListFormat("en").format(assigned)}` - ); - } -} - -/** - * Type guard to check if the payload is an IssuesLabeledEvent. - */ -function isIssuesLabeledEvent( - payload: object -): payload is Required { - return ( - payload && - "action" in payload && - payload.action === "labeled" && - "issue" in payload && - !!payload.issue && - "label" in payload && - !!payload.label - ); -}