243 lines
7.0 KiB
JavaScript
243 lines
7.0 KiB
JavaScript
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 = `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Cadence UI Registry Smoke</title>
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<script type="module" src="/src/main.tsx"></script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
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<FormValues>({
|
|
defaultValues: {
|
|
email: "team@cadence.dev"
|
|
}
|
|
});
|
|
|
|
return (
|
|
<main style={{ display: "grid", gap: "1.5rem", padding: "2rem" }}>
|
|
<Form {...form}>
|
|
<FormItem name="email">
|
|
<FormLabel>Email</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Email" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</Form>
|
|
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button>Open registry smoke dialog</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogTitle>Registry smoke</DialogTitle>
|
|
<DialogDescription>
|
|
Verifies copied components typecheck and build in a consumer app.
|
|
</DialogDescription>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
const root = document.getElementById("root");
|
|
|
|
if (!root) {
|
|
throw new Error("Missing root element.");
|
|
}
|
|
|
|
createRoot(root).render(<App />);
|
|
`;
|
|
|
|
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();
|