Skip to content

Support programmatic TypeScript config (cf.config.ts)#12575

Draft
penalosa wants to merge 1 commit intopenalosa/async-read-configfrom
penalosa/programmatic-legacy-config
Draft

Support programmatic TypeScript config (cf.config.ts)#12575
penalosa wants to merge 1 commit intopenalosa/async-read-configfrom
penalosa/programmatic-legacy-config

Conversation

@penalosa
Copy link
Contributor

@penalosa penalosa commented Feb 16, 2026

One step along the road of supporting programmatic config in Wrangler. This part adds support for programmatic config with the existing config file structure, to make the diff semi easy to read.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: still experimental

A picture of a cute animal (not mandatory, but encouraged)


Open with Devin

… readConfig

Make experimental_readRawConfig async to support loading programmatic
config files. Propagate async through all consumers: readConfig,
readPagesConfig, unstable_readConfig, unstable_getMiniflareWorkerOptions,
and the vite-plugin/vitest-pool-workers integration layers.

- Add programmatic config loading (loadProgrammaticConfig) and
  workerConfigToConfig conversion in workers-utils
- Wire programmatic config detection into register-yargs-command
- Update all ~40 call sites across wrangler, vite-plugin, and
  vitest-pool-workers to await the now-async config functions
- Update test assertions for async throwing (rejects.toThrow)
- Fix pre-existing yargs YError snapshot mismatches in dev/init tests
@penalosa penalosa requested a review from a team as a code owner February 16, 2026 21:20
@changeset-bot
Copy link

changeset-bot bot commented Feb 16, 2026

⚠️ No Changeset found

Latest commit: 1df4eb6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@penalosa penalosa marked this pull request as draft February 16, 2026 21:20
@github-project-automation github-project-automation bot moved this to Untriaged in workers-sdk Feb 16, 2026
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +33 to +34
const fileUrl = `file://${file}`;
const module = await import(fileUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 ESM module cache prevents watch mode from picking up config changes

In watchProgrammaticConfig, the rebuilt config file is always written to a fixed path (cf-config-watch.mjs at line 152), and loadFunctionOrObjectConfig loads it via import(fileUrl) at packages/workers-utils/src/config/programmatic.ts:34. Node.js caches ESM modules by URL — once a file:// URL has been imported, subsequent import() calls to the same URL return the cached module. This means only the first config load in watch mode will read the actual file; all subsequent rebuilds will silently return the stale cached module.

Root Cause and Impact

The one-shot loadProgrammaticConfig function avoids this problem by generating a unique filename per invocation using Date.now() and Math.random() (packages/workers-utils/src/config/programmatic.ts:104-107). However, watchProgrammaticConfig uses a static filename:

const tmpFile = path.join(tmpDir, `cf-config-watch.mjs`); // line 152

When the esbuild watch plugin's onEnd callback fires on a rebuild (line 164), it calls:

const workerConfig = await loadFunctionOrObjectConfig(tmpFile, { env }); // line 187

which internally does:

const fileUrl = `file://${file}`;
const module = await import(fileUrl); // line 34 — returns cached module!

Since the fileUrl is always file://<tmpDir>/cf-config-watch.mjs, Node.js returns the cached module from the first import. The onChange callback will be invoked with the original config object, not the updated one.

Impact: wrangler dev with a programmatic config file (e.g. cf.config.ts) will never reload config changes during watch mode. Users editing their config will see no effect until they restart wrangler dev.

The fix is to append a cache-busting query parameter to the URL, e.g.:

const fileUrl = `file://${file}?t=${Date.now()}`;
Suggested change
const fileUrl = `file://${file}`;
const module = await import(fileUrl);
const fileUrl = `file://${file}?t=${Date.now()}`;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 16, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@12575

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@12575

miniflare

npm i https://pkg.pr.new/miniflare@12575

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@12575

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@12575

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@12575

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@12575

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@12575

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@12575

wrangler

npm i https://pkg.pr.new/wrangler@12575

commit: 1df4eb6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

1 participant

Comments