import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const registryInstallerPath = path.join(repoRoot, "scripts", "registry-install.mjs"); const tscBinPath = path.join(repoRoot, "node_modules", "typescript", "bin", "tsc"); const viteBinPath = path.join(repoRoot, "node_modules", "vite", "bin", "vite.js"); const tempPrefix = path.join(os.tmpdir(), "cadence-ui-registry-consumer-"); const keepArtifacts = process.env.CADENCE_KEEP_REGISTRY_SMOKE === "1"; const pnpmCommand = process.platform === "win32" ? "pnpm.cmd" : "pnpm"; const registryItems = ["button", "dialog", "input", "form"]; const packageJson = { name: "cadence-ui-registry-consumer-smoke", private: true, type: "module", dependencies: { react: "^18.3.1", "react-dom": "^18.3.1" }, devDependencies: { "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7" } }; const tsconfig = { compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "Bundler", jsx: "react-jsx", strict: true, esModuleInterop: true, skipLibCheck: true, noEmit: true, types: ["react", "react-dom"] }, include: ["src"] }; const indexHtml = ` Cadence UI Registry Smoke
`; const mainSource = `import { useForm } from "react-hook-form"; import { createRoot } from "react-dom/client"; import "./cadence-ui/tokens/styles.css"; import { Button } from "./cadence-ui/components/button"; import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from "./cadence-ui/components/dialog"; import { Form, FormControl, FormItem, FormLabel, FormMessage } from "./cadence-ui/components/form"; import { Input } from "./cadence-ui/components/input"; type FormValues = { email: string; }; function App() { const form = useForm({ defaultValues: { email: "team@cadence.dev" } }); return (
Email
Registry smoke Verifies copied components typecheck and build in a consumer app.
); } const root = document.getElementById("root"); if (!root) { throw new Error("Missing root element."); } createRoot(root).render(); `; function formatCommand(command, args) { return [command, ...args].join(" "); } async function run(command, args, options = {}) { await new Promise((resolve, reject) => { const child = spawn(command, args, { cwd: options.cwd ?? repoRoot, env: { ...process.env, ...(options.env ?? {}) }, stdio: "inherit" }); child.once("error", reject); child.once("exit", (code, signal) => { if (code === 0) { resolve(); return; } reject( new Error( `${formatCommand(command, args)} failed with code ${code ?? "null"} and signal ${ signal ?? "null" }.` ) ); }); }); } async function writeConsumerFixture(projectDir) { await fs.mkdir(path.join(projectDir, "src"), { recursive: true }); await fs.writeFile(path.join(projectDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}\n`); await fs.writeFile(path.join(projectDir, "tsconfig.json"), `${JSON.stringify(tsconfig, null, 2)}\n`); await fs.writeFile(path.join(projectDir, "index.html"), indexHtml); await fs.writeFile(path.join(projectDir, "src", "global.d.ts"), 'declare module "*.css";\n'); await fs.writeFile(path.join(projectDir, "src", "main.tsx"), mainSource); } async function assertFileExists(projectDir, relativePath) { const absolutePath = path.join(projectDir, ...relativePath.split("/")); try { await fs.stat(absolutePath); } catch { throw new Error(`Expected ${relativePath} to exist in the consumer fixture.`); } } async function verifyManifest(projectDir) { const manifestPath = path.join(projectDir, "src", "cadence-ui", ".install-manifest.json"); const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")); for (const item of registryItems) { if (!manifest.items.includes(item)) { throw new Error(`Registry manifest is missing "${item}".`); } } if (!manifest.items.includes("tokens")) { throw new Error('Registry manifest is missing the required "tokens" item.'); } } async function main() { const projectDir = await fs.mkdtemp(tempPrefix); let succeeded = false; const cleanup = async () => { if (!succeeded || keepArtifacts) { console.log(`Keeping registry smoke fixture at ${projectDir}`); return; } await fs.rm(projectDir, { force: true, recursive: true }); }; try { console.log(`Creating registry consumer fixture in ${projectDir}`); await writeConsumerFixture(projectDir); console.log("Installing registry items into the consumer fixture"); await run(process.execPath, [registryInstallerPath, "--project", projectDir, ...registryItems]); await assertFileExists(projectDir, "src/cadence-ui/components/button.tsx"); await assertFileExists(projectDir, "src/cadence-ui/components/dialog.tsx"); await assertFileExists(projectDir, "src/cadence-ui/components/form.tsx"); await assertFileExists(projectDir, "src/cadence-ui/tokens/styles.css"); await verifyManifest(projectDir); console.log("Installing consumer dependencies"); await run(pnpmCommand, ["install", "--ignore-workspace"], { cwd: projectDir, env: { CI: "1" } }); console.log("Re-running registry install from the saved manifest"); await run(process.execPath, [registryInstallerPath, "--project", projectDir, "--dry-run"]); console.log("Typechecking the consumer fixture"); await run(process.execPath, [tscBinPath, "-p", path.join(projectDir, "tsconfig.json"), "--noEmit"]); console.log("Building the consumer fixture"); await run(process.execPath, [viteBinPath, "build"], { cwd: projectDir }); succeeded = true; console.log("Registry consumer smoke test passed."); } catch (error) { console.error(`Registry consumer smoke test failed. Fixture preserved at ${projectDir}`); throw error; } finally { await cleanup(); } } await main();