From 58f4ee5914eacc03de375c4c9dbc1d8d2b8bba40 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 4 Dec 2025 11:10:22 +0100 Subject: [PATCH 1/7] Add template susbsitution --- .changeset/brave-lights-run.md | 5 ++ .../src/helpers/__tests__/json.test.ts | 38 +++++++++ .../create-cloudflare/src/helpers/json.ts | 9 ++- .../src/wrangler/__tests__/config.test.ts | 19 +++++ .../create-cloudflare/src/wrangler/config.ts | 78 +++++++++++++++---- 5 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 .changeset/brave-lights-run.md diff --git a/.changeset/brave-lights-run.md b/.changeset/brave-lights-run.md new file mode 100644 index 000000000000..c923ad3f80af --- /dev/null +++ b/.changeset/brave-lights-run.md @@ -0,0 +1,5 @@ +--- +"create-cloudflare": minor +--- + +Add template substitution diff --git a/packages/create-cloudflare/src/helpers/__tests__/json.test.ts b/packages/create-cloudflare/src/helpers/__tests__/json.test.ts index fa27ca985421..eaac3a544298 100644 --- a/packages/create-cloudflare/src/helpers/__tests__/json.test.ts +++ b/packages/create-cloudflare/src/helpers/__tests__/json.test.ts @@ -50,6 +50,44 @@ describe("json helpers", () => { // post-comment" `); }); + + test("using a function replacer", () => { + mockReadFile.mockReturnValue( + JSON.stringify({ + name: "test", + rootValue: "_REPLACE_ME_", + keep: "DO_NOT_REPLACE_ME_", + nested: { + value: "_REPLACE_ME_", + list: [["_REPLACE_ME_"], "DO_NOT_REPLACE_ME_"], + }, + }), + ); + + const result = readJSONWithComments("/path/to/file.json"); + writeJSONWithComments("/path/to/file.json", result, (_key, value) => + value === "_REPLACE_ME_" ? "REPLACED" : value, + ); + expect(mockWriteFile.mock.calls[0][0]).toMatchInlineSnapshot( + `"/path/to/file.json"`, + ); + expect(mockWriteFile.mock.calls[0][1]).toMatchInlineSnapshot(` + "{ + "name": "test", + "rootValue": "REPLACED", + "keep": "DO_NOT_REPLACE_ME_", + "nested": { + "value": "REPLACED", + "list": [ + [ + "REPLACED" + ], + "DO_NOT_REPLACE_ME_" + ] + } + }" + `); + }); }); describe("addJSONComment", () => { diff --git a/packages/create-cloudflare/src/helpers/json.ts b/packages/create-cloudflare/src/helpers/json.ts index d5485b1bca19..217ac1977093 100644 --- a/packages/create-cloudflare/src/helpers/json.ts +++ b/packages/create-cloudflare/src/helpers/json.ts @@ -22,12 +22,19 @@ export function readJSONWithComments(jsonFilePath: string): CommentObject { * Writes a JSON object to a file, preserving comments. * @param jsonObject - The JSON object (with comment properties) to write. * @param jsonFilePath - The path to the JSON file. + * @param replacer A function that transforms the results or + * an array of strings and numbers that acts as an approved list for selecting + * the object properties that will be stringified. */ export function writeJSONWithComments( jsonFilePath: string, jsonObject: CommentObject, + replacer?: + | ((key: string, value: unknown) => unknown) + | Array + | null, ): void { - const jsonStr = stringify(jsonObject, null, "\t"); + const jsonStr = stringify(jsonObject, replacer, "\t"); writeFile(jsonFilePath, jsonStr); } diff --git a/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts b/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts index 6ae00bbe8949..67a7499922f3 100644 --- a/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts +++ b/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts @@ -35,6 +35,9 @@ describe("update wrangler config", () => { `name = ""`, `main = "src/index.ts"`, `compatibility_date = ""`, + `[[services]]`, + `binding = "SELF_SERVICE"`, + `service = "__WORKER_NAME__"`, ].join("\n"); vi.mocked(readFile).mockReturnValue(toml); @@ -49,6 +52,10 @@ describe("update wrangler config", () => { main = "src/index.ts" compatibility_date = "2024-01-17" + [[services]] + binding = "SELF_SERVICE" + service = "test" + [observability] enabled = true @@ -95,6 +102,12 @@ describe("update wrangler config", () => { name: "", main: "src/index.ts", compatibility_date: "", + services: [ + { + binding: "SELF_SERVICE", + service: "__WORKER_NAME__", + }, + ], }); vi.mocked(readFile).mockReturnValueOnce(json); @@ -111,6 +124,12 @@ describe("update wrangler config", () => { "name": "test", "main": "src/index.ts", "compatibility_date": "2024-01-17", + "services": [ + { + "binding": "SELF_SERVICE", + "service": "test" + } + ], "observability": { "enabled": true } diff --git a/packages/create-cloudflare/src/wrangler/config.ts b/packages/create-cloudflare/src/wrangler/config.ts index 02855639265f..46d17144ba8a 100644 --- a/packages/create-cloudflare/src/wrangler/config.ts +++ b/packages/create-cloudflare/src/wrangler/config.ts @@ -14,10 +14,24 @@ import type { CommentObject } from "comment-json"; import type { C3Context } from "types"; /** - * Update the `wrangler.(toml|json|jsonc)` file for this project by setting the name - * to the selected project name and adding the latest compatibility date. + * Update the `wrangler.(toml|json|jsonc)` file for this project by: + * + * - setting the `name` to the passed project name + * - adding the latest compatibility date when no valid one is present + * - enabling observability + * - adding comments with links to documentation for common configuration options + * - substituting placeholders with actual values + * - `__WORKER_NAME__` with the project name + * + * If both `wrangler.toml` and `wrangler.json`/`wrangler.jsonc` are present, only + * the `wrangler.json`/`wrangler.jsonc` file will be updated. */ export const updateWranglerConfig = async (ctx: C3Context) => { + // Placeholders to replace in the wrangler config files + const substitutions: Record = { + __WORKER_NAME__: ctx.project.name, + }; + if (wranglerJsonExists(ctx)) { let wranglerJson = readWranglerJson(ctx); @@ -70,7 +84,11 @@ export const updateWranglerConfig = async (ctx: C3Context) => { }, ]); - writeWranglerJson(ctx, wranglerJson); + writeWranglerJson(ctx, wranglerJson, (_key, value) => + typeof value === "string" && value in substitutions + ? substitutions[value] + : value, + ); addVscodeConfig(ctx); } else if (wranglerTomlExists(ctx)) { const wranglerTomlStr = readWranglerToml(ctx); @@ -79,14 +97,17 @@ export const updateWranglerConfig = async (ctx: C3Context) => { parsed["compatibility_date"] = await getCompatibilityDate(parsed); parsed["observability"] ??= { enabled: true }; - const comment = `#:schema node_modules/wrangler/config-schema.json\n# For more details on how to configure Wrangler, refer to:\n# https://developers.cloudflare.com/workers/wrangler/configuration/\n`; + let strToml = TOML.stringify(parsed); - const stringified = comment + TOML.stringify(parsed); + for (const [key, value] of Object.entries(substitutions)) { + strToml = strToml.replaceAll(key, value); + } writeWranglerToml( ctx, - stringified + - ` + `#:schema node_modules/wrangler/config-schema.json +# For more details on how to configure Wrangler, refer to:\n# https://developers.cloudflare.com/workers/wrangler/configuration/ +${strToml} # Smart Placement # Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement # [placement] @@ -166,13 +187,32 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => { return writeFile(wranglerTomlPath, contents); }; -export const writeWranglerJson = (ctx: C3Context, config: CommentObject) => { +/** + * Writes the passed JSON object as the configuration file for this project. + * + * If there is an existing `wrangler.json` file, it will be overwritten. + * If not, `wrangler.jsonc` will be created/overwritten. + * + * @param ctx The C3 context. + * @param config The JSON object (with comment properties) to write. + * @param replacer A function that transforms the results or + * an array of strings and numbers that acts as an approved list for selecting + * the object properties that will be stringified. + */ +export const writeWranglerJson = ( + ctx: C3Context, + config: CommentObject, + replacer?: + | ((key: string, value: unknown) => unknown) + | Array + | null, +) => { const wranglerJsonPath = getWranglerJsonPath(ctx); if (existsSync(wranglerJsonPath)) { - return writeJSONWithComments(wranglerJsonPath, config); + return writeJSONWithComments(wranglerJsonPath, config, replacer); } const wranglerJsoncPath = getWranglerJsoncPath(ctx); - return writeJSONWithComments(wranglerJsoncPath, config); + return writeJSONWithComments(wranglerJsoncPath, config, replacer); }; export const addVscodeConfig = (ctx: C3Context) => { @@ -193,16 +233,26 @@ export const addVscodeConfig = (ctx: C3Context) => { }); }; +/** + * Gets the compatibility date to use from the wrangler config. + * + * If the compatibility date is missing or invalid, it sets it to the latest workerd date. + * + * @param config Wrangler config + * @returns The compatibility date to use in the form "YYYY-MM-DD" + */ async function getCompatibilityDate>( config: T, -) { +): Promise { const validCompatDateRe = /^\d{4}-\d{2}-\d{2}$/m; + const dateFromConfig = config["compatibility_date"]; if ( - typeof config["compatibility_date"] === "string" && - config["compatibility_date"].match(validCompatDateRe) + typeof dateFromConfig === "string" && + dateFromConfig.match(validCompatDateRe) ) { // If the compat date is already a valid one, leave it since it may be there for a specific compat reason - return config["compatibility_date"]; + return dateFromConfig; } + // If the compat date is missing or invalid, set it to the latest workerd date return await getWorkerdCompatibilityDate(); } From 878dcfad9fab3afa402c45b05140ea6a7cbe145c Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 4 Dec 2025 16:52:17 +0100 Subject: [PATCH 2/7] fixup! use the format --- .changeset/brave-lights-run.md | 4 +++- .../create-cloudflare/src/wrangler/__tests__/config.test.ts | 4 ++-- packages/create-cloudflare/src/wrangler/config.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.changeset/brave-lights-run.md b/.changeset/brave-lights-run.md index c923ad3f80af..794213427cab 100644 --- a/.changeset/brave-lights-run.md +++ b/.changeset/brave-lights-run.md @@ -2,4 +2,6 @@ "create-cloudflare": minor --- -Add template substitution +Add template string substitution in wrangler config files. + +The value `""` will be replaced by the project name when wrangler create a config file (in all of the toml, json, and jsonc formats). diff --git a/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts b/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts index 67a7499922f3..0140588d5c69 100644 --- a/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts +++ b/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts @@ -37,7 +37,7 @@ describe("update wrangler config", () => { `compatibility_date = ""`, `[[services]]`, `binding = "SELF_SERVICE"`, - `service = "__WORKER_NAME__"`, + `service = ""`, ].join("\n"); vi.mocked(readFile).mockReturnValue(toml); @@ -105,7 +105,7 @@ describe("update wrangler config", () => { services: [ { binding: "SELF_SERVICE", - service: "__WORKER_NAME__", + service: "", }, ], }); diff --git a/packages/create-cloudflare/src/wrangler/config.ts b/packages/create-cloudflare/src/wrangler/config.ts index 46d17144ba8a..aa1131254174 100644 --- a/packages/create-cloudflare/src/wrangler/config.ts +++ b/packages/create-cloudflare/src/wrangler/config.ts @@ -21,7 +21,7 @@ import type { C3Context } from "types"; * - enabling observability * - adding comments with links to documentation for common configuration options * - substituting placeholders with actual values - * - `__WORKER_NAME__` with the project name + * - `` with the project name * * If both `wrangler.toml` and `wrangler.json`/`wrangler.jsonc` are present, only * the `wrangler.json`/`wrangler.jsonc` file will be updated. @@ -29,7 +29,7 @@ import type { C3Context } from "types"; export const updateWranglerConfig = async (ctx: C3Context) => { // Placeholders to replace in the wrangler config files const substitutions: Record = { - __WORKER_NAME__: ctx.project.name, + "": ctx.project.name, }; if (wranglerJsonExists(ctx)) { From fad6698797d1291d7db010c0eaf1389dbdd6a0c3 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 5 Dec 2025 09:58:34 +0100 Subject: [PATCH 3/7] fixup! reviver, , renaming --- .changeset/brave-lights-run.md | 5 +- packages/create-cloudflare/src/deploy.ts | 8 +- .../src/helpers/__tests__/json.test.ts | 43 +++++- .../create-cloudflare/src/helpers/json.ts | 9 +- packages/create-cloudflare/src/workers.ts | 8 +- .../src/wrangler/__tests__/config.test.ts | 141 +++++++++++++++++- .../create-cloudflare/src/wrangler/config.ts | 83 ++++++----- 7 files changed, 242 insertions(+), 55 deletions(-) diff --git a/.changeset/brave-lights-run.md b/.changeset/brave-lights-run.md index 794213427cab..fa8dd40514cb 100644 --- a/.changeset/brave-lights-run.md +++ b/.changeset/brave-lights-run.md @@ -4,4 +4,7 @@ Add template string substitution in wrangler config files. -The value `""` will be replaced by the project name when wrangler create a config file (in all of the toml, json, and jsonc formats). +When c3 updates the config file: + +- The value `""` is replaced with the worker name. +- The value `""` is replaced with the latest worked compatibility date. diff --git a/packages/create-cloudflare/src/deploy.ts b/packages/create-cloudflare/src/deploy.ts index c27dc7fb80f4..7221dc2b0e17 100644 --- a/packages/create-cloudflare/src/deploy.ts +++ b/packages/create-cloudflare/src/deploy.ts @@ -13,9 +13,9 @@ import TOML from "smol-toml"; import { isInsideGitRepo } from "./git"; import { chooseAccount, wranglerLogin } from "./wrangler/accounts"; import { - readWranglerJson, + readWranglerJsonOrJsonc, readWranglerToml, - wranglerJsonExists, + wranglerJsonOrJsoncExists, } from "./wrangler/config"; import type { C3Context } from "types"; @@ -81,8 +81,8 @@ const isDeployable = async (ctx: C3Context) => { }; const readWranglerConfig = (ctx: C3Context) => { - if (wranglerJsonExists(ctx)) { - return readWranglerJson(ctx); + if (wranglerJsonOrJsoncExists(ctx)) { + return readWranglerJsonOrJsonc(ctx); } const wranglerTomlStr = readWranglerToml(ctx); return TOML.parse(wranglerTomlStr.replace(/\r\n/g, "\n")); diff --git a/packages/create-cloudflare/src/helpers/__tests__/json.test.ts b/packages/create-cloudflare/src/helpers/__tests__/json.test.ts index eaac3a544298..7073dffe85b7 100644 --- a/packages/create-cloudflare/src/helpers/__tests__/json.test.ts +++ b/packages/create-cloudflare/src/helpers/__tests__/json.test.ts @@ -30,6 +30,35 @@ describe("json helpers", () => { expect(mockReadFile).toHaveBeenCalledWith("/path/to/file.json"); expect(result).toEqual({ name: "test" }); }); + + test("using a reviver function", () => { + mockReadFile.mockReturnValue( + JSON.stringify({ + name: "test", + rootValue: "", + keep: "", + nested: { + value: "", + list: [[""], ""], + }, + }), + ); + + const result = readJSONWithComments( + "/path/to/file.json", + (_key, value) => (value === "" ? "REPLACED" : value), + ); + expect(mockReadFile).toHaveBeenCalledWith("/path/to/file.json"); + expect(result).toEqual({ + name: "test", + rootValue: "REPLACED", + keep: "", + nested: { + value: "REPLACED", + list: [["REPLACED"], ""], + }, + }); + }); }); describe("writeJSONWithComments", () => { @@ -55,18 +84,18 @@ describe("json helpers", () => { mockReadFile.mockReturnValue( JSON.stringify({ name: "test", - rootValue: "_REPLACE_ME_", - keep: "DO_NOT_REPLACE_ME_", + rootValue: "", + keep: "", nested: { - value: "_REPLACE_ME_", - list: [["_REPLACE_ME_"], "DO_NOT_REPLACE_ME_"], + value: "", + list: [[""], ""], }, }), ); const result = readJSONWithComments("/path/to/file.json"); writeJSONWithComments("/path/to/file.json", result, (_key, value) => - value === "_REPLACE_ME_" ? "REPLACED" : value, + value === "" ? "REPLACED" : value, ); expect(mockWriteFile.mock.calls[0][0]).toMatchInlineSnapshot( `"/path/to/file.json"`, @@ -75,14 +104,14 @@ describe("json helpers", () => { "{ "name": "test", "rootValue": "REPLACED", - "keep": "DO_NOT_REPLACE_ME_", + "keep": "", "nested": { "value": "REPLACED", "list": [ [ "REPLACED" ], - "DO_NOT_REPLACE_ME_" + "" ] } }" diff --git a/packages/create-cloudflare/src/helpers/json.ts b/packages/create-cloudflare/src/helpers/json.ts index 217ac1977093..d674c89695d4 100644 --- a/packages/create-cloudflare/src/helpers/json.ts +++ b/packages/create-cloudflare/src/helpers/json.ts @@ -5,16 +5,21 @@ import type { CommentObject, CommentSymbol, CommentToken, + Reviver, } from "comment-json"; /** * Reads a JSON file and preserves comments. * @param jsonFilePath - The path to the JSON file. + * @param reviver A function that transforms the results. This function is called for each member of the object. * @returns The parsed JSON object with comments. */ -export function readJSONWithComments(jsonFilePath: string): CommentObject { +export function readJSONWithComments( + jsonFilePath: string, + reviver?: Reviver | null, +): CommentObject { const jsonString = readFile(jsonFilePath); - const jsonObject = parse(jsonString) as unknown as CommentObject; + const jsonObject = parse(jsonString, reviver) as unknown as CommentObject; return jsonObject; } diff --git a/packages/create-cloudflare/src/workers.ts b/packages/create-cloudflare/src/workers.ts index 9e0e389e8937..a09cc5ae593d 100644 --- a/packages/create-cloudflare/src/workers.ts +++ b/packages/create-cloudflare/src/workers.ts @@ -10,9 +10,9 @@ import { installPackages } from "helpers/packages"; import * as jsonc from "jsonc-parser"; import TOML from "smol-toml"; import { - readWranglerJson, + readWranglerJsonOrJsonc, readWranglerToml, - wranglerJsonExists, + wranglerJsonOrJsoncExists, wranglerTomlExists, } from "./wrangler/config"; import type { C3Context, PackageJson } from "types"; @@ -62,8 +62,8 @@ async function generateWorkersTypes(ctx: C3Context, npm: string) { const maybeInstallNodeTypes = async (ctx: C3Context, npm: string) => { let parsedConfig: Record = {}; - if (wranglerJsonExists(ctx)) { - parsedConfig = readWranglerJson(ctx); + if (wranglerJsonOrJsoncExists(ctx)) { + parsedConfig = readWranglerJsonOrJsonc(ctx); } else if (wranglerTomlExists(ctx)) { const wranglerTomlStr = readWranglerToml(ctx); parsedConfig = TOML.parse(wranglerTomlStr); diff --git a/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts b/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts index 0140588d5c69..94fea2067a54 100644 --- a/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts +++ b/packages/create-cloudflare/src/wrangler/__tests__/config.test.ts @@ -30,7 +30,7 @@ describe("update wrangler config", () => { ); }); - test("placeholder replacement", async () => { + test("placeholder replacement ``", async () => { const toml = [ `name = ""`, `main = "src/index.ts"`, @@ -94,7 +94,71 @@ describe("update wrangler config", () => { `); }); - test("placeholder replacement (json)", async () => { + test("placeholder replacement", async () => { + const toml = [ + `name = ""`, + `main = "src/index.ts"`, + `compatibility_date = ""`, + `[[services]]`, + `binding = "SELF_SERVICE"`, + `service = ""`, + ].join("\n"); + vi.mocked(readFile).mockReturnValue(toml); + + await updateWranglerConfig(ctx); + + const newToml = vi.mocked(writeFile).mock.calls[0][1]; + expect(newToml).toMatchInlineSnapshot(` + "#:schema node_modules/wrangler/config-schema.json + # For more details on how to configure Wrangler, refer to: + # https://developers.cloudflare.com/workers/wrangler/configuration/ + name = "test" + main = "src/index.ts" + compatibility_date = "2024-01-17" + + [[services]] + binding = "SELF_SERVICE" + service = "test" + + [observability] + enabled = true + + # Smart Placement + # Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement + # [placement] + # mode = "smart" + + ### + # Bindings + # Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including + # databases, object storage, AI inference, real-time communication and more. + # https://developers.cloudflare.com/workers/runtime-apis/bindings/ + ### + + # Environment Variables + # https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables + # [vars] + # MY_VARIABLE = "production_value" + + # Note: Use secrets to store sensitive data. + # https://developers.cloudflare.com/workers/configuration/secrets/ + + # Static Assets + # https://developers.cloudflare.com/workers/static-assets/binding/ + # [assets] + # directory = "./public/" + # binding = "ASSETS" + + # Service Bindings (communicate between multiple Workers) + # https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings + # [[services]] + # binding = "MY_SERVICE" + # service = "my-service" + " + `); + }); + + test("placeholder replacement `` (json)", async () => { vi.mocked(existsSync).mockImplementationOnce((f) => (f as string).endsWith(".json"), ); @@ -167,6 +231,79 @@ describe("update wrangler config", () => { `); }); + test("placeholder replacement (json)", async () => { + vi.mocked(existsSync).mockImplementationOnce((f) => + (f as string).endsWith(".json"), + ); + const json = JSON.stringify({ + name: "", + main: "src/index.ts", + compatibility_date: "", + services: [ + { + binding: "SELF_SERVICE", + service: "", + }, + ], + }); + vi.mocked(readFile).mockReturnValueOnce(json); + + await updateWranglerConfig(ctx); + + const newConfig = vi.mocked(writeFile).mock.calls[0][1]; + expect(newConfig).toMatchInlineSnapshot(` + "/** + * For more details on how to configure Wrangler, refer to: + * https://developers.cloudflare.com/workers/wrangler/configuration/ + */ + { + "$schema": "node_modules/wrangler/config-schema.json", + "name": "test", + "main": "src/index.ts", + "compatibility_date": "2024-01-17", + "services": [ + { + "binding": "SELF_SERVICE", + "service": "test" + } + ], + "observability": { + "enabled": true + } + /** + * Smart Placement + * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement + */ + // "placement": { "mode": "smart" } + /** + * Bindings + * Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including + * databases, object storage, AI inference, real-time communication and more. + * https://developers.cloudflare.com/workers/runtime-apis/bindings/ + */ + /** + * Environment Variables + * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables + */ + // "vars": { "MY_VARIABLE": "production_value" } + /** + * Note: Use secrets to store sensitive data. + * https://developers.cloudflare.com/workers/configuration/secrets/ + */ + /** + * Static Assets + * https://developers.cloudflare.com/workers/static-assets/binding/ + */ + // "assets": { "directory": "./public/", "binding": "ASSETS" } + /** + * Service Bindings (communicate between multiple Workers) + * https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings + */ + // "services": [{ "binding": "MY_SERVICE", "service": "my-service" }] + }" + `); + }); + test("string literal replacement", async () => { const toml = [`name = "my-cool-worker"`, `main = "src/index.ts"`].join( "\n", diff --git a/packages/create-cloudflare/src/wrangler/config.ts b/packages/create-cloudflare/src/wrangler/config.ts index aa1131254174..208de8dd1fe2 100644 --- a/packages/create-cloudflare/src/wrangler/config.ts +++ b/packages/create-cloudflare/src/wrangler/config.ts @@ -10,7 +10,7 @@ import { writeJSONWithComments, } from "helpers/json"; import TOML from "smol-toml"; -import type { CommentObject } from "comment-json"; +import type { CommentObject, Reviver } from "comment-json"; import type { C3Context } from "types"; /** @@ -22,6 +22,7 @@ import type { C3Context } from "types"; * - adding comments with links to documentation for common configuration options * - substituting placeholders with actual values * - `` with the project name + * - `` with the max compatibility date of the installed worked * * If both `wrangler.toml` and `wrangler.json`/`wrangler.jsonc` are present, only * the `wrangler.json`/`wrangler.jsonc` file will be updated. @@ -30,10 +31,15 @@ export const updateWranglerConfig = async (ctx: C3Context) => { // Placeholders to replace in the wrangler config files const substitutions: Record = { "": ctx.project.name, + "": await getWorkerdCompatibilityDate(), }; - if (wranglerJsonExists(ctx)) { - let wranglerJson = readWranglerJson(ctx); + if (wranglerJsonOrJsoncExists(ctx)) { + let wranglerJson = readWranglerJsonOrJsonc(ctx, (_key, value) => + typeof value === "string" && value in substitutions + ? substitutions[value] + : value, + ); // Put the schema at the top of the file wranglerJson = insertJSONProperty( @@ -46,7 +52,7 @@ export const updateWranglerConfig = async (ctx: C3Context) => { wranglerJson = appendJSONProperty( wranglerJson, "compatibility_date", - await getCompatibilityDate(wranglerJson), + await getCompatibilityDate(wranglerJson.compatibility_date), ); wranglerJson = appendJSONProperty(wranglerJson, "observability", { enabled: true, @@ -84,30 +90,27 @@ export const updateWranglerConfig = async (ctx: C3Context) => { }, ]); - writeWranglerJson(ctx, wranglerJson, (_key, value) => - typeof value === "string" && value in substitutions - ? substitutions[value] - : value, - ); + writeWranglerJsonOrJsonc(ctx, wranglerJson); addVscodeConfig(ctx); } else if (wranglerTomlExists(ctx)) { - const wranglerTomlStr = readWranglerToml(ctx); - const parsed = TOML.parse(wranglerTomlStr); - parsed.name = ctx.project.name; - parsed["compatibility_date"] = await getCompatibilityDate(parsed); - parsed["observability"] ??= { enabled: true }; - - let strToml = TOML.stringify(parsed); + let strToml = readWranglerToml(ctx); for (const [key, value] of Object.entries(substitutions)) { strToml = strToml.replaceAll(key, value); } + const wranglerToml = TOML.parse(strToml); + wranglerToml.name = ctx.project.name; + wranglerToml.compatibility_date = await getCompatibilityDate( + wranglerToml.compatibility_date, + ); + wranglerToml.observability ??= { enabled: true }; + writeWranglerToml( ctx, `#:schema node_modules/wrangler/config-schema.json # For more details on how to configure Wrangler, refer to:\n# https://developers.cloudflare.com/workers/wrangler/configuration/ -${strToml} +${TOML.stringify(wranglerToml)} # Smart Placement # Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement # [placement] @@ -161,8 +164,8 @@ export const wranglerTomlExists = (ctx: C3Context) => { return existsSync(wranglerTomlPath); }; -/** Checks for wrangler.json and wrangler.jsonc */ -export const wranglerJsonExists = (ctx: C3Context) => { +/** Checks for an existing `wrangler.json` or `wrangler.jsonc` */ +export const wranglerJsonOrJsoncExists = (ctx: C3Context) => { const wranglerJsonPath = getWranglerJsonPath(ctx); const wranglerJsoncPath = getWranglerJsoncPath(ctx); return existsSync(wranglerJsonPath) || existsSync(wranglerJsoncPath); @@ -173,13 +176,25 @@ export const readWranglerToml = (ctx: C3Context) => { return readFile(wranglerTomlPath); }; -export const readWranglerJson = (ctx: C3Context) => { +/** + * Reads the JSON configuration file for this project. + * + * If both `wrangler.json` and `wrangler.jsonc` are present, `wrangler.json` will be read. + * + * @param ctx The C3 context. + * @param reviver A function that transforms the results. This function is called for each member of the object. + * @returns The parsed JSON object with comments. + */ +export const readWranglerJsonOrJsonc = ( + ctx: C3Context, + reviver?: Reviver | null, +): CommentObject => { const wranglerJsonPath = getWranglerJsonPath(ctx); if (existsSync(wranglerJsonPath)) { - return readJSONWithComments(wranglerJsonPath); + return readJSONWithComments(wranglerJsonPath, reviver); } const wranglerJsoncPath = getWranglerJsoncPath(ctx); - return readJSONWithComments(wranglerJsoncPath); + return readJSONWithComments(wranglerJsoncPath, reviver); }; export const writeWranglerToml = (ctx: C3Context, contents: string) => { @@ -199,7 +214,7 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => { * an array of strings and numbers that acts as an approved list for selecting * the object properties that will be stringified. */ -export const writeWranglerJson = ( +export const writeWranglerJsonOrJsonc = ( ctx: C3Context, config: CommentObject, replacer?: @@ -234,25 +249,23 @@ export const addVscodeConfig = (ctx: C3Context) => { }; /** - * Gets the compatibility date to use from the wrangler config. + * Gets the compatibility date to use. * - * If the compatibility date is missing or invalid, it sets it to the latest workerd date. + * If the tentative date is valid, it is returned. Otherwise the latest workerd date is used. * - * @param config Wrangler config + * @param tentativeDate A tentative compatibility date, usually from wrangler config. * @returns The compatibility date to use in the form "YYYY-MM-DD" */ -async function getCompatibilityDate>( - config: T, -): Promise { +async function getCompatibilityDate(tentativeDate: unknown): Promise { const validCompatDateRe = /^\d{4}-\d{2}-\d{2}$/m; - const dateFromConfig = config["compatibility_date"]; if ( - typeof dateFromConfig === "string" && - dateFromConfig.match(validCompatDateRe) + typeof tentativeDate === "string" && + tentativeDate.match(validCompatDateRe) ) { - // If the compat date is already a valid one, leave it since it may be there for a specific compat reason - return dateFromConfig; + // Use the tentative date when it is valid. + // It may be there for a specific compat reason + return tentativeDate; } - // If the compat date is missing or invalid, set it to the latest workerd date + // Fallback to the latest workerd date return await getWorkerdCompatibilityDate(); } From 86a39852cec060cf087b95083b36e136abb854fd Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 8 Dec 2025 11:45:43 +0100 Subject: [PATCH 4/7] Update wrangler config templates to use `` and `` --- .../templates/analog/templates/wrangler.jsonc | 4 ++-- .../templates/angular/pages/templates/wrangler.jsonc | 4 ++-- .../templates/angular/workers/templates/wrangler.jsonc | 4 ++-- .../templates/astro/pages/templates/js/wrangler.jsonc | 4 ++-- .../templates/astro/pages/templates/ts/wrangler.jsonc | 4 ++-- .../templates/astro/workers/templates/js/wrangler.jsonc | 4 ++-- .../templates/astro/workers/templates/ts/wrangler.jsonc | 4 ++-- packages/create-cloudflare/templates/common/js/wrangler.jsonc | 4 ++-- packages/create-cloudflare/templates/common/ts/wrangler.jsonc | 4 ++-- .../templates/docusaurus/workers/templates/wrangler.jsonc | 4 ++-- .../templates/gatsby/workers/templates/wrangler.jsonc | 4 ++-- .../hello-world-assets-only/templates/wrangler.jsonc | 4 ++-- .../hello-world-durable-object-with-assets/js/wrangler.jsonc | 4 ++-- .../hello-world-durable-object-with-assets/py/wrangler.jsonc | 4 ++-- .../hello-world-durable-object-with-assets/ts/wrangler.jsonc | 4 ++-- .../templates/hello-world-durable-object/js/wrangler.jsonc | 4 ++-- .../templates/hello-world-durable-object/py/wrangler.jsonc | 4 ++-- .../templates/hello-world-durable-object/ts/wrangler.jsonc | 4 ++-- .../templates/hello-world-with-assets/js/wrangler.jsonc | 4 ++-- .../templates/hello-world-with-assets/py/wrangler.jsonc | 4 ++-- .../templates/hello-world-with-assets/ts/wrangler.jsonc | 4 ++-- .../templates/hello-world-workflows/js/wrangler.jsonc | 4 ++-- .../templates/hello-world-workflows/ts/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/hello-world/js/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/hello-world/py/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/hello-world/ts/wrangler.jsonc | 4 ++-- .../templates/hono/pages/templates/wrangler.jsonc | 4 ++-- .../templates/hono/workers/templates/wrangler.jsonc | 4 ++-- .../templates/nuxt/pages/templates/wrangler.jsonc | 4 ++-- .../templates/nuxt/workers/templates/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/openapi/ts/wrangler.jsonc | 4 ++-- packages/create-cloudflare/templates/queues/js/wrangler.jsonc | 4 ++-- packages/create-cloudflare/templates/queues/ts/wrangler.jsonc | 4 ++-- .../templates/qwik/pages/templates/wrangler.jsonc | 4 ++-- .../templates/qwik/workers/templates/wrangler.jsonc | 4 ++-- .../templates/react/pages/templates/wrangler.jsonc | 4 ++-- .../templates/react/workers/js/wrangler.jsonc | 4 ++-- .../templates/react/workers/ts/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/scheduled/js/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/scheduled/ts/wrangler.jsonc | 4 ++-- .../templates/solid/templates/wrangler.jsonc | 4 ++-- .../templates/svelte/pages/templates/wrangler.jsonc | 4 ++-- .../templates/svelte/workers/templates/wrangler.jsonc | 4 ++-- .../templates/vue/pages/templates/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/vue/workers/js/wrangler.jsonc | 4 ++-- .../create-cloudflare/templates/vue/workers/ts/wrangler.jsonc | 4 ++-- packages/create-cloudflare/templates/waku/wrangler.jsonc | 4 ++-- 47 files changed, 94 insertions(+), 94 deletions(-) diff --git a/packages/create-cloudflare/templates/analog/templates/wrangler.jsonc b/packages/create-cloudflare/templates/analog/templates/wrangler.jsonc index 5e096c4f0c0a..a572f1252473 100644 --- a/packages/create-cloudflare/templates/analog/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/analog/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "main": "dist/analog/server/index.mjs", "assets": { "binding": "ASSETS", diff --git a/packages/create-cloudflare/templates/angular/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/angular/pages/templates/wrangler.jsonc index 61c73b85f831..0c4c1f831912 100644 --- a/packages/create-cloudflare/templates/angular/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/angular/pages/templates/wrangler.jsonc @@ -1,5 +1,5 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "pages_build_output_dir": "./dist/cloudflare" } diff --git a/packages/create-cloudflare/templates/angular/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/angular/workers/templates/wrangler.jsonc index 7d63ed5c8727..6e0be53c5eb5 100644 --- a/packages/create-cloudflare/templates/angular/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/angular/workers/templates/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "./dist/server/server.mjs", - "compatibility_date": "", + "compatibility_date": "", "assets": { "binding": "ASSETS", "directory": "./dist/browser" diff --git a/packages/create-cloudflare/templates/astro/pages/templates/js/wrangler.jsonc b/packages/create-cloudflare/templates/astro/pages/templates/js/wrangler.jsonc index f1dde41a1b26..a41c367db016 100644 --- a/packages/create-cloudflare/templates/astro/pages/templates/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/astro/pages/templates/js/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat" ], diff --git a/packages/create-cloudflare/templates/astro/pages/templates/ts/wrangler.jsonc b/packages/create-cloudflare/templates/astro/pages/templates/ts/wrangler.jsonc index f1dde41a1b26..a41c367db016 100644 --- a/packages/create-cloudflare/templates/astro/pages/templates/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/astro/pages/templates/ts/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat" ], diff --git a/packages/create-cloudflare/templates/astro/workers/templates/js/wrangler.jsonc b/packages/create-cloudflare/templates/astro/workers/templates/js/wrangler.jsonc index 06d7824a5d42..dc0adf3e4b7f 100644 --- a/packages/create-cloudflare/templates/astro/workers/templates/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/astro/workers/templates/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "./dist/_worker.js/index.js", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" diff --git a/packages/create-cloudflare/templates/astro/workers/templates/ts/wrangler.jsonc b/packages/create-cloudflare/templates/astro/workers/templates/ts/wrangler.jsonc index fbb8908177aa..3915689a19b6 100644 --- a/packages/create-cloudflare/templates/astro/workers/templates/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/astro/workers/templates/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "./dist/_worker.js/index.js", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" diff --git a/packages/create-cloudflare/templates/common/js/wrangler.jsonc b/packages/create-cloudflare/templates/common/js/wrangler.jsonc index 183546064027..8d5311a8e790 100644 --- a/packages/create-cloudflare/templates/common/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/common/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true } diff --git a/packages/create-cloudflare/templates/common/ts/wrangler.jsonc b/packages/create-cloudflare/templates/common/ts/wrangler.jsonc index 0d4b76dc198a..d2e9d43fba62 100644 --- a/packages/create-cloudflare/templates/common/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/common/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true } diff --git a/packages/create-cloudflare/templates/docusaurus/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/docusaurus/workers/templates/wrangler.jsonc index ed60402d8ea5..6a0c4df252f2 100644 --- a/packages/create-cloudflare/templates/docusaurus/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/docusaurus/workers/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "assets": { "directory": "./build" }, diff --git a/packages/create-cloudflare/templates/gatsby/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/gatsby/workers/templates/wrangler.jsonc index 9a612e6a142b..0cb11da5cc72 100644 --- a/packages/create-cloudflare/templates/gatsby/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/gatsby/workers/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "assets": { "directory": "./public" }, diff --git a/packages/create-cloudflare/templates/hello-world-assets-only/templates/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-assets-only/templates/wrangler.jsonc index e00d6246e7c1..423c71ae3c31 100644 --- a/packages/create-cloudflare/templates/hello-world-assets-only/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-assets-only/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "assets": { // The path to the directory containing the `index.html` file to be served at `/` "directory": "./public" diff --git a/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/js/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/js/wrangler.jsonc index 04156ad845be..d6d8539ce401 100644 --- a/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "assets": { // The path to the directory containing the `index.html` file to be served at `/` "directory": "./public" diff --git a/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/py/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/py/wrangler.jsonc index 2ae422d70d23..baf11b798da4 100644 --- a/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/py/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/py/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/entry.py", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "python_workers" ], diff --git a/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/ts/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/ts/wrangler.jsonc index fb9a4786beed..cab52ece6401 100644 --- a/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-durable-object-with-assets/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "migrations": [ { "new_sqlite_classes": [ diff --git a/packages/create-cloudflare/templates/hello-world-durable-object/js/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-durable-object/js/wrangler.jsonc index 28cc9da904e6..983fd66d8abe 100644 --- a/packages/create-cloudflare/templates/hello-world-durable-object/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-durable-object/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "migrations": [ { "new_sqlite_classes": [ diff --git a/packages/create-cloudflare/templates/hello-world-durable-object/py/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-durable-object/py/wrangler.jsonc index cfbc48817ae1..0d01e7035d7c 100644 --- a/packages/create-cloudflare/templates/hello-world-durable-object/py/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-durable-object/py/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/entry.py", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "python_workers" ], diff --git a/packages/create-cloudflare/templates/hello-world-durable-object/ts/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-durable-object/ts/wrangler.jsonc index 22cb802e730e..6761d1c39b9c 100644 --- a/packages/create-cloudflare/templates/hello-world-durable-object/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-durable-object/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "migrations": [ { "new_sqlite_classes": [ diff --git a/packages/create-cloudflare/templates/hello-world-with-assets/js/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-with-assets/js/wrangler.jsonc index b30e31fa55e8..8d0de66af317 100644 --- a/packages/create-cloudflare/templates/hello-world-with-assets/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-with-assets/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" diff --git a/packages/create-cloudflare/templates/hello-world-with-assets/py/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-with-assets/py/wrangler.jsonc index 3d2766834492..d4940a8daef7 100644 --- a/packages/create-cloudflare/templates/hello-world-with-assets/py/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-with-assets/py/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/entry.py", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "python_workers" ], diff --git a/packages/create-cloudflare/templates/hello-world-with-assets/ts/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-with-assets/ts/wrangler.jsonc index 1d200d2c20c8..fffb13d42a37 100644 --- a/packages/create-cloudflare/templates/hello-world-with-assets/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-with-assets/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "global_fetch_strictly_public" ], diff --git a/packages/create-cloudflare/templates/hello-world-workflows/js/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-workflows/js/wrangler.jsonc index 95b33f114eef..5157d9cae44a 100644 --- a/packages/create-cloudflare/templates/hello-world-workflows/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-workflows/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true, diff --git a/packages/create-cloudflare/templates/hello-world-workflows/ts/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world-workflows/ts/wrangler.jsonc index 0c56e6ca9314..97eefc1c489e 100644 --- a/packages/create-cloudflare/templates/hello-world-workflows/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world-workflows/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true, diff --git a/packages/create-cloudflare/templates/hello-world/js/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world/js/wrangler.jsonc index 168bc0dd9a0a..b6ea3641c7ae 100644 --- a/packages/create-cloudflare/templates/hello-world/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true, }, diff --git a/packages/create-cloudflare/templates/hello-world/py/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world/py/wrangler.jsonc index 3ca3e9bcafa1..0d817bdd31d8 100644 --- a/packages/create-cloudflare/templates/hello-world/py/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world/py/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/entry.py", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": ["python_workers"], "observability": { "enabled": true, diff --git a/packages/create-cloudflare/templates/hello-world/ts/wrangler.jsonc b/packages/create-cloudflare/templates/hello-world/ts/wrangler.jsonc index 435aed249941..54163bc5895d 100644 --- a/packages/create-cloudflare/templates/hello-world/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hello-world/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true, }, diff --git a/packages/create-cloudflare/templates/hono/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/hono/pages/templates/wrangler.jsonc index 212954346581..3b246f52ae71 100644 --- a/packages/create-cloudflare/templates/hono/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hono/pages/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "pages_build_output_dir": "./dist", "observability": { "enabled": true diff --git a/packages/create-cloudflare/templates/hono/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/hono/workers/templates/wrangler.jsonc index 5b93fb6f101b..87d62453bc8b 100644 --- a/packages/create-cloudflare/templates/hono/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/hono/workers/templates/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "assets": { "binding": "ASSETS", "directory": "./public" diff --git a/packages/create-cloudflare/templates/nuxt/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/nuxt/pages/templates/wrangler.jsonc index 9c8bf46d4248..4cb3914366cf 100644 --- a/packages/create-cloudflare/templates/nuxt/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/nuxt/pages/templates/wrangler.jsonc @@ -1,5 +1,5 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "pages_build_output_dir": "./dist" } diff --git a/packages/create-cloudflare/templates/nuxt/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/nuxt/workers/templates/wrangler.jsonc index 58d5befda8b1..0de58ae0e526 100644 --- a/packages/create-cloudflare/templates/nuxt/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/nuxt/workers/templates/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "./.output/server/index.mjs", - "compatibility_date": "", + "compatibility_date": "", "assets": { "binding": "ASSETS", "directory": "./.output/public/" diff --git a/packages/create-cloudflare/templates/openapi/ts/wrangler.jsonc b/packages/create-cloudflare/templates/openapi/ts/wrangler.jsonc index 0d4b76dc198a..d2e9d43fba62 100644 --- a/packages/create-cloudflare/templates/openapi/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/openapi/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true } diff --git a/packages/create-cloudflare/templates/queues/js/wrangler.jsonc b/packages/create-cloudflare/templates/queues/js/wrangler.jsonc index d58eba397a9d..c4575ff26ef5 100644 --- a/packages/create-cloudflare/templates/queues/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/queues/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true }, diff --git a/packages/create-cloudflare/templates/queues/ts/wrangler.jsonc b/packages/create-cloudflare/templates/queues/ts/wrangler.jsonc index 04e682d14c83..5316d99cc586 100644 --- a/packages/create-cloudflare/templates/queues/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/queues/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true }, diff --git a/packages/create-cloudflare/templates/qwik/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/qwik/pages/templates/wrangler.jsonc index f1dde41a1b26..a41c367db016 100644 --- a/packages/create-cloudflare/templates/qwik/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/qwik/pages/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat" ], diff --git a/packages/create-cloudflare/templates/qwik/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/qwik/workers/templates/wrangler.jsonc index 3db4f915b7f8..2f84fe858c66 100644 --- a/packages/create-cloudflare/templates/qwik/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/qwik/workers/templates/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "./dist/_worker.js", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat", "global_fetch_strictly_public" diff --git a/packages/create-cloudflare/templates/react/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/react/pages/templates/wrangler.jsonc index 9fb9c7262e8b..76834d0a9929 100644 --- a/packages/create-cloudflare/templates/react/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/react/pages/templates/wrangler.jsonc @@ -1,5 +1,5 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "pages_build_output_dir": "./dist" } diff --git a/packages/create-cloudflare/templates/react/workers/js/wrangler.jsonc b/packages/create-cloudflare/templates/react/workers/js/wrangler.jsonc index fca2a5d866e7..c8e58db8259d 100644 --- a/packages/create-cloudflare/templates/react/workers/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/react/workers/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "worker/index.js", - "compatibility_date": "", + "compatibility_date": "", "assets": { "not_found_handling": "single-page-application" }, "observability": { "enabled": true diff --git a/packages/create-cloudflare/templates/react/workers/ts/wrangler.jsonc b/packages/create-cloudflare/templates/react/workers/ts/wrangler.jsonc index f3758be5594e..63adfcffd026 100644 --- a/packages/create-cloudflare/templates/react/workers/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/react/workers/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "worker/index.ts", - "compatibility_date": "", + "compatibility_date": "", "assets": { "not_found_handling": "single-page-application" }, "observability": { "enabled": true diff --git a/packages/create-cloudflare/templates/scheduled/js/wrangler.jsonc b/packages/create-cloudflare/templates/scheduled/js/wrangler.jsonc index 246b0a6e9ad6..589cd5d69625 100644 --- a/packages/create-cloudflare/templates/scheduled/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/scheduled/js/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.js", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true }, diff --git a/packages/create-cloudflare/templates/scheduled/ts/wrangler.jsonc b/packages/create-cloudflare/templates/scheduled/ts/wrangler.jsonc index f551a128e046..01cb2f28b2a4 100644 --- a/packages/create-cloudflare/templates/scheduled/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/scheduled/ts/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "src/index.ts", - "compatibility_date": "", + "compatibility_date": "", "observability": { "enabled": true }, diff --git a/packages/create-cloudflare/templates/solid/templates/wrangler.jsonc b/packages/create-cloudflare/templates/solid/templates/wrangler.jsonc index df0acc35cd27..43ba70955a16 100644 --- a/packages/create-cloudflare/templates/solid/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/solid/templates/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": "./.output/server/index.mjs", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": [ "nodejs_compat" ], diff --git a/packages/create-cloudflare/templates/svelte/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/svelte/pages/templates/wrangler.jsonc index 71712eb4ae99..916e26cbe02c 100644 --- a/packages/create-cloudflare/templates/svelte/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/svelte/pages/templates/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "compatibility_flags": ["nodejs_als"], "pages_build_output_dir": ".svelte-kit/cloudflare" } diff --git a/packages/create-cloudflare/templates/svelte/workers/templates/wrangler.jsonc b/packages/create-cloudflare/templates/svelte/workers/templates/wrangler.jsonc index f406a65b0cf7..e7dec61662c2 100644 --- a/packages/create-cloudflare/templates/svelte/workers/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/svelte/workers/templates/wrangler.jsonc @@ -1,7 +1,7 @@ { - "name": "", + "name": "", "main": ".svelte-kit/cloudflare/_worker.js", - "compatibility_date": "", + "compatibility_date": "", "compatibility_flags": ["nodejs_als"], "assets": { "binding": "ASSETS", diff --git a/packages/create-cloudflare/templates/vue/pages/templates/wrangler.jsonc b/packages/create-cloudflare/templates/vue/pages/templates/wrangler.jsonc index 9fb9c7262e8b..76834d0a9929 100644 --- a/packages/create-cloudflare/templates/vue/pages/templates/wrangler.jsonc +++ b/packages/create-cloudflare/templates/vue/pages/templates/wrangler.jsonc @@ -1,5 +1,5 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "pages_build_output_dir": "./dist" } diff --git a/packages/create-cloudflare/templates/vue/workers/js/wrangler.jsonc b/packages/create-cloudflare/templates/vue/workers/js/wrangler.jsonc index 7d8cbd29bbc5..c4c1c3aef739 100644 --- a/packages/create-cloudflare/templates/vue/workers/js/wrangler.jsonc +++ b/packages/create-cloudflare/templates/vue/workers/js/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "main": "server/index.js", "assets": { "not_found_handling": "single-page-application", diff --git a/packages/create-cloudflare/templates/vue/workers/ts/wrangler.jsonc b/packages/create-cloudflare/templates/vue/workers/ts/wrangler.jsonc index 2d6f58e49895..a6841d2cc4a8 100644 --- a/packages/create-cloudflare/templates/vue/workers/ts/wrangler.jsonc +++ b/packages/create-cloudflare/templates/vue/workers/ts/wrangler.jsonc @@ -1,6 +1,6 @@ { - "name": "", - "compatibility_date": "", + "name": "", + "compatibility_date": "", "main": "server/index.ts", "assets": { "not_found_handling": "single-page-application", diff --git a/packages/create-cloudflare/templates/waku/wrangler.jsonc b/packages/create-cloudflare/templates/waku/wrangler.jsonc index 9337caf0497a..7db66f48f522 100644 --- a/packages/create-cloudflare/templates/waku/wrangler.jsonc +++ b/packages/create-cloudflare/templates/waku/wrangler.jsonc @@ -1,8 +1,8 @@ { - "name": "", + "name": "", "main": "./dist/worker/serve-cloudflare.js", // https://developers.cloudflare.com/workers/platform/compatibility-dates - "compatibility_date": "", + "compatibility_date": "", // nodejs_als is required for Waku server-side request context // It can be removed if only building static pages "compatibility_flags": ["nodejs_als"], From 7413156f06e939f869c4cb4c5d0b4f2d2f8b0ae3 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 9 Dec 2025 11:56:12 +0100 Subject: [PATCH 5/7] fixup! address feedback --- .changeset/brave-lights-run.md | 2 +- .../src/helpers/__tests__/json.test.ts | 38 ------------------- .../create-cloudflare/src/helpers/json.ts | 11 +----- .../create-cloudflare/src/wrangler/config.ts | 17 ++------- 4 files changed, 7 insertions(+), 61 deletions(-) diff --git a/.changeset/brave-lights-run.md b/.changeset/brave-lights-run.md index fa8dd40514cb..e3a3a46237a3 100644 --- a/.changeset/brave-lights-run.md +++ b/.changeset/brave-lights-run.md @@ -2,7 +2,7 @@ "create-cloudflare": minor --- -Add template string substitution in wrangler config files. +Add placeholder substitution in wrangler config files. When c3 updates the config file: diff --git a/packages/create-cloudflare/src/helpers/__tests__/json.test.ts b/packages/create-cloudflare/src/helpers/__tests__/json.test.ts index 7073dffe85b7..41e4b0bd48f5 100644 --- a/packages/create-cloudflare/src/helpers/__tests__/json.test.ts +++ b/packages/create-cloudflare/src/helpers/__tests__/json.test.ts @@ -79,44 +79,6 @@ describe("json helpers", () => { // post-comment" `); }); - - test("using a function replacer", () => { - mockReadFile.mockReturnValue( - JSON.stringify({ - name: "test", - rootValue: "", - keep: "", - nested: { - value: "", - list: [[""], ""], - }, - }), - ); - - const result = readJSONWithComments("/path/to/file.json"); - writeJSONWithComments("/path/to/file.json", result, (_key, value) => - value === "" ? "REPLACED" : value, - ); - expect(mockWriteFile.mock.calls[0][0]).toMatchInlineSnapshot( - `"/path/to/file.json"`, - ); - expect(mockWriteFile.mock.calls[0][1]).toMatchInlineSnapshot(` - "{ - "name": "test", - "rootValue": "REPLACED", - "keep": "", - "nested": { - "value": "REPLACED", - "list": [ - [ - "REPLACED" - ], - "" - ] - } - }" - `); - }); }); describe("addJSONComment", () => { diff --git a/packages/create-cloudflare/src/helpers/json.ts b/packages/create-cloudflare/src/helpers/json.ts index d674c89695d4..77c0d2c61e53 100644 --- a/packages/create-cloudflare/src/helpers/json.ts +++ b/packages/create-cloudflare/src/helpers/json.ts @@ -16,7 +16,7 @@ import type { */ export function readJSONWithComments( jsonFilePath: string, - reviver?: Reviver | null, + reviver?: Reviver, ): CommentObject { const jsonString = readFile(jsonFilePath); const jsonObject = parse(jsonString, reviver) as unknown as CommentObject; @@ -27,19 +27,12 @@ export function readJSONWithComments( * Writes a JSON object to a file, preserving comments. * @param jsonObject - The JSON object (with comment properties) to write. * @param jsonFilePath - The path to the JSON file. - * @param replacer A function that transforms the results or - * an array of strings and numbers that acts as an approved list for selecting - * the object properties that will be stringified. */ export function writeJSONWithComments( jsonFilePath: string, jsonObject: CommentObject, - replacer?: - | ((key: string, value: unknown) => unknown) - | Array - | null, ): void { - const jsonStr = stringify(jsonObject, replacer, "\t"); + const jsonStr = stringify(jsonObject, null, "\t"); writeFile(jsonFilePath, jsonStr); } diff --git a/packages/create-cloudflare/src/wrangler/config.ts b/packages/create-cloudflare/src/wrangler/config.ts index 208de8dd1fe2..1486e125d8f0 100644 --- a/packages/create-cloudflare/src/wrangler/config.ts +++ b/packages/create-cloudflare/src/wrangler/config.ts @@ -210,24 +210,15 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => { * * @param ctx The C3 context. * @param config The JSON object (with comment properties) to write. - * @param replacer A function that transforms the results or - * an array of strings and numbers that acts as an approved list for selecting - * the object properties that will be stringified. + * @param replacer A function that transforms the results, called recursively for each property of the object. */ -export const writeWranglerJsonOrJsonc = ( - ctx: C3Context, - config: CommentObject, - replacer?: - | ((key: string, value: unknown) => unknown) - | Array - | null, -) => { +const writeWranglerJsonOrJsonc = (ctx: C3Context, config: CommentObject) => { const wranglerJsonPath = getWranglerJsonPath(ctx); if (existsSync(wranglerJsonPath)) { - return writeJSONWithComments(wranglerJsonPath, config, replacer); + return writeJSONWithComments(wranglerJsonPath, config); } const wranglerJsoncPath = getWranglerJsoncPath(ctx); - return writeJSONWithComments(wranglerJsoncPath, config, replacer); + return writeJSONWithComments(wranglerJsoncPath, config); }; export const addVscodeConfig = (ctx: C3Context) => { From 88915f0a93d04ad40c7926f2e4ac52aa989754f2 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 9 Dec 2025 11:58:46 +0100 Subject: [PATCH 6/7] fixup! fix comment --- packages/create-cloudflare/src/wrangler/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/create-cloudflare/src/wrangler/config.ts b/packages/create-cloudflare/src/wrangler/config.ts index 1486e125d8f0..f3d19570ff46 100644 --- a/packages/create-cloudflare/src/wrangler/config.ts +++ b/packages/create-cloudflare/src/wrangler/config.ts @@ -210,7 +210,6 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => { * * @param ctx The C3 context. * @param config The JSON object (with comment properties) to write. - * @param replacer A function that transforms the results, called recursively for each property of the object. */ const writeWranglerJsonOrJsonc = (ctx: C3Context, config: CommentObject) => { const wranglerJsonPath = getWranglerJsonPath(ctx); From c9f28a0e817bb389a75c6b63c97d0e9554a45eeb Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 9 Dec 2025 12:18:58 +0100 Subject: [PATCH 7/7] fixup! types --- packages/create-cloudflare/src/wrangler/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-cloudflare/src/wrangler/config.ts b/packages/create-cloudflare/src/wrangler/config.ts index f3d19570ff46..c44bcbb5868d 100644 --- a/packages/create-cloudflare/src/wrangler/config.ts +++ b/packages/create-cloudflare/src/wrangler/config.ts @@ -187,7 +187,7 @@ export const readWranglerToml = (ctx: C3Context) => { */ export const readWranglerJsonOrJsonc = ( ctx: C3Context, - reviver?: Reviver | null, + reviver?: Reviver, ): CommentObject => { const wranglerJsonPath = getWranglerJsonPath(ctx); if (existsSync(wranglerJsonPath)) {