Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublicTeam
Assigneeshammz

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

  1. In a fresh repo (swamp repo init), create extensions/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 })] };
      },
    },
  },
};
  1. 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)
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 9 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/6/2026, 7:53:43 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
hammz assigned hammz10/6/2026, 6:55:49 PM
Editable. Press Enter to edit.

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.