#!/usr/bin/env node /* global console */ import { cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { spawn } from "node:child_process"; import process from "node:process"; const scriptDir = dirname(fileURLToPath(import.meta.url)); const bundleRoot = resolve(scriptDir, ".."); const args = process.argv.slice(2); const positional = args.filter((arg) => !arg.startsWith("--")); const target = resolve(positional[0] ?? process.cwd()); const flags = new Set(args.filter((arg) => arg.startsWith("--"))); const force = flags.has("--force"); const withShowcase = flags.has("--with-showcase"); const withManaged = flags.has("--with-managed-service"); const runChecks = flags.has("--check"); async function exists(path) { try { await stat(path); return true; } catch { return false; } } async function copyDirectory(source, destination) { if ((await exists(destination)) && force) await rm(destination, { recursive: true, force: true }); await mkdir(dirname(destination), { recursive: true }); await cp(source, destination, { recursive: true, force: true }); } async function readJson(path) { return JSON.parse(await readFile(path, "utf8")); } async function writeJson(path, value) { await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); } async function run(command, commandArgs) { await new Promise((resolvePromise, reject) => { const child = spawn(command, commandArgs, { cwd: target, stdio: "inherit", shell: process.platform === "win32", }); child.on("exit", (code) => code === 0 ? resolvePromise() : reject(new Error(`${command} exited with ${code}`)), ); child.on("error", reject); }); } if (!(await exists(join(target, "packages"))) || !(await exists(join(target, "tsconfig.json")))) { throw new Error(`Target does not look like a WRNexusJS repository: ${target}`); } await copyDirectory(join(bundleRoot, "packages/captcha"), join(target, "packages/captcha")); // WRNexusJS 0.4 discovers package-owned components and browser runtimes through // package.json `wrnexus` metadata. Do not copy Captcha.wrn or captcha.js into the // UI/public directories: doing so creates stale duplicates that bypass package // upgrades and content-hashed production assets. if (withShowcase) await copyDirectory( join(bundleRoot, "examples/captcha-showcase"), join(target, "examples/captcha-showcase"), ); if (withManaged) await copyDirectory( join(bundleRoot, "services/managed-captcha"), join(target, "services/managed-captcha"), ); const tsconfigPath = join(target, "tsconfig.json"); const tsconfig = await readJson(tsconfigPath); tsconfig.compilerOptions ??= {}; tsconfig.compilerOptions.paths ??= {}; Object.assign(tsconfig.compilerOptions.paths, { "@wrnexus/captcha": ["./packages/captcha/src/index.ts"], "@wrnexus/captcha/server": ["./packages/captcha/src/server/index.ts"], "@wrnexus/captcha/client": ["./packages/captcha/src/client/index.ts"], "@wrnexus/captcha/plugin": ["./packages/captcha/src/plugin.ts"], "@wrnexus/captcha/*": ["./packages/captcha/src/*"], }); await writeJson(tsconfigPath, tsconfig); if (withManaged) { const packagePath = join(target, "package.json"); const packageJson = await readJson(packagePath); packageJson.workspaces ??= ["packages/*", "examples/*"]; if (!packageJson.workspaces.includes("services/*")) packageJson.workspaces.push("services/*"); await writeJson(packagePath, packageJson); } console.log("Installed @wrnexus/captcha."); console.log("Canonical package: packages/captcha"); console.log("Component/runtime discovery: automatic through package.json wrnexus.plugin"); console.log("No public captcha.js or UI component copy is required."); if (runChecks) { await run("bun", ["install"]); await run("bun", ["run", "scripts/generate-ui-component-reference.mjs"]); await run("bun", ["test", "packages/captcha"]); if (withManaged) await run("bun", ["test", "services/managed-captcha"]); await run("bun", ["run", "typecheck"]); if (withShowcase) await run("bun", ["run", "--cwd", "examples/captcha-showcase", "check"]); }