Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fix-type-generation-multi-worker-env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"wrangler": patch
---

fix: resolve secondary worker types when environment overrides the worker name in multi-worker type generation

When running `wrangler types` with multiple `-c` config flags and the secondary worker has named environments that override the worker name (e.g. a worker named `do-worker` with env `staging` whose effective name becomes `do-worker-staging`), service bindings and Durable Object bindings in the primary worker that reference `do-worker-staging` now correctly resolve to the typed entry point instead of falling back to an unresolved comment type such as `DurableObjectNamespace /* MyClass from do-worker-staging */`.

The fix extends the secondary entries map to also register environment-specific worker names, so that lookups by the env-qualified name (e.g. `do-worker-staging`) resolve to the same source file as the base worker name.
113 changes: 113 additions & 0 deletions packages/wrangler/src/__tests__/type-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,119 @@ describe("generate types", () => {
`);
});

it("should resolve secondary worker types when env overrides the worker name", async ({
expect,
}) => {
// Reproduces https://github.com/cloudflare/workers-sdk/issues/12971
// When a secondary worker has named environments that override the worker name
// (e.g. name "do-worker" with env "staging" whose effective name is "do-worker-staging"),
// service/DO bindings in the primary worker that reference "do-worker-staging" should
// resolve to the typed entry instead of falling back to an unresolved comment type.
fs.mkdirSync("primary");
fs.mkdirSync("secondary");

fs.writeFileSync(
"./secondary/index.ts",
`import { DurableObject } from 'cloudflare:workers';
export default { async fetch() {} };
export class MyDurableObject extends DurableObject {}
`
);
fs.writeFileSync(
"./secondary/wrangler.jsonc",
JSON.stringify({
compatibility_date: "2022-01-12",
name: "do-worker",
main: "./index.ts",
durable_objects: {
bindings: [{ name: "MY_DO", class_name: "MyDurableObject" }],
},
migrations: [{ tag: "v1", new_classes: ["MyDurableObject"] }],
env: {
staging: {
name: "do-worker-staging",
durable_objects: {
bindings: [{ name: "MY_DO", class_name: "MyDurableObject" }],
},
},
},
}),
"utf-8"
);

fs.writeFileSync(
"./primary/index.ts",
"export default { async fetch() {} };"
);
fs.writeFileSync(
"./primary/wrangler.jsonc",
JSON.stringify({
compatibility_date: "2022-01-12",
name: "primary-worker",
main: "./index.ts",
services: [{ binding: "DO_WORKER", service: "do-worker" }],
durable_objects: {
bindings: [
{
name: "DO_OBJECT",
class_name: "MyDurableObject",
script_name: "do-worker",
},
],
},
env: {
staging: {
name: "primary-worker-staging",
services: [{ binding: "DO_WORKER", service: "do-worker-staging" }],
durable_objects: {
bindings: [
{
name: "DO_OBJECT",
class_name: "MyDurableObject",
script_name: "do-worker-staging",
},
],
},
},
},
}),
"utf-8"
);

await runWrangler(
"types --include-runtime=false -c primary/wrangler.jsonc -c secondary/wrangler.jsonc --path primary/worker-configuration.d.ts"
);

expect(std.out).toMatchInlineSnapshot(`
"
⛅️ wrangler x.x.x
──────────────────
- Found Worker 'do-worker' at 'secondary/index.ts' (secondary/wrangler.jsonc)
Generating project types...

declare namespace Cloudflare {
interface GlobalProps {
mainModule: typeof import("./index");
}
interface StagingEnv {
DO_OBJECT: DurableObjectNamespace<import("../secondary/index").MyDurableObject>;
DO_WORKER: Service<typeof import("../secondary/index").default>;
}
interface Env {
DO_OBJECT: DurableObjectNamespace<import("../secondary/index").MyDurableObject>;
DO_WORKER: Service<typeof import("../secondary/index").default>;
}
}
interface Env extends Cloudflare.Env {}

────────────────────────────────────────────────────────────
✨ Types written to primary/worker-configuration.d.ts

📣 Remember to rerun 'wrangler types' after you change your wrangler.jsonc file.
"
`);
});

it("should create a DTS file at the location that the command is executed from", async ({
expect,
}) => {
Expand Down
16 changes: 16 additions & 0 deletions packages/wrangler/src/type-generation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,22 @@ export const typesCommand = createCommand({
`- Found Worker '${key}' at '${relative(process.cwd(), serviceEntry.file)}' (${secondaryConfig.configPath})`
)
);

// Also register environment-specific names (e.g. "worker-foo" for env "foo")
// so that service/DO bindings referencing those names can be resolved.
const { rawConfig } = experimental_readRawConfig({
config: secondaryConfig.configPath,
});
for (const envName of Object.keys(rawConfig.env ?? {})) {
const envConfig = readConfig({
config: secondaryConfig.configPath,
env: envName,
});
const envKey = envConfig.name;
if (envKey && envKey !== key && !secondaryEntries.has(envKey)) {
secondaryEntries.set(envKey, serviceEntry);
}
}
} else {
throw new UserError(
`Could not resolve entry point for service config '${secondaryConfig}'.`
Expand Down
Loading