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
10 changes: 7 additions & 3 deletions .claude/skills/swamp-extension-model/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,13 @@ Files are classified by export name: `export const model` defines new types,
Deno-compatible import (`npm:`, `jsr:`, `https://`) can also be used — swamp
bundles all dependencies automatically (see
[references/examples.md](references/examples.md#using-external-dependencies))
3. **Type naming**: Use `@<namespace>/<name>` format (e.g., `@user/my-model`)
4. **No type annotations**: Avoid TypeScript types in execute parameters
5. **File naming**: Use snake_case (`my_model.ts`)
3. **Pin npm versions**: Always pin explicit versions for npm imports (e.g.,
`npm:[email protected]`, not `npm:lodash-es`). Swamp does not use a lockfile
during bundling, so unpinned versions may resolve differently across runs.
`npm:zod@4` is the one exception — it is externalized and provided by swamp.
4. **Type naming**: Use `@<namespace>/<name>` format (e.g., `@user/my-model`)
5. **No type annotations**: Avoid TypeScript types in execute parameters
6. **File naming**: Use snake_case (`my_model.ts`)

## Namespace Rules

Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/swamp-extension-model/references/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ with swamp to preserve schema `instanceof` checks.
```typescript
// extensions/models/text_analyzer.ts
import { z } from "npm:zod@4";
import { countBy, sortBy, words } from "npm:lodash-es";
import { countBy, sortBy, words } from "npm:lodash-es@4.17.21";

const GlobalArgsSchema = z.object({
text: z.string().describe("Text to analyze"),
Expand Down Expand Up @@ -93,8 +93,8 @@ export const model = {
| Import | Bundled? | Notes |
| ---------------------------------------- | -------- | -------------------------------- |
| `npm:zod@4` | No | Shared with swamp (externalized) |
| `npm:lodash-es` | Yes | Resolved and inlined |
| `npm:@aws-sdk/client-s3` | Yes | Resolved and inlined |
| `npm:lodash-es@4.17.21` | Yes | Resolved and inlined |
| `npm:@aws-sdk/client-s3@3.750.0` | Yes | Resolved and inlined |
| `jsr:@std/path` | Yes | Resolved and inlined |
| `https://deno.land/[email protected]/async/..` | Yes | Resolved and inlined |
| Any other Deno-compatible import | Yes | Resolved and inlined |
Expand Down
1 change: 1 addition & 0 deletions src/domain/models/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function bundleExtension(
const command = new Deno.Command(denoPath, {
args: [
"bundle",
"--no-lock",
"--external",
"npm:zod@4",
"--external",
Expand Down
46 changes: 46 additions & 0 deletions src/domain/models/bundle_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,52 @@ export const schema = z.object({ name: z.string() });
});
});

Deno.test("bundleExtension resolves npm imports without creating deno.lock", async () => {
const tsCode = `
import { z } from "npm:zod@4";
import { parse, stringify } from "npm:[email protected]";

const ConfigSchema = z.object({
data: z.string(),
});

export const model = {
type: "@test/yaml-model",
version: "2026.01.01.1",
schema: ConfigSchema,
transform: (input: string) => {
const parsed = parse(input);
return stringify(parsed);
},
};
`;

const dir = await Deno.makeTempDir({ prefix: "swamp_bundle_test_" });
const path = join(dir, "test_ext.ts");
await Deno.writeTextFile(path, tsCode);
try {
const js = await bundleExtension(path, DENO_PATH);

// Bundle should succeed and contain inlined yaml library code
assertEquals(js.length > 0, true);

// No deno.lock should be created in the source directory
let lockExists = true;
try {
await Deno.stat(join(dir, "deno.lock"));
} catch {
lockExists = false;
}
assertEquals(
lockExists,
false,
"deno.lock should not be created in the source directory",
);
} finally {
await Deno.remove(dir, { recursive: true });
}
});

Deno.test("bundleExtension produces importable module with working zod instanceof", async () => {
const tsCode = `
import { z } from "npm:zod@4";
Expand Down
Loading