Skip to content

Commit b403d67

Browse files
committed
Add template susbsitution
1 parent 8672321 commit b403d67

File tree

5 files changed

+134
-15
lines changed

5 files changed

+134
-15
lines changed

.changeset/brave-lights-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-cloudflare": minor
3+
---
4+
5+
Add template substitution

packages/create-cloudflare/src/helpers/__tests__/json.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,44 @@ describe("json helpers", () => {
5050
// post-comment"
5151
`);
5252
});
53+
54+
test("using a function replacer", () => {
55+
mockReadFile.mockReturnValue(
56+
JSON.stringify({
57+
name: "test",
58+
rootValue: "_REPLACE_ME_",
59+
keep: "DO_NOT_REPLACE_ME_",
60+
nested: {
61+
value: "_REPLACE_ME_",
62+
list: [["_REPLACE_ME_"], "DO_NOT_REPLACE_ME_"],
63+
},
64+
}),
65+
);
66+
67+
const result = readJSONWithComments("/path/to/file.json");
68+
writeJSONWithComments("/path/to/file.json", result, (_key, value) =>
69+
value === "_REPLACE_ME_" ? "REPLACED" : value,
70+
);
71+
expect(mockWriteFile.mock.calls[0][0]).toMatchInlineSnapshot(
72+
`"/path/to/file.json"`,
73+
);
74+
expect(mockWriteFile.mock.calls[0][1]).toMatchInlineSnapshot(`
75+
"{
76+
"name": "test",
77+
"rootValue": "REPLACED",
78+
"keep": "DO_NOT_REPLACE_ME_",
79+
"nested": {
80+
"value": "REPLACED",
81+
"list": [
82+
[
83+
"REPLACED"
84+
],
85+
"DO_NOT_REPLACE_ME_"
86+
]
87+
}
88+
}"
89+
`);
90+
});
5391
});
5492

5593
describe("addJSONComment", () => {

packages/create-cloudflare/src/helpers/json.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@ export function readJSONWithComments(jsonFilePath: string): CommentObject {
2222
* Writes a JSON object to a file, preserving comments.
2323
* @param jsonObject - The JSON object (with comment properties) to write.
2424
* @param jsonFilePath - The path to the JSON file.
25+
* @param replacer A function that transforms the results or
26+
* an array of strings and numbers that acts as an approved list for selecting
27+
* the object properties that will be stringified.
2528
*/
2629
export function writeJSONWithComments(
2730
jsonFilePath: string,
2831
jsonObject: CommentObject,
32+
replacer?:
33+
| ((key: string, value: unknown) => unknown)
34+
| Array<number | string>
35+
| null,
2936
): void {
30-
const jsonStr = stringify(jsonObject, null, "\t");
37+
const jsonStr = stringify(jsonObject, replacer, "\t");
3138
writeFile(jsonFilePath, jsonStr);
3239
}
3340

packages/create-cloudflare/src/wrangler/__tests__/config.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ describe("update wrangler config", () => {
3535
`name = "<TBD>"`,
3636
`main = "src/index.ts"`,
3737
`compatibility_date = "<TBD>"`,
38+
`[[services]]`,
39+
`binding = "SELF_SERVICE"`,
40+
`service = "__WORKER_NAME__"`,
3841
].join("\n");
3942
vi.mocked(readFile).mockReturnValue(toml);
4043

@@ -49,6 +52,10 @@ describe("update wrangler config", () => {
4952
main = "src/index.ts"
5053
compatibility_date = "2024-01-17"
5154
55+
[[services]]
56+
binding = "SELF_SERVICE"
57+
service = "test"
58+
5259
[observability]
5360
enabled = true
5461
@@ -95,6 +102,12 @@ describe("update wrangler config", () => {
95102
name: "<TBD>",
96103
main: "src/index.ts",
97104
compatibility_date: "<TBD>",
105+
services: [
106+
{
107+
binding: "SELF_SERVICE",
108+
service: "__WORKER_NAME__",
109+
},
110+
],
98111
});
99112
vi.mocked(readFile).mockReturnValueOnce(json);
100113

@@ -111,6 +124,12 @@ describe("update wrangler config", () => {
111124
"name": "test",
112125
"main": "src/index.ts",
113126
"compatibility_date": "2024-01-17",
127+
"services": [
128+
{
129+
"binding": "SELF_SERVICE",
130+
"service": "test"
131+
}
132+
],
114133
"observability": {
115134
"enabled": true
116135
}

packages/create-cloudflare/src/wrangler/config.ts

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,24 @@ import type { CommentObject } from "comment-json";
1414
import type { C3Context } from "types";
1515

1616
/**
17-
* Update the `wrangler.(toml|json|jsonc)` file for this project by setting the name
18-
* to the selected project name and adding the latest compatibility date.
17+
* Update the `wrangler.(toml|json|jsonc)` file for this project by:
18+
*
19+
* - setting the `name` to the passed project name
20+
* - adding the latest compatibility date when no valid one is present
21+
* - enabling observability
22+
* - adding comments with links to documentation for common configuration options
23+
* - substituting placeholders with actual values
24+
* - `__WORKER_NAME__` with the project name
25+
*
26+
* If both `wrangler.toml` and `wrangler.json`/`wrangler.jsonc` are present, only
27+
* the `wrangler.json`/`wrangler.jsonc` file will be updated.
1928
*/
2029
export const updateWranglerConfig = async (ctx: C3Context) => {
30+
// Placeholders to replace in the wrangler config files
31+
const substitutions: Record<string, string> = {
32+
__WORKER_NAME__: ctx.project.name,
33+
};
34+
2135
if (wranglerJsonExists(ctx)) {
2236
let wranglerJson = readWranglerJson(ctx);
2337

@@ -70,7 +84,11 @@ export const updateWranglerConfig = async (ctx: C3Context) => {
7084
},
7185
]);
7286

73-
writeWranglerJson(ctx, wranglerJson);
87+
writeWranglerJson(ctx, wranglerJson, (_key, value) =>
88+
typeof value === "string" && value in substitutions
89+
? substitutions[value]
90+
: value,
91+
);
7492
addVscodeConfig(ctx);
7593
} else if (wranglerTomlExists(ctx)) {
7694
const wranglerTomlStr = readWranglerToml(ctx);
@@ -79,14 +97,17 @@ export const updateWranglerConfig = async (ctx: C3Context) => {
7997
parsed["compatibility_date"] = await getCompatibilityDate(parsed);
8098
parsed["observability"] ??= { enabled: true };
8199

82-
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`;
100+
let strToml = TOML.stringify(parsed);
83101

84-
const stringified = comment + TOML.stringify(parsed);
102+
for (const [key, value] of Object.entries(substitutions)) {
103+
strToml = strToml.replaceAll(key, value);
104+
}
85105

86106
writeWranglerToml(
87107
ctx,
88-
stringified +
89-
`
108+
`#:schema node_modules/wrangler/config-schema.json
109+
# For more details on how to configure Wrangler, refer to:\n# https://developers.cloudflare.com/workers/wrangler/configuration/
110+
${strToml}
90111
# Smart Placement
91112
# Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
92113
# [placement]
@@ -166,13 +187,32 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => {
166187
return writeFile(wranglerTomlPath, contents);
167188
};
168189

169-
export const writeWranglerJson = (ctx: C3Context, config: CommentObject) => {
190+
/**
191+
* Writes the passed JSON object as the configuration file for this project.
192+
*
193+
* If there is an existing `wrangler.json` file, it will be overwritten.
194+
* If not, `wrangler.jsonc` will be created/overwritten.
195+
*
196+
* @param ctx The C3 context.
197+
* @param config The JSON object (with comment properties) to write.
198+
* @param replacer A function that transforms the results or
199+
* an array of strings and numbers that acts as an approved list for selecting
200+
* the object properties that will be stringified.
201+
*/
202+
export const writeWranglerJson = (
203+
ctx: C3Context,
204+
config: CommentObject,
205+
replacer?:
206+
| ((key: string, value: unknown) => unknown)
207+
| Array<number | string>
208+
| null,
209+
) => {
170210
const wranglerJsonPath = getWranglerJsonPath(ctx);
171211
if (existsSync(wranglerJsonPath)) {
172-
return writeJSONWithComments(wranglerJsonPath, config);
212+
return writeJSONWithComments(wranglerJsonPath, config, replacer);
173213
}
174214
const wranglerJsoncPath = getWranglerJsoncPath(ctx);
175-
return writeJSONWithComments(wranglerJsoncPath, config);
215+
return writeJSONWithComments(wranglerJsoncPath, config, replacer);
176216
};
177217

178218
export const addVscodeConfig = (ctx: C3Context) => {
@@ -193,16 +233,26 @@ export const addVscodeConfig = (ctx: C3Context) => {
193233
});
194234
};
195235

236+
/**
237+
* Gets the compatibility date to use from the wrangler config.
238+
*
239+
* If the compatibility date is missing or invalid, it sets it to the latest workerd date.
240+
*
241+
* @param config Wrangler config
242+
* @returns The compatibility date to use in the form "YYYY-MM-DD"
243+
*/
196244
async function getCompatibilityDate<T extends Record<string, unknown>>(
197245
config: T,
198-
) {
246+
): Promise<string> {
199247
const validCompatDateRe = /^\d{4}-\d{2}-\d{2}$/m;
248+
const dateFromConfig = config["compatibility_date"];
200249
if (
201-
typeof config["compatibility_date"] === "string" &&
202-
config["compatibility_date"].match(validCompatDateRe)
250+
typeof dateFromConfig === "string" &&
251+
dateFromConfig.match(validCompatDateRe)
203252
) {
204253
// If the compat date is already a valid one, leave it since it may be there for a specific compat reason
205-
return config["compatibility_date"];
254+
return dateFromConfig;
206255
}
256+
// If the compat date is missing or invalid, set it to the latest workerd date
207257
return await getWorkerdCompatibilityDate();
208258
}

0 commit comments

Comments
 (0)