import { Command } from "commander"; import { checkbox, select, input } from "@inquirer/prompts"; import { resolve, basename } from "node:path"; import { writeFile } from "node:fs/promises"; import { resolveSource } from "../lib/config.js"; import { loadIndex } from "../lib/index-loader.js"; import { inferRepoUrl, cloneBlock, cleanupTemp } from "../lib/git.js"; import { applyTemplate } from "../lib/template.js"; import { compileAiRules } from "../lib/ai-compiler.js"; import type { Block, BrickJson } from "../types/index.js"; interface SelectedBlock { block: Block; branch: string; variantLabel: string; } const STYLES: { value: string; label: string }[] = [ { value: "standard", label: "标准多模块 — 根 pom.xml 聚合所有积木子模块" }, { value: "bom", label: "BOM 模式 — 根 pom + bom 子模块统一依赖版本管理", }, { value: "simple", label: "简单模式 — 不生成根 pom,积木模块平铺" }, ]; const AI_TOOLS: { value: string; label: string }[] = [ { value: "claude", label: "Claude Code" }, { value: "cursor", label: "Cursor" }, { value: "copilot", label: "GitHub Copilot" }, { value: "aider", label: "Aider" }, { value: "generic", label: "通用(AI_CONTEXT.md)" }, ]; export function registerCreate(program: Command): void { program .command("create ") .description("创建新项目") .action(async (projectName: string) => { try { await runCreate(projectName); } catch (err: unknown) { const msg = (err as Error).message ?? ""; if (msg.includes("User force closed") || msg.includes("ExitPromptError")) { console.log("\n已取消。"); process.exit(0); } throw err; } }); } async function runCreate(projectName: string): Promise { const cwd = process.cwd(); // 1. Load index const source = resolveSource(); console.log(`[1/7] 拉取积木索引...`); const index = await loadIndex(source); if (index.blocks.length === 0) { console.log("没有可用的积木。"); return; } // 2. Interactive prompts // 2a. Select blocks const chosenNames = await checkbox({ message: "选择要集成的积木(空格选择,回车确认):", choices: index.blocks.map((b) => ({ name: `${b.name} — ${b.description}`, value: b.name, checked: true, })), }); if (chosenNames.length === 0) { console.log("未选择任何积木,已取消。"); return; } const chosenBlocks = index.blocks.filter((b) => chosenNames.includes(b.name) ); // 2b. Resolve dependencies (auto-add missing) const resolved = resolveDeps(chosenBlocks, index.blocks); if (resolved.added.length > 0) { console.log( ` 自动补全依赖:${resolved.added.map((b) => b.name).join(", ")}` ); } // 2c. Select variant for blocks with multiple variants const selected: SelectedBlock[] = []; for (const block of resolved.all) { let branch = block.defaultBranch; let variantLabel = block.defaultBranch; if (block.variants.length > 1) { const chosen = await select({ message: `为 ${block.name} 选择变体:`, choices: block.variants.map((v) => ({ name: `${v.label} (${v.branch})`, value: v.branch, })), default: block.defaultBranch, }); branch = chosen; variantLabel = block.variants.find((v) => v.branch === chosen)?.label ?? chosen; } selected.push({ block, branch, variantLabel }); } // 2d. Project style const style = await select({ message: "选择工程风格:", choices: STYLES, default: "standard", }); // 2e. Variables const defaultPkg = `com.example.${projectName.replace(/[^a-zA-Z0-9]/g, "")}`; const basePackage = await input({ message: "基础包名 (basePackage):", default: defaultPkg, }); const groupId = await input({ message: "Maven groupId:", default: basePackage, }); const javaVersion = await input({ message: "Java 版本:", default: "17", }); // 2f. AI tool const aiTool = await select({ message: "选择 AI 工具:", choices: AI_TOOLS, default: "claude", }); // Summary console.log("\n--- 配置摘要 ---"); console.log(` 项目名: ${projectName}`); console.log(` 积木: ${selected.map((s) => `${s.block.name} (${s.variantLabel})`).join(", ")}`); console.log(` 工程风格: ${STYLES.find((s) => s.value === style)?.label}`); console.log(` 基础包名: ${basePackage}`); console.log(` groupId: ${groupId}`); console.log(` Java 版本: ${javaVersion}`); console.log(` AI 工具: ${AI_TOOLS.find((t) => t.value === aiTool)?.label}`); console.log("-----------------\n"); // 3. Clone blocks console.log("[2/7] 拉取积木仓库..."); const blockDirs: { name: string; dir: string }[] = []; for (const sel of selected) { const url = inferRepoUrl(sel.block.name, sel.block.repo); console.log(` clone ${url} (branch: ${sel.branch})...`); const dir = await cloneBlock(url, sel.branch); blockDirs.push({ name: sel.block.name, dir }); } // 4. Apply templates console.log("[3/7] 生成项目源码..."); const targetDir = resolve(cwd, projectName); const vars: Record = { basePackage, groupId, projectName, javaVersion, }; for (const bd of blockDirs) { await applyTemplate(bd.dir, targetDir, vars); } // 5. Generate AI rules console.log("[4/7] 生成 AI 规则文件..."); await compileAiRules(blockDirs, targetDir, aiTool); // 6. Generate root pom.xml console.log("[5/7] 生成根 pom.xml..."); if (style !== "simple") { const rootPom = buildRootPom( projectName, groupId, javaVersion, selected, style ); await writeFile(resolve(targetDir, "pom.xml"), rootPom, "utf-8"); } // 7. Generate brick.json console.log("[6/7] 生成 brick.json..."); const brickJson: BrickJson = { projectName, basePackage, javaVersion, aiTool, style, blocks: selected.map((s) => ({ name: s.block.name, variant: s.variantLabel, version: s.block.version, })), }; await writeFile( resolve(targetDir, "brick.json"), JSON.stringify(brickJson, null, 2), "utf-8" ); // 8. Cleanup temp dirs console.log("[7/7] 清理临时文件..."); for (const bd of blockDirs) { await cleanupTemp(bd.dir); } console.log(`\n项目已创建:${targetDir}`); console.log("Done!"); } function resolveDeps( chosen: Block[], all: Block[] ): { all: Block[]; added: Block[] } { const blockMap = new Map(all.map((b) => [b.name, b])); const result = new Map(chosen.map((b) => [b.name, b])); const added: Block[] = []; // Iteratively resolve dependencies let changed = true; while (changed) { changed = false; for (const block of [...result.values()]) { for (const depName of block.dependsOn) { if (!result.has(depName)) { const dep = blockMap.get(depName); if (dep) { result.set(depName, dep); added.push(dep); changed = true; } else { process.stderr.write( `Warning: Dependency ${depName} not found in index\n` ); } } } } } return { all: [...result.values()], added }; } function buildRootPom( projectName: string, groupId: string, javaVersion: string, selected: SelectedBlock[], style: string ): string { const modules = selected .map((s) => ` ${projectName}-common-${s.block.module}`) .join("\n"); if (style === "bom") { return [ '', '', ' 4.0.0', "", ` ${groupId}`, ` ${projectName}`, " 0.0.0", " pom", "", ` ${projectName}`, "", " ", ` ${javaVersion}`, ` UTF-8`, ` 0.0.0`, " ", "", " ", " ", ...selected.map( (s) => ` \n` + ` ${groupId}\n` + ` ${projectName}-common-${s.block.module}\n` + ` ${"$"}{brick.version}\n` + ` ` ), " ", " ", "", " ", ` ${projectName}-common-bom`, modules, " ", "", ].join("\n"); } // standard return [ '', '', ' 4.0.0', "", ` ${groupId}`, ` ${projectName}`, " 0.0.0", " pom", "", ` ${projectName}`, "", " ", ` ${javaVersion}`, ` UTF-8`, " ", "", " ", modules, " ", "", ].join("\n"); }