// Independent consumer check for @hm-design/sw (AI template, 2026-10-19). // Usage: node packages/sw/tests/ai-template-consumer.mjs // Packs the built package (npm pack), creates a React 19 + TypeScript + Vite project in , installs the // tarball, copies the shipped ai/examples from node_modules (not from the repo) and runs tsc + vite build. // Build the package first: npm run build -w @hm-design/sw import { execFileSync } from "node:child_process" import { cp, mkdir, readdir, writeFile } from "node:fs/promises" import path from "node:path" import { fileURLToPath } from "node:url" const packageRoot = fileURLToPath(new URL("../", import.meta.url)) const out = path.resolve(process.argv[2] ?? "") if (!process.argv[2] || out.startsWith(path.resolve(packageRoot, "../.."))) throw new Error("Pass an empty directory outside the repository") await mkdir(out, { recursive: true }) if ((await readdir(out)).length) throw new Error(`${out} is not empty`) const npm = (args, cwd) => execFileSync(process.platform === "win32" ? "npm.cmd" : "npm", args, { cwd, stdio: "inherit", shell: process.platform === "win32" }) npm(["pack", "--ignore-scripts", "--pack-destination", out], packageRoot) const tarball = (await readdir(out)).find(name => name.endsWith(".tgz")) const files = { "package.json": JSON.stringify({ name: "sw-ai-template-consumer", private: true, type: "module", scripts: { typecheck: "tsc -p tsconfig.json", build: "vite build" }, }, null, 2), "tsconfig.json": JSON.stringify({ compilerOptions: { target: "ES2022", lib: ["ES2022", "DOM", "DOM.Iterable"], module: "ESNext", moduleResolution: "Bundler", jsx: "react-jsx", strict: true, noEmit: true, skipLibCheck: false, isolatedModules: true, noUnusedLocals: true, noUnusedParameters: true, types: ["vite/client"], }, include: ["src"], }, null, 2), "vite.config.ts": `import { defineConfig } from "vite"\nimport react from "@vitejs/plugin-react"\nexport default defineConfig({ plugins: [react()] })\n`, "index.html": `SW consumer
\n`, "src/main.tsx": `import { StrictMode, useState } from "react" import { createRoot } from "react-dom/client" import "@hm-design/sw/styles.css" import { ExampleADefaultHeader } from "./examples/ExampleA-DefaultHeader" import { ExampleBViewerHeader } from "./examples/ExampleB-ViewerHeader" import { ExampleCFeatureHeader } from "./examples/ExampleC-FeatureHeader" import { ExampleDSearchExtension } from "./examples/ExampleD-SearchExtension" const examples = { a: ExampleADefaultHeader, b: ExampleBViewerHeader, c: ExampleCFeatureHeader, d: ExampleDSearchExtension } function App() { const [events, setEvents] = useState([]) const onEvent = (name: string) => setEvents(list => [...list, name]) return ( <> {Object.entries(examples).map(([id, Example]) => (
))}
{events.join("\\n")}
) } createRoot(document.getElementById("root")!).render() `, } for (const [name, text] of Object.entries(files)) { await mkdir(path.dirname(path.join(out, name)), { recursive: true }) await writeFile(path.join(out, name), text) } npm(["install", "--no-audit", "--no-fund", `./${tarball}`, "react@19.3.0", "react-dom@19.3.0"], out) npm(["install", "--no-audit", "--no-fund", "-D", "typescript@7.0.2", "vite@8.3.0", "@vitejs/plugin-react@6.1.1", "@types/react@19.3.0", "@types/react-dom@19.3.0"], out) // The examples are taken from the installed package: what an AI tool in another project would read. await cp(path.join(out, "node_modules/@hm-design/sw/ai/examples"), path.join(out, "src/examples"), { recursive: true }) npm(["run", "typecheck"], out) npm(["run", "build"], out) console.log(`Consumer ready: ${out} (dist/ built). Next: node packages/sw/tests/ai-template-consumer.browser.mjs ${out} `)