Relationships
#3072 Extension bundler rewrites zod import text inside string/template literals, breaking code-generating extensions
Opened by mellens · 10/6/2026· Shipped 10/6/2026
Description
When swamp bundles an extension, it rewrites import { z } from "npm:zod@4" to const { z } = globalThis.__swamp_zod;. The rewrite also applies to text inside string and template literals, not only to real import statements.
That breaks any extension that generates TypeScript (scaffolders, code generators). The generated text comes out with the swamp-only globalThis.__swamp_zod line, so the generated file doesn't compile outside swamp. Running the same module directly with deno gives the correct text, so the extension's unit tests pass and the bug only shows up through swamp.
Steps to reproduce
- In a fresh repo (
swamp repo init), createextensions/models/gen.ts:
import { z } from "npm:zod@4";
const TEMPLATE = `import { z } from "npm:zod@4";
export const x = z.string();
`;
export const model = {
type: "@example/gen",
version: "2026.10.06.1",
globalArguments: z.object({}),
resources: {
out: { description: "generated text", schema: z.object({ text: z.string() }), lifetime: "1h" as const, garbageCollection: 5 },
},
methods: {
emit: {
description: "Return the template text unchanged",
arguments: z.object({}),
execute: async (_args: Record<string, never>, ctx: { writeResource: (s: string, n: string, d: Record<string, unknown>) => Promise<{ name: string }> }) => {
return { dataHandles: [await ctx.writeResource("out", "out", { text: TEMPLATE })] };
},
},
},
};- Run it:
swamp model create @example/gen gen
swamp model method run gen emit
swamp data query 'modelName == "gen" && name == "out"' --select content --json | jq -r '.results[0].text'Expected:
import { z } from "npm:zod@4";
export const x = z.string();Actual:
const { z } = globalThis.__swamp_zod;
export const x = z.string();Importing the same module directly with deno and calling execute returns the expected text.
Suggested fix
Rewrite only real import declarations (e.g. with an esbuild plugin or an AST pass), not every text match in the source.
Workaround
Build the specifier at runtime so the full import text never appears in the source, e.g. `import { z } from ${JSON.stringify(["npm:", "zod@4"].join(""))};`.
Environment
- swamp 20261006.100009.0-sha.d9670e54
- macOS (Darwin 25.6.0, arm64)
Shipped
Click a lifecycle step above to view its details.
hammz commented 10/6/2026, 7:53:45 PM
Thanks @mellens for reporting this! We shipped: Make the post-bundle zod rewrite apply only to real import declarations. rewriteZodImports and rejectZodV3Imports currently regex-match the whole bundled JS text, so zod import text inside string or template literals is rewritten to the swamp-only global line, corrupting the output of code-generating extensions. Parse the bundle with the already-vendored Babel parser, take only top-level import declarations, and apply the existing replacement to those spans, with a no-parse fast path and a fall back to the old behavior if the parse fails.. The fix has been merged and a release is on its way. We appreciate your contribution to swamp.
Sign in to post a ripple.