Skip to content

Commit 83e8243

Browse files
committed
fixup! reviver, <COMPATIBILITY_DATE>, renaming
1 parent 17531bf commit 83e8243

File tree

6 files changed

+101
-51
lines changed

6 files changed

+101
-51
lines changed

.changeset/brave-lights-run.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44

55
Add template string substitution in wrangler config files.
66

7-
The value `"<WORKER_NAME>"` will be replaced by the project name when wrangler create a config file (in all of the toml, json, and jsonc formats).
7+
When c3 updates the config file:
8+
9+
- The value `"<WORKER_NAME>"` is replaced with the worker name.
10+
- The value `"<COMPATIBILITY_DATE>"` is replaced with the latest worked compatibility date.

packages/create-cloudflare/src/deploy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import TOML from "smol-toml";
1313
import { isInsideGitRepo } from "./git";
1414
import { chooseAccount, wranglerLogin } from "./wrangler/accounts";
1515
import {
16-
readWranglerJson,
16+
readWranglerJsonOrJsonc,
1717
readWranglerToml,
18-
wranglerJsonExists,
18+
wranglerJsonOrJsoncExists,
1919
} from "./wrangler/config";
2020
import type { C3Context } from "types";
2121

@@ -81,8 +81,8 @@ const isDeployable = async (ctx: C3Context) => {
8181
};
8282

8383
const readWranglerConfig = (ctx: C3Context) => {
84-
if (wranglerJsonExists(ctx)) {
85-
return readWranglerJson(ctx);
84+
if (wranglerJsonOrJsoncExists(ctx)) {
85+
return readWranglerJsonOrJsonc(ctx);
8686
}
8787
const wranglerTomlStr = readWranglerToml(ctx);
8888
return TOML.parse(wranglerTomlStr.replace(/\r\n/g, "\n"));

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

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,35 @@ describe("json helpers", () => {
3030
expect(mockReadFile).toHaveBeenCalledWith("/path/to/file.json");
3131
expect(result).toEqual({ name: "test" });
3232
});
33+
34+
test("using a reviver function", () => {
35+
mockReadFile.mockReturnValue(
36+
JSON.stringify({
37+
name: "test",
38+
rootValue: "<REPLACE_ME>",
39+
keep: "<DO_NOT_REPLACE_ME>",
40+
nested: {
41+
value: "<REPLACE_ME>",
42+
list: [["<REPLACE_ME>"], "<DO_NOT_REPLACE_ME>"],
43+
},
44+
}),
45+
);
46+
47+
const result = readJSONWithComments(
48+
"/path/to/file.json",
49+
(_key, value) => (value === "<REPLACE_ME>" ? "REPLACED" : value),
50+
);
51+
expect(mockReadFile).toHaveBeenCalledWith("/path/to/file.json");
52+
expect(result).toEqual({
53+
name: "test",
54+
rootValue: "REPLACED",
55+
keep: "<DO_NOT_REPLACE_ME>",
56+
nested: {
57+
value: "REPLACED",
58+
list: [["REPLACED"], "<DO_NOT_REPLACE_ME>"],
59+
},
60+
});
61+
});
3362
});
3463

