| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- #!/usr/bin/env node
- import { spawnSync } from "node:child_process";
- import fs from "node:fs";
- import path from "node:path";
- import { fileURLToPath } from "node:url";
- const ROOT = path.dirname(fileURLToPath(import.meta.url));
- const MIN_NODE_MAJOR = 20;
- const REQUIRED_FILES = [
- "package.json",
- ".claude-plugin/plugin.json",
- ".mcp.json",
- "skill-package-manifest.json",
- "skills/fmode-image-set/SKILL.md",
- "mcp/src/server.mjs",
- "mcp/src/tools/image-set-run.mjs",
- "mcp/src/tools/image-set-validate.mjs",
- "mcp/src/features/image-set/image-set-runner.mjs",
- "mcp/src/features/image-set/product-vision-client.mjs",
- "mcp/src/features/image-set/validate-plan.mjs",
- "mcp/src/providers/jimeng-v4-client.mjs",
- ];
- function parseArgs(argv) {
- return {
- check: argv.includes("--check"),
- smoke: argv.includes("--smoke"),
- skipInstall: argv.includes("--skip-install"),
- help: argv.includes("--help") || argv.includes("-h"),
- };
- }
- function run(command, args, cwd = ROOT) {
- const useCmd = process.platform === "win32" && command === "npm";
- const executable = useCmd ? "cmd.exe" : command;
- const finalArgs = useCmd ? ["/d", "/s", "/c", "npm", ...args] : args;
- const child = spawnSync(executable, finalArgs, {
- cwd,
- encoding: "utf8",
- stdio: "inherit",
- maxBuffer: 1024 * 1024 * 100,
- });
- if (child.status !== 0) {
- throw new Error(`${command} ${args.join(" ")} failed with exit ${child.status}`);
- }
- }
- function readJson(relativePath) {
- return JSON.parse(fs.readFileSync(path.join(ROOT, relativePath), "utf8"));
- }
- function writeMcpConfig(rootDir = ROOT) {
- const config = {
- mcpServers: {
- "fmode-image-set": {
- command: "node",
- args: [path.join(rootDir, "mcp", "src", "server.mjs")],
- cwd: rootDir,
- },
- },
- };
- fs.writeFileSync(path.join(rootDir, ".mcp.json"), `${JSON.stringify(config, null, 2)}\n`, "utf8");
- }
- export function checkPackage() {
- const major = Number(process.versions.node.split(".")[0]);
- if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
- throw new Error(`Node.js ${MIN_NODE_MAJOR}+ is required. Current version: ${process.version}`);
- }
- const missing = REQUIRED_FILES.filter((relativePath) => !fs.existsSync(path.join(ROOT, relativePath)));
- if (missing.length) throw new Error(`Missing required files: ${missing.join(", ")}`);
- const packageJson = readJson("package.json");
- const pluginJson = readJson(".claude-plugin/plugin.json");
- const manifest = readJson("skill-package-manifest.json");
- const serverText = fs.readFileSync(path.join(ROOT, "mcp", "src", "server.mjs"), "utf8");
- const serverVersion = serverText.match(/const VERSION = "([^"]+)"/)?.[1] || "";
- const versions = new Set([packageJson.version, pluginJson.version, manifest.version, serverVersion]);
- if (versions.size !== 1) throw new Error("package, plugin, and skill manifest versions must match");
- if (pluginJson.name !== "fmode-image-set" || manifest.name !== "fmode-image-set") {
- throw new Error("plugin and skill manifest names must be fmode-image-set");
- }
- if (!manifest.skills.includes("fmode-image-set")) throw new Error("entry skill is missing from package manifest");
- return { version: packageJson.version, files: REQUIRED_FILES.length };
- }
- async function main() {
- const options = parseArgs(process.argv.slice(2));
- if (options.help) {
- console.log("Usage: node install.js [--check] [--smoke] [--skip-install]");
- return;
- }
- const checked = checkPackage();
- writeMcpConfig(ROOT);
- console.log(`Fmode Image Set ${checked.version}`);
- console.log(`Required files: OK (${checked.files})`);
- if (!options.check && !options.skipInstall) {
- run("npm", ["install", "--omit=dev", "--ignore-scripts"]);
- }
- if (options.smoke) {
- run(process.execPath, ["scripts/smoke-package.mjs"]);
- run(process.execPath, ["scripts/smoke-mcp.mjs"]);
- }
- }
- main().catch((error) => {
- console.error(`Install failed: ${error.message}`);
- process.exit(1);
- });
|