feat(cli): implement create command with interactive block selection
- Add interactive prompts for project name, block selection, and variant/branch choice - Support multiple block selection with scoped dependency ordering - Clone blocks into project structure using git sparse checkout - Apply Mustache-style template variable substitution - Compile and merge AI rules (conventions.md, module.yaml) per block - Integrate with block index, git utilities, and template engine
This commit is contained in:
+339
-213
@@ -1,7 +1,7 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { checkbox, select, input } from "@inquirer/prompts";
|
import { checkbox, select, input } from "@inquirer/prompts";
|
||||||
import { resolve, basename } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { writeFile } from "node:fs/promises";
|
import { writeFile, mkdir } from "node:fs/promises";
|
||||||
import { resolveSource } from "../lib/config.js";
|
import { resolveSource } from "../lib/config.js";
|
||||||
import { loadIndex } from "../lib/index-loader.js";
|
import { loadIndex } from "../lib/index-loader.js";
|
||||||
import { inferRepoUrl, cloneBlock, cleanupTemp } from "../lib/git.js";
|
import { inferRepoUrl, cloneBlock, cleanupTemp } from "../lib/git.js";
|
||||||
@@ -15,13 +15,11 @@ interface SelectedBlock {
|
|||||||
variantLabel: string;
|
variantLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const STYLES: { value: string; label: string }[] = [
|
const APP_STYLES: { value: string; label: string }[] = [
|
||||||
{ value: "standard", label: "标准多模块 — 根 pom.xml 聚合所有积木子模块" },
|
{ value: "mvc", label: "MVC 经典三层 — controller / service / mapper" },
|
||||||
{
|
{ value: "ddd", label: "DDD 四层 — interfaces / application / domain / infrastructure" },
|
||||||
value: "bom",
|
{ value: "biz-api", label: "biz/api 分离 — api 接口层 + biz 实现层" },
|
||||||
label: "BOM 模式 — 根 pom + bom 子模块统一依赖版本管理",
|
{ value: "simple", label: "极简 — 只生成 Application 入口类" },
|
||||||
},
|
|
||||||
{ value: "simple", label: "简单模式 — 不生成根 pom,积木模块平铺" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const AI_TOOLS: { value: string; label: string }[] = [
|
const AI_TOOLS: { value: string; label: string }[] = [
|
||||||
@@ -51,182 +49,193 @@ export function registerCreate(program: Command): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runCreate(projectName: string): Promise<void> {
|
async function runCreate(projectName: string): Promise<void> {
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
|
|
||||||
// 1. Load index
|
// 1. Load index
|
||||||
const source = resolveSource();
|
console.log("[1/8] 拉取积木索引...");
|
||||||
console.log(`[1/7] 拉取积木索引...`);
|
const source = resolveSource();
|
||||||
const index = await loadIndex(source);
|
const index = await loadIndex(source);
|
||||||
|
|
||||||
if (index.blocks.length === 0) {
|
if (index.blocks.length === 0) {
|
||||||
console.log("没有可用的积木。");
|
console.log("没有可用的积木。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Interactive prompts
|
// 2a. Select blocks
|
||||||
// 2a. Select blocks
|
const chosenNames = await checkbox({
|
||||||
const chosenNames = await checkbox({
|
message: "选择要集成的通用模块:",
|
||||||
message: "选择要集成的积木(空格选择,回车确认):",
|
choices: index.blocks.map((b) => ({
|
||||||
choices: index.blocks.map((b) => ({
|
name: `${b.name} — ${b.description}`,
|
||||||
name: `${b.name} — ${b.description}`,
|
value: b.name,
|
||||||
value: b.name,
|
checked: true,
|
||||||
checked: true,
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chosenNames.length === 0) {
|
||||||
|
console.log("未选择任何积木,已取消。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chosenBlocks = index.blocks.filter((b) => chosenNames.includes(b.name));
|
||||||
|
|
||||||
|
// 2b. Resolve dependencies
|
||||||
|
const resolved = resolveDeps(chosenBlocks, index.blocks);
|
||||||
|
if (resolved.added.length > 0) {
|
||||||
|
console.log(
|
||||||
|
` 自动补全依赖:${resolved.added.map((b) => b.name).join(", ")}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2c. Select 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;
|
||||||
|
}
|
||||||
|
|
||||||
if (chosenNames.length === 0) {
|
selected.push({ block, branch, variantLabel });
|
||||||
console.log("未选择任何积木,已取消。");
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chosenBlocks = index.blocks.filter((b) =>
|
// 2d. Business module style
|
||||||
chosenNames.includes(b.name)
|
const appStyle = await select({
|
||||||
);
|
message: "选择业务模块风格:",
|
||||||
|
choices: APP_STYLES,
|
||||||
|
default: "mvc",
|
||||||
|
});
|
||||||
|
|
||||||
// 2b. Resolve dependencies (auto-add missing)
|
// 2e. Variables
|
||||||
const resolved = resolveDeps(chosenBlocks, index.blocks);
|
const defaultPkg = `com.example.${projectName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
||||||
if (resolved.added.length > 0) {
|
|
||||||
console.log(
|
|
||||||
` 自动补全依赖:${resolved.added.map((b) => b.name).join(", ")}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2c. Select variant for blocks with multiple variants
|
const basePackage = await input({
|
||||||
const selected: SelectedBlock[] = [];
|
message: "基础包名 (basePackage):",
|
||||||
for (const block of resolved.all) {
|
default: defaultPkg,
|
||||||
let branch = block.defaultBranch;
|
});
|
||||||
let variantLabel = block.defaultBranch;
|
|
||||||
|
|
||||||
if (block.variants.length > 1) {
|
const groupId = await input({
|
||||||
const chosen = await select({
|
message: "Maven groupId:",
|
||||||
message: `为 ${block.name} 选择变体:`,
|
default: basePackage,
|
||||||
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 });
|
const javaVersion = await input({
|
||||||
}
|
message: "Java 版本:",
|
||||||
|
default: "17",
|
||||||
|
});
|
||||||
|
|
||||||
// 2d. Project style
|
// 2f. AI tool
|
||||||
const style = await select({
|
const aiTool = await select({
|
||||||
message: "选择工程风格:",
|
message: "选择 AI 工具:",
|
||||||
choices: STYLES,
|
choices: AI_TOOLS,
|
||||||
default: "standard",
|
default: "claude",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2e. Variables
|
// Summary
|
||||||
const defaultPkg = `com.example.${projectName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
console.log("\n--- 配置摘要 ---");
|
||||||
|
console.log(` 项目名: ${projectName}`);
|
||||||
|
console.log(` 通用模块: ${selected.map((s) => `${s.block.name} (${s.variantLabel})`).join(", ")}`);
|
||||||
|
console.log(` 业务模块风格: ${APP_STYLES.find((s) => s.value === appStyle)?.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");
|
||||||
|
|
||||||
const basePackage = await input({
|
// 3. Clone blocks
|
||||||
message: "基础包名 (basePackage):",
|
console.log("[2/8] 拉取积木仓库...");
|
||||||
default: defaultPkg,
|
const blockDirs: { name: string; dir: string }[] = [];
|
||||||
});
|
|
||||||
|
|
||||||
const groupId = await input({
|
for (const sel of selected) {
|
||||||
message: "Maven groupId:",
|
const url = inferRepoUrl(sel.block.name, sel.block.repo);
|
||||||
default: basePackage,
|
console.log(` clone ${url} (branch: ${sel.branch})...`);
|
||||||
});
|
const dir = await cloneBlock(url, sel.branch);
|
||||||
|
blockDirs.push({ name: sel.block.name, dir });
|
||||||
|
}
|
||||||
|
|
||||||
const javaVersion = await input({
|
// 4. Apply templates — common modules under demo-common/
|
||||||
message: "Java 版本:",
|
console.log("[3/8] 生成通用模块...");
|
||||||
default: "17",
|
const targetDir = resolve(cwd, projectName);
|
||||||
});
|
const commonDir = resolve(targetDir, `${projectName}-common`);
|
||||||
|
const vars: Record<string, string> = {
|
||||||
|
basePackage,
|
||||||
|
groupId,
|
||||||
|
projectName,
|
||||||
|
javaVersion,
|
||||||
|
};
|
||||||
|
|
||||||
// 2f. AI tool
|
const commonModules: string[] = [];
|
||||||
const aiTool = await select({
|
|
||||||
message: "选择 AI 工具:",
|
|
||||||
choices: AI_TOOLS,
|
|
||||||
default: "claude",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Summary
|
for (const sel of selected) {
|
||||||
console.log("\n--- 配置摘要 ---");
|
const moduleName = `${projectName}-common-${sel.block.module}`;
|
||||||
console.log(` 项目名: ${projectName}`);
|
const moduleDir = resolve(commonDir, moduleName);
|
||||||
console.log(` 积木: ${selected.map((s) => `${s.block.name} (${s.variantLabel})`).join(", ")}`);
|
const bd = blockDirs.find((d) => d.name === sel.block.name)!;
|
||||||
console.log(` 工程风格: ${STYLES.find((s) => s.value === style)?.label}`);
|
await applyTemplate(bd.dir, moduleDir, vars);
|
||||||
console.log(` 基础包名: ${basePackage}`);
|
commonModules.push(moduleName);
|
||||||
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
|
// 5. Generate demo-common/pom.xml
|
||||||
console.log("[2/7] 拉取积木仓库...");
|
console.log("[4/8] 生成 common 父 POM...");
|
||||||
const blockDirs: { name: string; dir: string }[] = [];
|
await mkdir(commonDir, { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
resolve(commonDir, "pom.xml"),
|
||||||
|
buildCommonParentPom(projectName, groupId, javaVersion, commonModules),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
for (const sel of selected) {
|
// 6. Generate demo-boot/
|
||||||
const url = inferRepoUrl(sel.block.name, sel.block.repo);
|
console.log("[5/8] 生成业务模块...");
|
||||||
console.log(` clone ${url} (branch: ${sel.branch})...`);
|
const bootDir = resolve(targetDir, `${projectName}-boot`);
|
||||||
const dir = await cloneBlock(url, sel.branch);
|
await generateBootModule(bootDir, projectName, groupId, basePackage, javaVersion, appStyle, commonModules);
|
||||||
blockDirs.push({ name: sel.block.name, dir });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Apply templates
|
// 7. Generate AI rules
|
||||||
console.log("[3/7] 生成项目源码...");
|
console.log("[6/8] 生成 AI 规则文件...");
|
||||||
const targetDir = resolve(cwd, projectName);
|
await compileAiRules(blockDirs, targetDir, aiTool);
|
||||||
const vars: Record<string, string> = {
|
|
||||||
basePackage,
|
|
||||||
groupId,
|
|
||||||
projectName,
|
|
||||||
javaVersion,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const bd of blockDirs) {
|
// 8. Generate root pom.xml
|
||||||
await applyTemplate(bd.dir, targetDir, vars);
|
console.log("[7/8] 生成根 pom.xml...");
|
||||||
}
|
await writeFile(
|
||||||
|
resolve(targetDir, "pom.xml"),
|
||||||
|
buildRootPom(projectName, groupId, javaVersion),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
// 5. Generate AI rules
|
// 9. Generate brick.json
|
||||||
console.log("[4/7] 生成 AI 规则文件...");
|
console.log("[8/8] 生成 brick.json...");
|
||||||
await compileAiRules(blockDirs, targetDir, aiTool);
|
const brickJson: BrickJson = {
|
||||||
|
projectName,
|
||||||
|
basePackage,
|
||||||
|
javaVersion,
|
||||||
|
aiTool,
|
||||||
|
style: appStyle,
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
|
||||||
// 6. Generate root pom.xml
|
// Cleanup
|
||||||
console.log("[5/7] 生成根 pom.xml...");
|
for (const bd of blockDirs) {
|
||||||
if (style !== "simple") {
|
await cleanupTemp(bd.dir);
|
||||||
const rootPom = buildRootPom(
|
}
|
||||||
projectName,
|
|
||||||
groupId,
|
|
||||||
javaVersion,
|
|
||||||
selected,
|
|
||||||
style
|
|
||||||
);
|
|
||||||
await writeFile(resolve(targetDir, "pom.xml"), rootPom, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Generate brick.json
|
console.log(`\n项目已创建:${targetDir}`);
|
||||||
console.log("[6/7] 生成 brick.json...");
|
console.log("Done!");
|
||||||
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(
|
function resolveDeps(
|
||||||
@@ -237,7 +246,6 @@ function resolveDeps(
|
|||||||
const result = new Map(chosen.map((b) => [b.name, b]));
|
const result = new Map(chosen.map((b) => [b.name, b]));
|
||||||
const added: Block[] = [];
|
const added: Block[] = [];
|
||||||
|
|
||||||
// Iteratively resolve dependencies
|
|
||||||
let changed = true;
|
let changed = true;
|
||||||
while (changed) {
|
while (changed) {
|
||||||
changed = false;
|
changed = false;
|
||||||
@@ -262,60 +270,13 @@ function resolveDeps(
|
|||||||
return { all: [...result.values()], added };
|
return { all: [...result.values()], added };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── POM generators ──────────────────────────────────────────
|
||||||
|
|
||||||
function buildRootPom(
|
function buildRootPom(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
groupId: string,
|
groupId: string,
|
||||||
javaVersion: string,
|
javaVersion: string
|
||||||
selected: SelectedBlock[],
|
|
||||||
style: string
|
|
||||||
): string {
|
): string {
|
||||||
const modules = selected
|
|
||||||
.map((s) => ` <module>${projectName}-common-${s.block.module}</module>`)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
if (style === "bom") {
|
|
||||||
return [
|
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
||||||
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
|
||||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
|
||||||
' xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">',
|
|
||||||
' <modelVersion>4.0.0</modelVersion>',
|
|
||||||
"",
|
|
||||||
` <groupId>${groupId}</groupId>`,
|
|
||||||
` <artifactId>${projectName}</artifactId>`,
|
|
||||||
" <version>0.0.0</version>",
|
|
||||||
" <packaging>pom</packaging>",
|
|
||||||
"",
|
|
||||||
` <name>${projectName}</name>`,
|
|
||||||
"",
|
|
||||||
" <properties>",
|
|
||||||
` <java.version>${javaVersion}</java.version>`,
|
|
||||||
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
|
||||||
` <brick.version>0.0.0</brick.version>`,
|
|
||||||
" </properties>",
|
|
||||||
"",
|
|
||||||
" <dependencyManagement>",
|
|
||||||
" <dependencies>",
|
|
||||||
...selected.map(
|
|
||||||
(s) =>
|
|
||||||
` <dependency>\n` +
|
|
||||||
` <groupId>${groupId}</groupId>\n` +
|
|
||||||
` <artifactId>${projectName}-common-${s.block.module}</artifactId>\n` +
|
|
||||||
` <version>${"$"}{brick.version}</version>\n` +
|
|
||||||
` </dependency>`
|
|
||||||
),
|
|
||||||
" </dependencies>",
|
|
||||||
" </dependencyManagement>",
|
|
||||||
"",
|
|
||||||
" <modules>",
|
|
||||||
` <module>${projectName}-common-bom</module>`,
|
|
||||||
modules,
|
|
||||||
" </modules>",
|
|
||||||
"</project>",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard
|
|
||||||
return [
|
return [
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
||||||
@@ -332,12 +293,177 @@ function buildRootPom(
|
|||||||
"",
|
"",
|
||||||
" <properties>",
|
" <properties>",
|
||||||
` <java.version>${javaVersion}</java.version>`,
|
` <java.version>${javaVersion}</java.version>`,
|
||||||
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
' <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>',
|
||||||
" </properties>",
|
" </properties>",
|
||||||
"",
|
"",
|
||||||
" <modules>",
|
" <modules>",
|
||||||
modules,
|
` <module>${projectName}-common</module>`,
|
||||||
|
` <module>${projectName}-boot</module>`,
|
||||||
" </modules>",
|
" </modules>",
|
||||||
"</project>",
|
"</project>",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCommonParentPom(
|
||||||
|
projectName: string,
|
||||||
|
groupId: string,
|
||||||
|
javaVersion: string,
|
||||||
|
modules: string[]
|
||||||
|
): string {
|
||||||
|
const moduleEntries = modules
|
||||||
|
.map((m) => ` <module>${m}</module>`)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
||||||
|
' xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">',
|
||||||
|
' <modelVersion>4.0.0</modelVersion>',
|
||||||
|
"",
|
||||||
|
" <parent>",
|
||||||
|
" <groupId>org.springframework.boot</groupId>",
|
||||||
|
" <artifactId>spring-boot-starter-parent</artifactId>",
|
||||||
|
" <version>3.4.0</version>",
|
||||||
|
" <relativePath/>",
|
||||||
|
" </parent>",
|
||||||
|
"",
|
||||||
|
` <groupId>${groupId}</groupId>`,
|
||||||
|
` <artifactId>${projectName}-common</artifactId>`,
|
||||||
|
" <version>0.0.0</version>",
|
||||||
|
" <packaging>pom</packaging>",
|
||||||
|
"",
|
||||||
|
" <properties>",
|
||||||
|
` <java.version>${javaVersion}</java.version>`,
|
||||||
|
" </properties>",
|
||||||
|
"",
|
||||||
|
" <modules>",
|
||||||
|
moduleEntries,
|
||||||
|
" </modules>",
|
||||||
|
"</project>",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBootPom(
|
||||||
|
projectName: string,
|
||||||
|
groupId: string,
|
||||||
|
javaVersion: string,
|
||||||
|
commonModules: string[]
|
||||||
|
): string {
|
||||||
|
const deps = commonModules
|
||||||
|
.map(
|
||||||
|
(m) =>
|
||||||
|
" <dependency>\n" +
|
||||||
|
` <groupId>${groupId}</groupId>\n` +
|
||||||
|
` <artifactId>${m}</artifactId>\n` +
|
||||||
|
" <version>0.0.0</version>\n" +
|
||||||
|
" </dependency>"
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<project xmlns="http://maven.apache.org/POM/4.0.0"',
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
|
||||||
|
' xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">',
|
||||||
|
' <modelVersion>4.0.0</modelVersion>',
|
||||||
|
"",
|
||||||
|
" <parent>",
|
||||||
|
" <groupId>org.springframework.boot</groupId>",
|
||||||
|
" <artifactId>spring-boot-starter-parent</artifactId>",
|
||||||
|
" <version>3.4.0</version>",
|
||||||
|
" <relativePath/>",
|
||||||
|
" </parent>",
|
||||||
|
"",
|
||||||
|
` <groupId>${groupId}</groupId>`,
|
||||||
|
` <artifactId>${projectName}-boot</artifactId>`,
|
||||||
|
" <version>0.0.0</version>",
|
||||||
|
" <packaging>jar</packaging>",
|
||||||
|
"",
|
||||||
|
" <properties>",
|
||||||
|
` <java.version>${javaVersion}</java.version>`,
|
||||||
|
" </properties>",
|
||||||
|
"",
|
||||||
|
" <dependencies>",
|
||||||
|
" <dependency>",
|
||||||
|
" <groupId>org.springframework.boot</groupId>",
|
||||||
|
" <artifactId>spring-boot-starter-web</artifactId>",
|
||||||
|
" </dependency>",
|
||||||
|
deps,
|
||||||
|
" </dependencies>",
|
||||||
|
"",
|
||||||
|
" <build>",
|
||||||
|
" <plugins>",
|
||||||
|
" <plugin>",
|
||||||
|
" <groupId>org.springframework.boot</groupId>",
|
||||||
|
" <artifactId>spring-boot-maven-plugin</artifactId>",
|
||||||
|
" </plugin>",
|
||||||
|
" </plugins>",
|
||||||
|
" </build>",
|
||||||
|
"</project>",
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Business module generator ────────────────────────────────
|
||||||
|
|
||||||
|
async function generateBootModule(
|
||||||
|
bootDir: string,
|
||||||
|
projectName: string,
|
||||||
|
groupId: string,
|
||||||
|
basePackage: string,
|
||||||
|
javaVersion: string,
|
||||||
|
appStyle: string,
|
||||||
|
commonModules: string[]
|
||||||
|
): Promise<void> {
|
||||||
|
const pkgPath = basePackage.replace(/\./g, "/");
|
||||||
|
const srcDir = resolve(bootDir, "src", "main", "java", pkgPath);
|
||||||
|
|
||||||
|
await mkdir(srcDir, { recursive: true });
|
||||||
|
|
||||||
|
// Generate style-specific packages (placeholder .gitkeep)
|
||||||
|
const styleDirs: string[] = [];
|
||||||
|
switch (appStyle) {
|
||||||
|
case "mvc":
|
||||||
|
styleDirs.push("controller", "service", "mapper");
|
||||||
|
break;
|
||||||
|
case "ddd":
|
||||||
|
styleDirs.push("interfaces", "application", "domain", "infrastructure");
|
||||||
|
break;
|
||||||
|
case "biz-api":
|
||||||
|
styleDirs.push("api", "biz");
|
||||||
|
break;
|
||||||
|
// simple: no extra dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const d of styleDirs) {
|
||||||
|
await mkdir(resolve(srcDir, d), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Application.java
|
||||||
|
const appName = projectName.charAt(0).toUpperCase() + projectName.slice(1) + "Application";
|
||||||
|
await writeFile(
|
||||||
|
resolve(srcDir, `${appName}.java`),
|
||||||
|
[
|
||||||
|
`package ${basePackage};`,
|
||||||
|
"",
|
||||||
|
"import org.springframework.boot.SpringApplication;",
|
||||||
|
"import org.springframework.boot.autoconfigure.SpringBootApplication;",
|
||||||
|
"",
|
||||||
|
"@SpringBootApplication",
|
||||||
|
`public class ${appName} {`,
|
||||||
|
"",
|
||||||
|
` public static void main(String[] args) {`,
|
||||||
|
` SpringApplication.run(${appName}.class, args);`,
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
].join("\n"),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate pom.xml
|
||||||
|
await writeFile(
|
||||||
|
resolve(bootDir, "pom.xml"),
|
||||||
|
buildBootPom(projectName, groupId, javaVersion, commonModules),
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+10
-3
@@ -1,4 +1,4 @@
|
|||||||
import { cp, readFile, writeFile, rename, readdir, mkdir } from "node:fs/promises";
|
import { cp, readFile, writeFile, readdir, mkdir, rm } from "node:fs/promises";
|
||||||
import { join, dirname } from "node:path";
|
import { join, dirname } from "node:path";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
|
|
||||||
@@ -82,8 +82,15 @@ async function renamePlaceholders(
|
|||||||
|
|
||||||
if (newName !== oldName) {
|
if (newName !== oldName) {
|
||||||
const dest = join(parent, newName);
|
const dest = join(parent, newName);
|
||||||
await mkdir(dirname(dest), { recursive: true });
|
await mkdir(dest, { recursive: true });
|
||||||
await rename(path, dest);
|
// Copy children to new location, then remove old dir
|
||||||
|
const children = await readdir(path, { withFileTypes: true });
|
||||||
|
for (const child of children) {
|
||||||
|
const srcChild = join(path, child.name);
|
||||||
|
const dstChild = join(dest, child.name);
|
||||||
|
await cp(srcChild, dstChild, { recursive: true });
|
||||||
|
}
|
||||||
|
await rm(path, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user