3564
describe("writeJSONWithComments", () => {
@@ -55,18 +84,18 @@ describe("json helpers", () => {
5584
mockReadFile.mockReturnValue(
5685
JSON.stringify({
5786
name: "test",
58-
rootValue: "_REPLACE_ME_",
59-
keep: "DO_NOT_REPLACE_ME_",
87+
rootValue: "<REPLACE_ME>",
88+
keep: "<DO_NOT_REPLACE_ME>",
6089
nested: {
61-
value: "_REPLACE_ME_",
62-
list: [["_REPLACE_ME_"], "DO_NOT_REPLACE_ME_"],
90+
value: "<REPLACE_ME>",
91+
list: [["<REPLACE_ME>"], "<DO_NOT_REPLACE_ME>"],
6392
},
6493
}),
6594
);
6695

6796
const result = readJSONWithComments("/path/to/file.json");
6897
writeJSONWithComments("/path/to/file.json", result, (_key, value) =>
69-
value === "_REPLACE_ME_" ? "REPLACED" : value,
98+
value === "<REPLACE_ME>" ? "REPLACED" : value,
7099
);
71100
expect(mockWriteFile.mock.calls[0][0]).toMatchInlineSnapshot(
72101
`"/path/to/file.json"`,
@@ -75,14 +104,14 @@ describe("json helpers", () => {
75104
"{
76105
"name": "test",
77106
"rootValue": "REPLACED",
78-
"keep": "DO_NOT_REPLACE_ME_",
107+
"keep": "<DO_NOT_REPLACE_ME>",
79108
"nested": {
80109
"value": "REPLACED",
81110
"list": [
82111
[
83112
"REPLACED"
84113
],
85-
"DO_NOT_REPLACE_ME_"
114+
"<DO_NOT_REPLACE_ME>"
86115
]
87116
}
88117
}"

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ import type {
55
CommentObject,
66
CommentSymbol,
77
CommentToken,
8+
Reviver,
89
} from "comment-json";
910

1011
/**
1112
* Reads a JSON file and preserves comments.
1213
* @param jsonFilePath - The path to the JSON file.
14+
* @param reviver A function that transforms the results. This function is called for each member of the object.
1315
* @returns The parsed JSON object with comments.
1416
*/
15-
export function readJSONWithComments(jsonFilePath: string): CommentObject {
17+
export function readJSONWithComments(
18+
jsonFilePath: string,
19+
reviver?: Reviver | null,
20+
): CommentObject {
1621
const jsonString = readFile(jsonFilePath);
17-
const jsonObject = parse(jsonString) as unknown as CommentObject;
22+
const jsonObject = parse(jsonString, reviver) as unknown as CommentObject;
1823
return jsonObject;
1924
}
2025

packages/create-cloudflare/src/workers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { installPackages } from "helpers/packages";
1010
import * as jsonc from "jsonc-parser";
1111
import TOML from "smol-toml";
1212
import {
13-
readWranglerJson,
13+
readWranglerJsonOrJsonc,
1414
readWranglerToml,
15-
wranglerJsonExists,
15+
wranglerJsonOrJsoncExists,
1616
wranglerTomlExists,
1717
} from "./wrangler/config";
1818
import type { C3Context, PackageJson } from "types";
@@ -63,8 +63,8 @@ async function generateWorkersTypes(ctx: C3Context, npm: string) {
6363

6464
const maybeInstallNodeTypes = async (ctx: C3Context, npm: string) => {
6565
let parsedConfig: Record<string, unknown> = {};
66-
if (wranglerJsonExists(ctx)) {
67-
parsedConfig = readWranglerJson(ctx);
66+
if (wranglerJsonOrJsoncExists(ctx)) {
67+
parsedConfig = readWranglerJsonOrJsonc(ctx);
6868
} else if (wranglerTomlExists(ctx)) {
6969
const wranglerTomlStr = readWranglerToml(ctx);
7070
parsedConfig = TOML.parse(wranglerTomlStr);

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

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
writeJSONWithComments,
1111
} from "helpers/json";
1212
import TOML from "smol-toml";
13-
import type { CommentObject } from "comment-json";
13+
import type { CommentObject, Reviver } from "comment-json";
1414
import type { C3Context } from "types";
1515

1616
/**
@@ -22,6 +22,7 @@ import type { C3Context } from "types";
2222
* - adding comments with links to documentation for common configuration options
2323
* - substituting placeholders with actual values
2424
* - `<WORKER_NAME>` with the project name
25+
* - `<COMPATIBILITY_DATE>` with the max compatibility date of the installed worked
2526
*
2627
* If both `wrangler.toml` and `wrangler.json`/`wrangler.jsonc` are present, only
2728
* the `wrangler.json`/`wrangler.jsonc` file will be updated.
@@ -30,10 +31,15 @@ export const updateWranglerConfig = async (ctx: C3Context) => {
3031
// Placeholders to replace in the wrangler config files
3132
const substitutions: Record<string, string> = {
3233
"<WORKER_NAME>": ctx.project.name,
34+
"<COMPATIBILITY_DATE>": await getWorkerdCompatibilityDate(),
3335
};
3436

35-
if (wranglerJsonExists(ctx)) {
36-
let wranglerJson = readWranglerJson(ctx);
37+
if (wranglerJsonOrJsoncExists(ctx)) {
38+
let wranglerJson = readWranglerJsonOrJsonc(ctx, (_key, value) =>
39+
typeof value === "string" && value in substitutions
40+
? substitutions[value]
41+
: value,
42+
);
3743

3844
// Put the schema at the top of the file
3945
wranglerJson = insertJSONProperty(
@@ -46,7 +52,7 @@ export const updateWranglerConfig = async (ctx: C3Context) => {
4652
wranglerJson = appendJSONProperty(
4753
wranglerJson,
4854
"compatibility_date",
49-
await getCompatibilityDate(wranglerJson),
55+
await getCompatibilityDate(wranglerJson.compatibility_date),
5056
);
5157
wranglerJson = appendJSONProperty(wranglerJson, "observability", {
5258
enabled: true,
@@ -84,20 +90,17 @@ export const updateWranglerConfig = async (ctx: C3Context) => {
8490
},
8591
]);
8692

87-
writeWranglerJson(ctx, wranglerJson, (_key, value) =>
88-
typeof value === "string" && value in substitutions
89-
? substitutions[value]
90-
: value,
91-
);
93+
writeWranglerJsonOrJsonc(ctx, wranglerJson);
9294
addVscodeConfig(ctx);
9395
} else if (wranglerTomlExists(ctx)) {
94-
const wranglerTomlStr = readWranglerToml(ctx);
95-
const parsed = TOML.parse(wranglerTomlStr);
96-
parsed.name = ctx.project.name;
97-
parsed["compatibility_date"] = await getCompatibilityDate(parsed);
98-
parsed["observability"] ??= { enabled: true };
96+
const wranglerToml = TOML.parse(readWranglerToml(ctx));
97+
wranglerToml.name = ctx.project.name;
98+
wranglerToml.compatibility_date = await getCompatibilityDate(
99+
wranglerToml.compatibility_date,
100+
);
101+
wranglerToml.observability ??= { enabled: true };
99102

100-
let strToml = TOML.stringify(parsed);
103+
let strToml = TOML.stringify(wranglerToml);
101104

102105
for (const [key, value] of Object.entries(substitutions)) {
103106
strToml = strToml.replaceAll(key, value);
@@ -161,8 +164,8 @@ export const wranglerTomlExists = (ctx: C3Context) => {
161164
return existsSync(wranglerTomlPath);
162165
};
163166

164-
/** Checks for wrangler.json and wrangler.jsonc */
165-
export const wranglerJsonExists = (ctx: C3Context) => {
167+
/** Checks for an existing `wrangler.json` or `wrangler.jsonc` */
168+
export const wranglerJsonOrJsoncExists = (ctx: C3Context) => {
166169
const wranglerJsonPath = getWranglerJsonPath(ctx);
167170
const wranglerJsoncPath = getWranglerJsoncPath(ctx);
168171
return existsSync(wranglerJsonPath) || existsSync(wranglerJsoncPath);
@@ -173,13 +176,25 @@ export const readWranglerToml = (ctx: C3Context) => {
173176
return readFile(wranglerTomlPath);
174177
};
175178

176-
export const readWranglerJson = (ctx: C3Context) => {
179+
/**
180+
* Reads the JSON configuration file for this project.
181+
*
182+
* If both `wrangler.json` and `wrangler.jsonc` are present, `wrangler.json` will be read.
183+
*
184+
* @param ctx The C3 context.
185+
* @param reviver A function that transforms the results. This function is called for each member of the object.
186+
* @returns The parsed JSON object with comments.
187+
*/
188+
export const readWranglerJsonOrJsonc = (
189+
ctx: C3Context,
190+
reviver?: Reviver | null,
191+
): CommentObject => {
177192
const wranglerJsonPath = getWranglerJsonPath(ctx);
178193
if (existsSync(wranglerJsonPath)) {
179-
return readJSONWithComments(wranglerJsonPath);
194+
return readJSONWithComments(wranglerJsonPath, reviver);
180195
}
181196
const wranglerJsoncPath = getWranglerJsoncPath(ctx);
182-
return readJSONWithComments(wranglerJsoncPath);
197+
return readJSONWithComments(wranglerJsoncPath, reviver);
183198
};
184199

185200
export const writeWranglerToml = (ctx: C3Context, contents: string) => {
@@ -199,7 +214,7 @@ export const writeWranglerToml = (ctx: C3Context, contents: string) => {
199214
* an array of strings and numbers that acts as an approved list for selecting
200215
* the object properties that will be stringified.
201216
*/
202-
export const writeWranglerJson = (
217+
export const writeWranglerJsonOrJsonc = (
203218
ctx: C3Context,
204219
config: CommentObject,
205220
replacer?:
@@ -234,25 +249,23 @@ export const addVscodeConfig = (ctx: C3Context) => {
234249
};
235250

236251
/**
237-
* Gets the compatibility date to use from the wrangler config.
252+
* Gets the compatibility date to use.
238253
*
239-
* If the compatibility date is missing or invalid, it sets it to the latest workerd date.
254+
* If the tentative date is valid, it is returned. Otherwise the latest workerd date is used.
240255
*
241-
* @param config Wrangler config
256+
* @param tentativeDate A tentative compatibility date, usually from wrangler config.
242257
* @returns The compatibility date to use in the form "YYYY-MM-DD"
243258
*/
244-
async function getCompatibilityDate<T extends Record<string, unknown>>(
245-
config: T,
246-
): Promise<string> {
259+
async function getCompatibilityDate(tentativeDate: unknown): Promise<string> {
247260
const validCompatDateRe = /^\d{4}-\d{2}-\d{2}$/m;
248-
const dateFromConfig = config["compatibility_date"];
249261
if (
250-
typeof dateFromConfig === "string" &&
251-
dateFromConfig.match(validCompatDateRe)
262+
typeof tentativeDate === "string" &&
263+
tentativeDate.match(validCompatDateRe)
252264
) {
253-
// If the compat date is already a valid one, leave it since it may be there for a specific compat reason
254-
return dateFromConfig;
265+
// Use the tentative date when it is valid.
266+
// It may be there for a specific compat reason
267+
return tentativeDate;
255268
}
256-
// If the compat date is missing or invalid, set it to the latest workerd date
269+
// Fallback to the latest workerd date
257270
return await getWorkerdCompatibilityDate();
258271
}

0 commit comments

Comments
 (0)