import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { banReason, checkWasmSubpathDeps, extractSpecifiers, } from "./check-wasm-subpath-deps.js"; let tmp: string; beforeEach(async () => { tmp = await mkdtemp(join(tmpdir(), "wasm-deps-")); }); afterEach(async () => { await rm(tmp, { recursive: true, force: true }); }); async function writeFixture(rel: string, content: string): Promise { const full = join(tmp, rel); await mkdir(join(full, ".."), { recursive: true }); await writeFile(full, content, "utf8"); return full; } describe("check-wasm-subpath-deps", () => { it("banReason flags node: specifiers", { timeout: 5000 }, () => { expect(banReason("node:fs")).toMatch(/node:/); }); it("banReason flags exact bans", { timeout: 5000 }, () => { expect(banReason("hono")).toMatch(/banned package/); expect(banReason("@suaveplan/api-host")).toMatch(/banned package/); }); it("banReason flags prefix bans", { timeout: 5000 }, () => { expect(banReason("@hono/zod-validator")).toMatch(/banned prefix/); expect(banReason("@opentelemetry/sdk-metrics")).toMatch( /banned prefix/ ); expect(banReason("@opentelemetry/sdk-trace-base")).toMatch( /banned prefix/ ); }); it("banReason passes safe specifiers", { timeout: 5000 }, () => { expect(banReason("@opentelemetry/api")).toBeNull(); expect(banReason("zod")).toBeNull(); expect(banReason("@suaveplan/error")).toBeNull(); }); it("extractSpecifiers picks up named, default, and type imports", { timeout: 5000, }, () => { const src = ` import x from "a"; import { b } from "b"; import type { C } from "c"; export { d } from "d"; // import "ignored-in-comment"; `; const out = extractSpecifiers(src); expect(out).toContain("a"); expect(out).toContain("b"); expect(out).toContain("c"); expect(out).toContain("d"); expect(out).not.toContain("ignored-in-comment"); }); it("rejects banned specifier in wasm subpath", { timeout: 5000, }, async () => { // Build a tiny fixture: entry imports "hono" directly. const entry = await writeFixture( "packages/example/wasm/index.ts", `import { Hono } from "hono";\nexport const app = new Hono();\n` ); const hits = await checkWasmSubpathDeps({ repoRoot: tmp, entry }); expect(hits.length).toBeGreaterThan(0); const hononHit = hits.find((h) => h.specifier === "hono"); expect(hononHit).toBeDefined(); }); it("accepts a clean entry with only allowed specifiers", { timeout: 5000, }, async () => { const entry = await writeFixture( "packages/example/wasm/index.ts", `import z from "zod";\nimport { foo } from "./foo.js";\n` ); await writeFixture( "packages/example/wasm/foo.ts", "export const foo = 1;\n" ); const hits = await checkWasmSubpathDeps({ repoRoot: tmp, entry }); expect(hits).toEqual([]); }); it("detects node:* specifier in entry", { timeout: 5000 }, async () => { const entry = await writeFixture( "packages/example/wasm/index.ts", `import fs from "node:fs";\n` ); const hits = await checkWasmSubpathDeps({ repoRoot: tmp, entry }); expect(hits.length).toBeGreaterThan(0); expect(hits[0]?.specifier).toBe("node:fs"); }); });