Skip to content

Commit 776098c

Browse files
authored
[wrangler] Fix types --check incorrectly reporting out of date in multi-worker setups (#14034)
1 parent f387256 commit 776098c

4 files changed

Lines changed: 133 additions & 8 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Fix `wrangler types --check` reporting types as out of date in multi-worker setups
6+
7+
Previously, running `wrangler types --check -c primary/wrangler.jsonc` in a multi-worker project would incorrectly report types as out of date, even when they were current. This happened because the secondary worker config paths (passed via additional `-c` flags during generation) were not stored in the generated types file header, so `--check` had no way to resolve the secondary workers' service bindings when verifying the hash.
8+
9+
The fix stores secondary config paths in the generated file's header comment so that `--check` can recover them automatically. Users no longer need to re-pass every `-c` flag when running `--check` — only the primary config is required.

packages/wrangler/e2e/types.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe("types", () => {
133133
).split("\n");
134134

135135
expect(lines[1]).toMatchInlineSnapshot(
136-
`"// Generated by Wrangler by running \`wrangler types -c wranglerA.toml --env-interface MyCloudflareEnv ./cflare-env.d.ts\` (hash: b5768def7c11ba0a77ed50583b661706)"`
136+
`"// Generated by Wrangler by running \`wrangler types --config=wranglerA.toml --env-interface=MyCloudflareEnv ./cflare-env.d.ts\` (hash: b5768def7c11ba0a77ed50583b661706)"`
137137
);
138138
expect(lines[2]).match(
139139
/\/\/ Runtime types generated with workerd@1\.\d{8}\.\d \d{4}-\d{2}-\d{2} ([a-z_]+,?)*/
@@ -432,7 +432,7 @@ describe("types", () => {
432432
expect(output.status).toBe(1);
433433
});
434434

435-
it("should error when --check omits secondary configs that were used during generation", async ({
435+
it("should not error when --check omits secondary configs (auto-recovered from header)", async ({
436436
expect,
437437
}) => {
438438
await helper.run(
@@ -442,8 +442,9 @@ describe("types", () => {
442442
const output = await helper.run(
443443
`wrangler types --check --include-runtime=false -c primary/wrangler.jsonc --path primary/worker-configuration.d.ts`
444444
);
445-
expect(output.stderr).toContain("out of date");
446-
expect(output.status).toBe(1);
445+
expect(output.stderr).toBeFalsy();
446+
expect(output.stdout).toContain("up to date");
447+
expect(output.status).toBe(0);
447448
});
448449
});
449450
});

packages/wrangler/src/__tests__/type-generation.test.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ describe("generate types - CLI", () => {
12971297
expect(fs.readFileSync("./worker-configuration.d.ts", "utf-8"))
12981298
.toMatchInlineSnapshot(`
12991299
"/* eslint-disable */
1300-
// Generated by Wrangler by running \`wrangler\` (hash: 87f2cdaf48add6af8118936351f2fdf6)
1300+
// Generated by Wrangler by running \`wrangler types\` (hash: 87f2cdaf48add6af8118936351f2fdf6)
13011301
// Runtime types generated with workerd@
13021302
interface __BaseEnv_Env {
13031303
SOMETHING: "asdasdfasdf";
@@ -1747,6 +1747,59 @@ describe("generate types - CLI", () => {
17471747
);
17481748
});
17491749

1750+
it("should report types as up to date for multi-worker setup without re-passing -c flags (--check)", async ({
1751+
expect,
1752+
}) => {
1753+
fs.mkdirSync("primary", { recursive: true });
1754+
fs.mkdirSync("secondary", { recursive: true });
1755+
1756+
fs.writeFileSync(
1757+
"./primary/index.ts",
1758+
`export default { async fetch() { return new Response("ok"); } };`,
1759+
"utf-8"
1760+
);
1761+
fs.writeFileSync(
1762+
"./primary/wrangler.jsonc",
1763+
JSON.stringify({
1764+
name: "primary-worker",
1765+
main: "./index.ts",
1766+
compatibility_date: "2024-01-01",
1767+
services: [{ binding: "SECONDARY", service: "secondary-worker" }],
1768+
}),
1769+
"utf-8"
1770+
);
1771+
1772+
fs.writeFileSync(
1773+
"./secondary/index.ts",
1774+
`export default { async fetch() { return new Response("ok"); } };`,
1775+
"utf-8"
1776+
);
1777+
fs.writeFileSync(
1778+
"./secondary/wrangler.jsonc",
1779+
JSON.stringify({
1780+
name: "secondary-worker",
1781+
main: "./index.ts",
1782+
compatibility_date: "2024-01-01",
1783+
}),
1784+
"utf-8"
1785+
);
1786+
1787+
// Generate types with both configs
1788+
await runWrangler(
1789+
"types --include-runtime=false -c primary/wrangler.jsonc -c secondary/wrangler.jsonc --path primary/worker-configuration.d.ts"
1790+
);
1791+
1792+
// --check with only the primary -c (no secondary -c) should still detect
1793+
// the types as up to date by recovering the secondary config from the header.
1794+
await runWrangler(
1795+
"types --check --include-runtime=false -c primary/wrangler.jsonc --path primary/worker-configuration.d.ts"
1796+
);
1797+
1798+
expect(std.out).toContain(
1799+
"Types at primary/worker-configuration.d.ts are up to date."
1800+
);
1801+
});
1802+
17501803
it("should include secret keys from .env, if there is no .dev.vars", async ({
17511804
expect,
17521805
}) => {
@@ -3317,7 +3370,7 @@ describe("generate types - CLI", () => {
33173370
expect(fs.readFileSync("./cloudflare-env.d.ts", "utf-8"))
33183371
.toMatchInlineSnapshot(`
33193372
"/* eslint-disable */
3320-
// Generated by Wrangler by running \`wrangler\` (hash: d3ba65ad57f6692e19d214b1925cf9fc)
3373+
// Generated by Wrangler by running \`wrangler types cloudflare-env.d.ts\` (hash: d3ba65ad57f6692e19d214b1925cf9fc)
33213374
// Runtime types generated with workerd@
33223375
interface __BaseEnv_Env {
33233376
SOMETHING: "asdasdfasdf";
@@ -3380,7 +3433,7 @@ describe("generate types - CLI", () => {
33803433
expect(fs.readFileSync("./my-cloudflare-env-interface.d.ts", "utf-8"))
33813434
.toMatchInlineSnapshot(`
33823435
"/* eslint-disable */
3383-
// Generated by Wrangler by running \`wrangler\` (hash: 98b5807d9daed69f691128b816dc9f4d)
3436+
// Generated by Wrangler by running \`wrangler types --env-interface=MyCloudflareEnvInterface my-cloudflare-env-interface.d.ts\` (hash: 98b5807d9daed69f691128b816dc9f4d)
33843437
// Runtime types generated with workerd@
33853438
interface __BaseEnv_MyCloudflareEnvInterface {
33863439
SOMETHING: "asdasdfasdf";

packages/wrangler/src/type-generation/index.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import chalk from "chalk";
1313
import * as find from "empathic/find";
1414
import { getNodeCompat } from "miniflare";
15+
import yargs from "yargs";
1516
import { readConfig } from "../config";
1617
import { createCommand } from "../core/create-command";
1718
import { getEntry } from "../deployment-bundle/entry";
@@ -208,6 +209,11 @@ export const typesCommand = createCommand({
208209
validateOutputPath: false,
209210
});
210211

212+
resolvedOptions.envHeaderCommand = buildGenerateTypesHeaderCommand(
213+
args,
214+
resolvedOptions
215+
);
216+
211217
const {
212218
config,
213219
envInterface,
@@ -216,11 +222,19 @@ export const typesCommand = createCommand({
216222
} = resolvedOptions;
217223

218224
if (args.check) {
225+
// If the user didn't supply secondary configs via -c, try to recover
226+
// them from the paths stored in the types file header so --check is
227+
// self-contained for multi-worker setups.
228+
const effectiveSecondaryEntries =
229+
secondaryEntries.size > 0
230+
? secondaryEntries
231+
: await recoverSecondaryEntriesFromTypesFile(outputPath);
232+
219233
const outOfDate = await checkTypesUpToDate(
220234
config,
221235
envInterface,
222236
outputPath,
223-
secondaryEntries,
237+
effectiveSecondaryEntries,
224238
args.envFile,
225239
args.env
226240
);
@@ -292,12 +306,60 @@ export async function generateTypesFromWranglerOptions(
292306
return generateTypesFromResolvedOptions(resolvedOptions, false);
293307
}
294308

309+
async function recoverSecondaryEntriesFromTypesFile(
310+
typesPath: string
311+
): Promise<Map<string, Entry>> {
312+
try {
313+
const lines = fs.readFileSync(typesPath, "utf-8").split("\n");
314+
const headerLine = lines.find((l) =>
315+
l.startsWith("// Generated by Wrangler by running `")
316+
);
317+
const storedCommand =
318+
headerLine?.match(
319+
/\/\/ Generated by Wrangler by running `(?<command>.*)`/
320+
)?.groups?.command ?? "";
321+
322+
const rawArgs = await yargs(storedCommand).parse();
323+
const configArg = rawArgs.config as string | string[] | undefined;
324+
// The stored command has primary config first, then secondaries.
325+
// Skip the first entry (primary — the user always re-passes it via -c).
326+
const allConfigs = Array.isArray(configArg)
327+
? configArg
328+
: typeof configArg === "string"
329+
? [configArg]
330+
: [];
331+
const secondaryPaths = allConfigs.slice(1);
332+
333+
if (secondaryPaths.length === 0) {
334+
return new Map();
335+
}
336+
337+
const secondaryConfigs = secondaryPaths.map((p) =>
338+
readConfig({ config: p })
339+
);
340+
return resolveSecondaryEntries(secondaryConfigs, false);
341+
} catch {
342+
return new Map();
343+
}
344+
}
345+
295346
function buildGenerateTypesHeaderCommand(
296347
options: GenerateTypesOptions,
297348
resolvedOptions: ResolvedGenerateTypesOptions
298349
): string {
299350
const commandParts: string[] = ["wrangler", "types"];
300351

352+
// Store all config paths (primary first, then secondaries) so --check can
353+
// recover secondary entries without requiring the user to re-pass every -c.
354+
const configs = Array.isArray(options.config)
355+
? options.config
356+
: typeof options.config === "string"
357+
? [options.config]
358+
: [];
359+
for (const configPath of configs) {
360+
commandParts.push(`--config=${configPath}`);
361+
}
362+
301363
if (options.env !== undefined) {
302364
commandParts.push(`--env=${options.env}`);
303365
}

0 commit comments

Comments
 (0)