Skip to content

[wrangler] Add programmatic TypeScript config file support#12533

Draft
penalosa wants to merge 9 commits intomainfrom
penalosa/cf-ts
Draft

[wrangler] Add programmatic TypeScript config file support#12533
penalosa wants to merge 9 commits intomainfrom
penalosa/cf-ts

Conversation

@penalosa
Copy link
Contributor

WIP — introduces programmatic TypeScript config files (cf.config.ts) as an alternative to static wrangler.toml/wrangler.json.

Changes

  • Flat bindings refactorcreateWorkerUploadForm and provisionBindings use the new flat bindings format
  • Move core types to @cloudflare/workers-utilsStartDevWorkerInput, Hook, CfAccount, etc. so they can be imported by user config files without pulling in all of wrangler
  • New WorkerConfig type — user-facing subset of StartDevWorkerInput with env instead of bindings
  • worker() helper — type-safe identity function for config files: export default worker(() => ({ ... }))
  • Programmatic config loading — esbuild bundles and executes .ts/.js config files, with watch mode support
  • Config discovery — now searches for cf.config.tscloudflare.config.tswrangler.config.tswrangler.jsonwrangler.toml
  • New subpath exportswrangler/config and @cloudflare/workers-utils/programmatic
  • ConfigController updated to detect and handle programmatic configs

  • 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:

@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

⚠️ No Changeset found

Latest commit: 1516480

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

case "programmatic":
// Return the actual filename for programmatic configs
return configPath
? configPath.split("/").pop() ?? "config file"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a path API for this that'd be better

Comment on lines 145 to 155
// For programmatic configs, return empty config - they are handled
// separately via loadProgrammaticConfig() in the ConfigController
if (isProgrammaticConfigPath(configPath)) {
return {
rawConfig: {},
configPath,
userConfigPath,
deployConfigPath,
redirected,
};
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should probably throw in this case instead?

/**
* Helper function to define a worker configuration with type safety.
*/
export function worker(fn: WorkerConfigFn): WorkerConfigFn {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we actually want this user facing function to be called defineConfig()

Comment on lines 123 to 124
const wranglerDir = path.join(configDir, ".wrangler", "tmp");
await fs.promises.mkdir(wranglerDir, { recursive: true });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't we have helpers for finding Wrangler's temp dir?

const tsconfigPath = findTsConfig(configDir);

// Create a unique temp file path in .wrangler directory
const wranglerDir = path.join(configDir, ".wrangler", "tmp");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't we have helpers for this?

Comment on lines 43 to 45
"dependencies": {
"esbuild": "catalog:default"
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Move this to devDeps along with everything else

this.#programmaticWatcher = await watchProgrammaticConfig({
configPath,
env: this.latestInput?.env,
onChange: (_result: LoadProgrammaticConfigResult) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why isn't this result being used?

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 11, 2026

create-cloudflare

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

@cloudflare/kv-asset-handler

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

miniflare

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

@cloudflare/pages-shared

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

@cloudflare/unenv-preset

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

@cloudflare/vite-plugin

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

@cloudflare/vitest-pool-workers

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

@cloudflare/workers-editor-shared

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

@cloudflare/workers-utils

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

wrangler

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

commit: 1516480

- Add missing curly braces for single-line if statements (config-helpers, programmatic)
- Replace non-null assertions with proper narrowing guards (ConfigController, register-yargs-command, worker-config-to-config)
- Remove unused type imports (startDevWorker/types)
- Use const for single-assignment variable (programmatic.ts ctx)
- Remove unused BuildContext import
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file is so much messier than I wanted it to be. Turns out that even after the CfWorkerInit["bindings"] refactor we had a lot of places accessing bindings directly from the Config type (which had a similar shape). More refactoring to come to make this simpler!

Comment on lines +56 to +64
/**
* Pre-flattened bindings from programmatic config.
* When set, deploy can use these directly instead of calling getBindings(config)
* which would unflatten→reflatten. TOML configs leave this undefined.
*
* Typed as `Record<string, unknown>` to avoid cross-module type dependency in
* the DTS bundle. Consumers should cast to `Record<string, Binding>`.
*/
flatBindings?: Record<string, unknown>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is messy, and there's more refactoring to do to make this not necessary

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

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

1 participant

Comments