@gsp-bef/create-igix-backend 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.mjs +231 -0
  2. package/package.json +21 -4
  3. package/README.md +0 -3
package/index.mjs ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ /** create-igix-backend — iGIX 后端工程 pom 生成(自包含单文件)。
3
+ * 用法:交互两步(BO 目录 → 工程名)或命令行 --bo <BO 目录> --name <工程名> [--dir <落点>]
4
+ * 产物:{dir}/pom.xml + src/main/java 源码根(包目录随首个类自动创建)—— 命名 {bo}-{工程名}-backend,包根含工程名段(多工程防类冲突)。 */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import readline from "node:readline/promises";
8
+
9
+ const PARENT = { groupId: "io.iec.edp", artifactId: "caf-boot-parent", version: "1.10.0-rc.11" };
10
+ const VERSION = "0.1.0-SNAPSHOT";
11
+
12
+ const parseArgs = (argv) => {
13
+ const o = {};
14
+ for (let i = 0; i < argv.length; i++) {
15
+ if (!argv[i].startsWith("--")) continue;
16
+ const k = argv[i].slice(2);
17
+ const v = argv[i + 1];
18
+ if (v === undefined || v.startsWith("--")) o[k] = true;
19
+ else { o[k] = v.trim(); i++; }
20
+ }
21
+ return o;
22
+ };
23
+
24
+ function locateBoDir(startDir) {
25
+ if (fs.existsSync(path.join(startDir, "gspproject.json")))
26
+ return { boDir: path.dirname(startDir), projectDir: startDir, corrected: true };
27
+ for (const e of fs.readdirSync(startDir, { withFileTypes: true })) {
28
+ if (!e.isDirectory()) continue;
29
+ const child = path.join(startDir, e.name);
30
+ if (fs.existsSync(path.join(child, "gspproject.json")))
31
+ return { boDir: startDir, projectDir: child, corrected: false };
32
+ }
33
+ return null;
34
+ }
35
+
36
+ function readGspProject(projectDir) {
37
+ const gsp = JSON.parse(fs.readFileSync(path.join(projectDir, "gspproject.json"), "utf8"));
38
+ const missing = ["AppCode", "ServiceUnitCode", "BizobjectCode"].filter((k) => !gsp[k]);
39
+ if (missing.length > 0) throw new Error(`gspproject.json 缺少字段:${missing.join("、")}(${projectDir})`);
40
+ return gsp;
41
+ }
42
+
43
+ function pomXml({ groupId, name, version, pkg, parentVersion }) {
44
+ return `<?xml version="1.0" encoding="UTF-8"?>
45
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
46
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
47
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
48
+ <modelVersion>4.0.0</modelVersion>
49
+
50
+ <parent>
51
+ <groupId>${PARENT.groupId}</groupId>
52
+ <artifactId>${PARENT.artifactId}</artifactId>
53
+ <version>${parentVersion || PARENT.version}</version>
54
+ </parent>
55
+
56
+ <groupId>${groupId}</groupId>
57
+ <artifactId>${name}</artifactId>
58
+ <version>${version}</version>
59
+
60
+ <properties>
61
+ <maven.compiler.source>1.8</maven.compiler.source>
62
+ <maven.compiler.target>1.8</maven.compiler.target>
63
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
64
+ </properties>
65
+
66
+ <dependencies>
67
+ <dependency>
68
+ <groupId>io.iec.edp</groupId>
69
+ <artifactId>caf-boot-starter-base</artifactId>
70
+ </dependency>
71
+ <dependency>
72
+ <groupId>io.iec.edp</groupId>
73
+ <artifactId>caf-boot-starter-rest</artifactId>
74
+ </dependency>
75
+ <dependency>
76
+ <groupId>io.iec.edp</groupId>
77
+ <artifactId>caf-boot-starter-data</artifactId>
78
+ </dependency>
79
+ <dependency>
80
+ <groupId>io.iec.edp</groupId>
81
+ <artifactId>caf-boot-starter-session</artifactId>
82
+ </dependency>
83
+ <dependency>
84
+ <groupId>io.iec.edp</groupId>
85
+ <artifactId>caf-boot-starter-application-context</artifactId>
86
+ </dependency>
87
+ </dependencies>
88
+
89
+ <build>
90
+ <finalName>${name}</finalName>
91
+ </build>
92
+ </project>
93
+ `;
94
+ }
95
+
96
+ function readmeMd({ name, groupId, pkg }) {
97
+ return `# ${name}
98
+
99
+ \`${name}\` 是 iGIX 后端 Java 工程(由 \`create-igix-backend\` 生成)。
100
+
101
+ - groupId:\`${groupId}\`
102
+ - 包根:\`${pkg}\`
103
+ - parent:\`io.iec.edp:caf-boot-parent\`,JDK 8
104
+
105
+ ## 编码
106
+
107
+ 代码按能力加载对应 skill 生成:BEF CRUD 用 \`bef-core\`(基于 .be 建模)、REST 用 \`caf-rest-service\`、部署口径对齐用 \`caf-host-hardening\`。
108
+
109
+ 部署见 [DEPLOY-IGIX.md](DEPLOY-IGIX.md)。
110
+ `;
111
+ }
112
+
113
+ function deployMd({ app, su, bo, name }) {
114
+ return `# iGIX 部署说明
115
+
116
+ 本工程随 **BO 目录整体**经平台发布流水线交付(主路径),产物最终落到 **iGIX 安装盘**;
117
+ 本地 \`spring-boot:run\` 不是交付形态。
118
+
119
+ ## 主路径:平台发布流水线(随 BO 发布)
120
+
121
+ 经 iGIX MCP 发布(内部先做发布前校验 validatebo,**不通过不发布**):
122
+
123
+ \`\`\`text
124
+ igix_apply_publish_backend({ appCode: "${app}", suCode: "${su}", boCode: "${bo}" })
125
+ \`\`\`
126
+
127
+ 流水线:\`mvn clean install → 元数据部署 → DBO 部署 → 构件热加载\`。产物同时落三处:
128
+ Maven 仓库、\`bo-${bo}-back/publish/server/apps/${app}/${su}/libs/\`、
129
+ **\`<安装盘>/server/apps/${app}/${su}/libs/\`**(宿主真正加载的位置)。
130
+
131
+ - 同步长调用(实测约 2~5 分钟);**客户端超时先到 ≠ 失败**——以安装盘
132
+ \`server/apps/${app}/${su}/libs/\` 产物落盘(滞后约 1~2 分钟)+ 宿主日志「构件热加载完成」二次确认
133
+ - \`.be\` 元数据有变更时先经 bef-modeler 保存并同步(sysnbe)——**发布不替代元数据交付**
134
+
135
+ ## 备选:手动打包复制(不经流水线、无门禁)
136
+
137
+ 仅当该 BO 未纳入平台发布体系时使用:
138
+
139
+ \`\`\`bash
140
+ mvn package
141
+ node <caf-new-app>/scripts/package-igix.mjs --module-dir <本工程根> --app ${app} --su ${su} --alias "${name}"
142
+ # 将 dist/igix/server/apps/${app} 整体复制到 <IGIX_HOME>/server/apps/(新建目录,勿写入 oc/emc)
143
+ # 安装盘 startup 后验证
144
+ \`\`\`
145
+
146
+ ## 禁止
147
+
148
+ - 原位覆盖安装盘 \`server/apps/**/libs/*.jar\`(Windows 文件锁会拒绝;热更新走 bef-deploy 的 agent staging 投放)
149
+ - 把交付物写进安装盘已有应用目录(oc / emc / cms)
150
+ - 本地 CXF 回退作为交付物或验收标准
151
+ `;
152
+ }
153
+
154
+ async function main() {
155
+ const args = parseArgs(process.argv.slice(2));
156
+ let rl = null;
157
+ let pipedLines = null;
158
+ const clean = (s) => String(s).replace(/^\uFEFF/, "").trim();
159
+ // TTY 走 readline 交互;管道(AI 调用/echo 喂入)非 TTY——readline 在 stdin EOF 时会挂起、
160
+ // readFileSync(0) 在 Windows 管道不可靠,改为异步读完 stdin 按行消费
161
+ let pipedText = null;
162
+ const readAllStdin = () =>
163
+ new Promise((resolve) => {
164
+ const chunks = [];
165
+ process.stdin.on("data", (c) => chunks.push(c));
166
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
167
+ process.stdin.on("error", () => resolve(""));
168
+ });
169
+ const ask = async (t) => {
170
+ if (process.stdin.isTTY) {
171
+ if (!rl) rl = readline.createInterface({ input: process.stdin, output: process.stdout });
172
+ return clean(await rl.question(t));
173
+ }
174
+ if (pipedText === null) pipedText = await readAllStdin();
175
+ if (!pipedLines) pipedLines = pipedText.split(/\r?\n/).map(clean).filter(Boolean);
176
+ return pipedLines.shift() ?? "";
177
+ };
178
+
179
+ // 第 1 步:BO 目录
180
+ const boInput = args.bo ?? (await ask("请输入业务对象目录全路径(至 <APP>\\<SU>\\<BO> 层级,示例 D:\\projects\\demo\\demo-su\\order): "));
181
+
182
+ const startDir = path.resolve(boInput);
183
+ if (!fs.existsSync(startDir)) {
184
+ console.error(`路径不存在:${startDir}`);
185
+ process.exit(3);
186
+ }
187
+ const located = locateBoDir(startDir);
188
+ if (!located) {
189
+ console.error(`未在 ${startDir}(及其一级子目录)找到 gspproject.json。请先经 MCP 创建业务对象与元数据工程(igix_apply_create_bo → igix_apply_create_project)。`);
190
+ process.exit(3);
191
+ }
192
+ const { boDir, projectDir, corrected } = located;
193
+ const gsp = readGspProject(projectDir);
194
+
195
+ const app = String(gsp.AppCode).toLowerCase();
196
+ const su = String(gsp.ServiceUnitCode).toLowerCase();
197
+ const bo = String(gsp.BizobjectCode).toLowerCase();
198
+ // 第 2 步:工程名 → {bo}-{工程名}-backend
199
+ const nameInput = args.name ?? String(await ask(`请输入工程名: `));
200
+ const normalized = nameInput.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
201
+ if (!/^[a-z][a-z0-9-]*$/.test(normalized)) {
202
+ console.error(`工程名含非法字符(字母/数字/连字符以外的部分已转连字符):${nameInput} → ${normalized}`);
203
+ process.exit(3);
204
+ }
205
+ const name = `${bo}-${normalized}-backend`;
206
+ if (rl) rl.close(); const groupId = `com.inspur.gs.${app}.${su}`;
207
+ const pkg = `com.inspur.gs.${app}.${su}.${bo}.${normalized}`;
208
+ const targetDir = args.dir ? path.resolve(args.dir) : path.join(boDir, name);
209
+
210
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
211
+ console.error(`目标目录非空:${targetDir}`);
212
+ process.exit(1);
213
+ }
214
+ fs.mkdirSync(targetDir, { recursive: true });
215
+ fs.writeFileSync(path.join(targetDir, "pom.xml"), pomXml({ groupId, name, version: VERSION, pkg, parentVersion: args["parent-version"] }));
216
+ // 源码目录结构(src/main/java 或多模块拆分)不在此创建,由 AI 按场景自由发挥
217
+ fs.writeFileSync(path.join(targetDir, ".gitignore"), "target/\n*.class\n*.jar\n.idea/\n*.iml\n.vscode/\n");
218
+ fs.writeFileSync(
219
+ path.join(targetDir, "README.md"),
220
+ readmeMd({ name, groupId, pkg })
221
+ );
222
+ fs.writeFileSync(path.join(targetDir, "DEPLOY-IGIX.md"), deployMd({ app, su, bo, name }));
223
+
224
+ console.log(`[create-igix-backend] 已生成 ${targetDir}`);
225
+ console.log(`[create-igix-backend] 包根 ${pkg};随 BO ${bo} 经发布流水线交付(见 DEPLOY-IGIX.md)`);
226
+ }
227
+
228
+ main().catch((e) => {
229
+ console.error(`[create-igix-backend] ${e.message}`);
230
+ process.exit(1);
231
+ });
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "@gsp-bef/create-igix-backend",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "iGIX 后端工程生成器(交互两步:BO 目录 → 工程名;命名 {bo}-{工程名}-backend,pom 极简 DDD 自由演进,含部署指导文档)",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-igix-backend": "index.mjs"
8
+ },
9
+ "files": [
10
+ "index.mjs"
11
+ ],
12
+ "keywords": [
13
+ "igix",
14
+ "caf",
15
+ "maven",
16
+ "scaffold",
17
+ "backend"
18
+ ],
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=18"
22
+ }
23
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.