@llryiop/avatar-boot-cli 1.0.0

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 (77) hide show
  1. package/README.md +309 -0
  2. package/bin/cli.js +3 -0
  3. package/docs/plans/2026-03-12-avatar-boot-cli-design.md +73 -0
  4. package/docs/plans/2026-03-12-avatar-boot-cli-plan.md +681 -0
  5. package/package.json +28 -0
  6. package/src/index.js +78 -0
  7. package/src/prompts.js +78 -0
  8. package/src/template.js +37 -0
  9. package/src/transform.js +172 -0
  10. package/src/utils.js +34 -0
  11. package/templates/.claude/rules/architecture-redlines.md +146 -0
  12. package/templates/.claude/rules/code-review-standards.md +137 -0
  13. package/templates/.claude/rules/coding-standards.md +56 -0
  14. package/templates/.claude/rules/git-commit.md +59 -0
  15. package/templates/.claude/rules/layered-architecture.md +201 -0
  16. package/templates/.claude/rules/mybatis-plus.md +263 -0
  17. package/templates/.claude/rules/tech-stack.md +41 -0
  18. package/templates/.claude/rules/version.md +467 -0
  19. package/templates/.claude/settings.local.json +18 -0
  20. package/templates/.claude/skills/ai-tool-guide/SKILL.md +314 -0
  21. package/templates/.claude/skills/api-design/SKILL.md +200 -0
  22. package/templates/.claude/skills/api-doc-generator/SKILL.md +380 -0
  23. package/templates/.claude/skills/api-service-module-creator/SKILL.md +1114 -0
  24. package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +243 -0
  25. package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +437 -0
  26. package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +580 -0
  27. package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +572 -0
  28. package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +901 -0
  29. package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +594 -0
  30. package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +586 -0
  31. package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +662 -0
  32. package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +1007 -0
  33. package/templates/.claude/skills/changelog-generator/SKILL.md +114 -0
  34. package/templates/.claude/skills/code-review/SKILL.md +239 -0
  35. package/templates/.claude/skills/crud-generator/SKILL.md +824 -0
  36. package/templates/.claude/skills/database-design/SKILL.md +377 -0
  37. package/templates/.claude/skills/deployment-config/SKILL.md +277 -0
  38. package/templates/.claude/skills/incident-analysis/SKILL.md +241 -0
  39. package/templates/.claude/skills/integration-test-generator/SKILL.md +496 -0
  40. package/templates/.claude/skills/prompt-engineering/SKILL.md +249 -0
  41. package/templates/.claude/skills/requirement-management/SKILL.md +244 -0
  42. package/templates/.claude/skills/security-audit/SKILL.md +330 -0
  43. package/templates/.claude/skills/test-case-design/SKILL.md +257 -0
  44. package/templates/.claude/skills/testing-workflow/SKILL.md +68 -0
  45. package/templates/.claude/skills/troubleshooting/SKILL.md +240 -0
  46. package/templates/CLAUDE.md +173 -0
  47. package/templates/README.md +303 -0
  48. package/templates/avatar-scaffold-api/pom.xml +41 -0
  49. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/api/LoginFeignClient.java +40 -0
  50. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/constant/LoginConstant.java +21 -0
  51. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/LoginRequest.java +17 -0
  52. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/request/RefreshTokenRequest.java +14 -0
  53. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/LoginResponse.java +31 -0
  54. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/dto/response/TokenInfoResponse.java +25 -0
  55. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/enums/LoginTypeEnum.java +23 -0
  56. package/templates/avatar-scaffold-api/src/main/java/com/iflytek/avatar/login/exception/LoginException.java +23 -0
  57. package/templates/avatar-scaffold-service/k8s-app/Dockerfile +14 -0
  58. package/templates/avatar-scaffold-service/k8s-app/Dockerfile-arm64 +14 -0
  59. package/templates/avatar-scaffold-service/packaging/assembly.xml +16 -0
  60. package/templates/avatar-scaffold-service/pom.xml +150 -0
  61. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/Application.java +21 -0
  62. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/config/LoginConfig.java +20 -0
  63. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/controller/LoginController.java +37 -0
  64. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/converter/LoginConverter.java +54 -0
  65. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/feign/DemoFeign.java +21 -0
  66. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserLoginEntity.java +33 -0
  67. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/entity/UserTokenEntity.java +39 -0
  68. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/repository/mapper/UserLoginMapper.java +20 -0
  69. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/LoginService.java +22 -0
  70. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/service/impl/LoginServiceImpl.java +43 -0
  71. package/templates/avatar-scaffold-service/src/main/java/com/iflytek/avatar/login/utils/LoginUtils.java +31 -0
  72. package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +29 -0
  73. package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +61 -0
  74. package/templates/avatar-scaffold-service/src/main/resources/application-prod.yaml +28 -0
  75. package/templates/avatar-scaffold-service/src/main/resources/application-test.yaml +28 -0
  76. package/templates/avatar-scaffold-service/src/main/resources/application.yaml +12 -0
  77. package/templates/pom.xml +98 -0
@@ -0,0 +1,681 @@
1
+ # Avatar Boot CLI Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Build an npx CLI tool that interactively scaffolds AvatarBoot Java microservice projects from a Git template.
6
+
7
+ **Architecture:** Node.js CLI using commander for commands, inquirer for prompts, simple string replacement on cloned Git template. Template sourced from `https://code.iflytek.com/YHZ_VoicePlatform/avatar-scaffold-project.git`.
8
+
9
+ **Tech Stack:** Node.js, commander, inquirer, chalk, ora, glob, child_process (git)
10
+
11
+ ---
12
+
13
+ ### Task 1: Initialize npm project and create CLI entry point
14
+
15
+ **Files:**
16
+ - Create: `package.json`
17
+ - Create: `bin/cli.js`
18
+ - Create: `src/index.js`
19
+
20
+ **Step 1: Create package.json**
21
+
22
+ ```json
23
+ {
24
+ "name": "avatar-boot-cli",
25
+ "version": "1.0.0",
26
+ "description": "CLI tool for scaffolding AvatarBoot Java microservice projects",
27
+ "main": "src/index.js",
28
+ "bin": {
29
+ "avatar-boot-cli": "./bin/cli.js"
30
+ },
31
+ "type": "module",
32
+ "scripts": {
33
+ "test": "node --test src/__tests__/*.test.js"
34
+ },
35
+ "keywords": ["avatar", "boot", "scaffold", "java", "microservice"],
36
+ "license": "ISC"
37
+ }
38
+ ```
39
+
40
+ **Step 2: Install dependencies**
41
+
42
+ Run: `npm install commander inquirer chalk ora glob`
43
+ Expected: node_modules created, dependencies added to package.json
44
+
45
+ **Step 3: Create bin/cli.js**
46
+
47
+ ```javascript
48
+ #!/usr/bin/env node
49
+ import { run } from '../src/index.js';
50
+ run();
51
+ ```
52
+
53
+ **Step 4: Create src/index.js (skeleton)**
54
+
55
+ ```javascript
56
+ import { Command } from 'commander';
57
+ import chalk from 'chalk';
58
+
59
+ export function run() {
60
+ const program = new Command();
61
+
62
+ program
63
+ .name('avatar-boot-cli')
64
+ .description('CLI tool for scaffolding AvatarBoot Java microservice projects')
65
+ .version('1.0.0');
66
+
67
+ program
68
+ .command('init [project-name]')
69
+ .description('Initialize a new AvatarBoot project')
70
+ .action(async (projectName) => {
71
+ console.log(chalk.blue('🚀 Avatar Boot CLI'));
72
+ console.log('Project:', projectName || 'my-avatar-service');
73
+ });
74
+
75
+ program.parse();
76
+ }
77
+ ```
78
+
79
+ **Step 5: Test CLI entry point**
80
+
81
+ Run: `chmod +x bin/cli.js && node bin/cli.js init test-project`
82
+ Expected: Output showing "Avatar Boot CLI" and "Project: test-project"
83
+
84
+ **Step 6: Commit**
85
+
86
+ ```bash
87
+ git init
88
+ git add package.json bin/cli.js src/index.js
89
+ git commit -m "feat: initialize avatar-boot-cli project with commander setup"
90
+ ```
91
+
92
+ ---
93
+
94
+ ### Task 2: Implement Git clone logic
95
+
96
+ **Files:**
97
+ - Create: `src/clone.js`
98
+
99
+ **Step 1: Create src/clone.js**
100
+
101
+ ```javascript
102
+ import { execSync } from 'child_process';
103
+ import fs from 'fs';
104
+ import path from 'path';
105
+
106
+ const REPO_URL = 'https://code.iflytek.com/YHZ_VoicePlatform/avatar-scaffold-project.git';
107
+
108
+ /**
109
+ * Fetch available tags from remote repository
110
+ * @returns {string[]} List of tag names
111
+ */
112
+ export function fetchTags() {
113
+ try {
114
+ const output = execSync(`git ls-remote --tags ${REPO_URL}`, {
115
+ encoding: 'utf-8',
116
+ timeout: 30000,
117
+ });
118
+ const tags = output
119
+ .split('\n')
120
+ .filter((line) => line.includes('refs/tags/'))
121
+ .map((line) => line.replace(/.*refs\/tags\//, '').replace(/\^{}$/, ''))
122
+ .filter((tag, index, arr) => arr.indexOf(tag) === index)
123
+ .filter((tag) => tag.length > 0)
124
+ .reverse();
125
+ return tags;
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Clone the scaffold template repository
133
+ * @param {string} targetDir - Directory to clone into
134
+ * @param {string} [tag] - Specific tag/branch to clone
135
+ */
136
+ export function cloneTemplate(targetDir, tag) {
137
+ const absoluteTarget = path.resolve(targetDir);
138
+
139
+ if (fs.existsSync(absoluteTarget)) {
140
+ throw new Error(`Directory "${targetDir}" already exists`);
141
+ }
142
+
143
+ const branchArg = tag ? `--branch ${tag}` : '';
144
+ execSync(`git clone --depth 1 ${branchArg} ${REPO_URL} "${absoluteTarget}"`, {
145
+ stdio: 'pipe',
146
+ timeout: 120000,
147
+ });
148
+
149
+ // Remove .git directory
150
+ fs.rmSync(path.join(absoluteTarget, '.git'), { recursive: true, force: true });
151
+ }
152
+ ```
153
+
154
+ **Step 2: Test clone module**
155
+
156
+ Run: `node -e "import('./src/clone.js').then(m => console.log('Tags:', m.fetchTags()))"`
157
+ Expected: List of tags (or empty array if no tags yet)
158
+
159
+ **Step 3: Commit**
160
+
161
+ ```bash
162
+ git add src/clone.js
163
+ git commit -m "feat: add git clone and tag fetching logic"
164
+ ```
165
+
166
+ ---
167
+
168
+ ### Task 3: Implement interactive prompts
169
+
170
+ **Files:**
171
+ - Create: `src/prompts.js`
172
+
173
+ **Step 1: Create src/prompts.js**
174
+
175
+ ```javascript
176
+ import inquirer from 'inquirer';
177
+ import { fetchTags } from './clone.js';
178
+
179
+ const STARTER_MODULES = [
180
+ { name: 'avatar-boot-starter-web (Web 基础设施)', value: 'web', checked: true },
181
+ { name: 'avatar-boot-starter-nacos (服务注册与配置)', value: 'nacos', checked: true },
182
+ { name: 'avatar-boot-starter-feign (服务间调用)', value: 'feign', checked: false },
183
+ { name: 'avatar-boot-starter-mysql (MySQL 数据库)', value: 'mysql', checked: false },
184
+ { name: 'avatar-boot-starter-redis (Redis 缓存)', value: 'redis', checked: false },
185
+ { name: 'avatar-boot-starter-kafka (Kafka 消息队列)', value: 'kafka', checked: false },
186
+ { name: 'avatar-boot-starter-rocketmq (RocketMQ 消息队列)', value: 'rocketmq', checked: false },
187
+ { name: 'avatar-boot-starter-job (XXL-Job 任务调度)', value: 'job', checked: false },
188
+ { name: 'avatar-boot-starter-oss (对象存储)', value: 'oss', checked: false },
189
+ ];
190
+
191
+ /**
192
+ * Run interactive prompts to collect project configuration
193
+ * @param {string} [initialName] - Project name from CLI argument
194
+ * @returns {Promise<object>} User configuration
195
+ */
196
+ export async function collectConfig(initialName) {
197
+ const tags = fetchTags();
198
+
199
+ const versionChoices = tags.length > 0
200
+ ? [...tags.map((t) => ({ name: t, value: t })), { name: 'latest (main branch)', value: '' }]
201
+ : [{ name: 'latest (main branch)', value: '' }];
202
+
203
+ const questions = [
204
+ {
205
+ type: 'input',
206
+ name: 'projectName',
207
+ message: '项目名称:',
208
+ default: initialName || 'my-avatar-service',
209
+ validate: (input) => /^[a-z][a-z0-9-]*$/.test(input) || '项目名称只能包含小写字母、数字和连字符,且以字母开头',
210
+ },
211
+ {
212
+ type: 'input',
213
+ name: 'groupId',
214
+ message: 'Maven groupId:',
215
+ default: 'com.iflytek.avatar',
216
+ validate: (input) => /^[a-z][a-z0-9.]*$/.test(input) || 'groupId 格式不正确',
217
+ },
218
+ {
219
+ type: 'list',
220
+ name: 'version',
221
+ message: 'Avatar Boot 版本:',
222
+ choices: versionChoices,
223
+ },
224
+ {
225
+ type: 'checkbox',
226
+ name: 'starters',
227
+ message: '选择 Starter 模块:',
228
+ choices: STARTER_MODULES,
229
+ },
230
+ {
231
+ type: 'confirm',
232
+ name: 'includeExample',
233
+ message: '是否生成示例代码 (login 模块):',
234
+ default: true,
235
+ },
236
+ {
237
+ type: 'input',
238
+ name: 'nacosAddr',
239
+ message: 'Nacos 地址:',
240
+ default: '127.0.0.1:8848',
241
+ when: (answers) => answers.starters.includes('nacos'),
242
+ },
243
+ {
244
+ type: 'input',
245
+ name: 'dbUrl',
246
+ message: '数据库地址 (host:port/dbname):',
247
+ default: 'localhost:3306/avatar',
248
+ when: (answers) => answers.starters.includes('mysql'),
249
+ },
250
+ {
251
+ type: 'confirm',
252
+ name: 'includeAiConfig',
253
+ message: '是否生成 .claude/ AI Agent 配置:',
254
+ default: true,
255
+ },
256
+ {
257
+ type: 'input',
258
+ name: 'serverPort',
259
+ message: '服务端口:',
260
+ default: '8888',
261
+ },
262
+ ];
263
+
264
+ return inquirer.prompt(questions);
265
+ }
266
+ ```
267
+
268
+ **Step 2: Test prompts module**
269
+
270
+ Run: `node -e "import('./src/prompts.js').then(m => m.collectConfig('test').then(c => console.log(JSON.stringify(c, null, 2))))"`
271
+ Expected: Interactive prompts appear, config object printed after completion
272
+
273
+ **Step 3: Commit**
274
+
275
+ ```bash
276
+ git add src/prompts.js
277
+ git commit -m "feat: add interactive prompts for project configuration"
278
+ ```
279
+
280
+ ---
281
+
282
+ ### Task 4: Implement template transformation logic
283
+
284
+ **Files:**
285
+ - Create: `src/transform.js`
286
+ - Create: `src/utils.js`
287
+
288
+ **Step 1: Create src/utils.js**
289
+
290
+ ```javascript
291
+ import fs from 'fs';
292
+ import path from 'path';
293
+
294
+ /**
295
+ * Convert groupId to directory path (e.g., "com.iflytek.avatar" → "com/iflytek/avatar")
296
+ */
297
+ export function groupIdToPath(groupId) {
298
+ return groupId.replace(/\./g, '/');
299
+ }
300
+
301
+ /**
302
+ * Recursively get all files in a directory
303
+ */
304
+ export function getAllFiles(dir, fileList = []) {
305
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
306
+ for (const entry of entries) {
307
+ const fullPath = path.join(dir, entry.name);
308
+ if (entry.isDirectory()) {
309
+ getAllFiles(fullPath, fileList);
310
+ } else {
311
+ fileList.push(fullPath);
312
+ }
313
+ }
314
+ return fileList;
315
+ }
316
+
317
+ /**
318
+ * Replace content in a file
319
+ */
320
+ export function replaceInFile(filePath, replacements) {
321
+ let content = fs.readFileSync(filePath, 'utf-8');
322
+ for (const [search, replace] of replacements) {
323
+ content = content.replaceAll(search, replace);
324
+ }
325
+ fs.writeFileSync(filePath, content, 'utf-8');
326
+ }
327
+
328
+ /**
329
+ * Rename directory
330
+ */
331
+ export function renameDir(oldPath, newPath) {
332
+ if (fs.existsSync(oldPath)) {
333
+ fs.mkdirSync(path.dirname(newPath), { recursive: true });
334
+ fs.renameSync(oldPath, newPath);
335
+ }
336
+ }
337
+ ```
338
+
339
+ **Step 2: Create src/transform.js**
340
+
341
+ ```javascript
342
+ import fs from 'fs';
343
+ import path from 'path';
344
+ import { groupIdToPath, getAllFiles, replaceInFile, renameDir } from './utils.js';
345
+
346
+ const DEFAULT_GROUP_ID = 'com.iflytek.avatar';
347
+ const DEFAULT_GROUP_PATH = 'com/iflytek/avatar';
348
+ const DEFAULT_ARTIFACT_ID = 'avatar-template';
349
+ const DEFAULT_APP_NAME = 'scaffold-template';
350
+ const DEFAULT_NACOS_ADDR = '172.29.242.247:8848';
351
+
352
+ // All starters that can be managed in service pom.xml
353
+ const ALL_STARTERS = ['web', 'nacos', 'feign', 'mysql', 'redis', 'kafka', 'rocketmq', 'job', 'oss'];
354
+
355
+ /**
356
+ * Transform cloned template based on user configuration
357
+ * @param {string} projectDir - Root directory of cloned template
358
+ * @param {object} config - User configuration from prompts
359
+ */
360
+ export function transformProject(projectDir, config) {
361
+ const {
362
+ projectName,
363
+ groupId = DEFAULT_GROUP_ID,
364
+ starters = ['web', 'nacos'],
365
+ includeExample = true,
366
+ nacosAddr = '127.0.0.1:8848',
367
+ dbUrl,
368
+ includeAiConfig = true,
369
+ serverPort = '8888',
370
+ } = config;
371
+
372
+ const newGroupPath = groupIdToPath(groupId);
373
+ const apiModule = `${projectName}-api`;
374
+ const serviceModule = `${projectName}-service`;
375
+
376
+ // 1. Rename module directories
377
+ renameDir(
378
+ path.join(projectDir, 'avatar-scaffold-api'),
379
+ path.join(projectDir, apiModule)
380
+ );
381
+ renameDir(
382
+ path.join(projectDir, 'avatar-scaffold-service'),
383
+ path.join(projectDir, serviceModule)
384
+ );
385
+
386
+ // 2. Rename Java package directories if groupId changed
387
+ if (groupId !== DEFAULT_GROUP_ID) {
388
+ // API module
389
+ const apiOldPkg = path.join(projectDir, apiModule, 'src/main/java', DEFAULT_GROUP_PATH);
390
+ const apiNewPkg = path.join(projectDir, apiModule, 'src/main/java', newGroupPath);
391
+ if (fs.existsSync(apiOldPkg)) {
392
+ renameDir(apiOldPkg, apiNewPkg);
393
+ // Clean up empty parent directories
394
+ cleanEmptyDirs(path.join(projectDir, apiModule, 'src/main/java'));
395
+ }
396
+
397
+ // Service module
398
+ const svcOldPkg = path.join(projectDir, serviceModule, 'src/main/java', DEFAULT_GROUP_PATH);
399
+ const svcNewPkg = path.join(projectDir, serviceModule, 'src/main/java', newGroupPath);
400
+ if (fs.existsSync(svcOldPkg)) {
401
+ renameDir(svcOldPkg, svcNewPkg);
402
+ cleanEmptyDirs(path.join(projectDir, serviceModule, 'src/main/java'));
403
+ }
404
+ }
405
+
406
+ // 3. Remove example code if not needed
407
+ if (!includeExample) {
408
+ const apiLoginDir = path.join(projectDir, apiModule, 'src/main/java', newGroupPath, 'login');
409
+ const svcLoginDir = path.join(projectDir, serviceModule, 'src/main/java', newGroupPath, 'login');
410
+ if (fs.existsSync(apiLoginDir)) fs.rmSync(apiLoginDir, { recursive: true });
411
+ if (fs.existsSync(svcLoginDir)) fs.rmSync(svcLoginDir, { recursive: true });
412
+ }
413
+
414
+ // 4. Remove .claude/ if not needed
415
+ if (!includeAiConfig) {
416
+ const claudeDir = path.join(projectDir, '.claude');
417
+ if (fs.existsSync(claudeDir)) fs.rmSync(claudeDir, { recursive: true });
418
+ const claudeMd = path.join(projectDir, 'CLAUDE.md');
419
+ if (fs.existsSync(claudeMd)) fs.unlinkSync(claudeMd);
420
+ }
421
+
422
+ // 5. Replace content in all text files
423
+ const textExtensions = ['.xml', '.java', '.yaml', '.yml', '.properties', '.md', '.txt'];
424
+ const allFiles = getAllFiles(projectDir);
425
+ const textFiles = allFiles.filter((f) =>
426
+ textExtensions.some((ext) => f.endsWith(ext))
427
+ );
428
+
429
+ const replacements = [
430
+ // Module names in pom.xml
431
+ ['avatar-scaffold-api', apiModule],
432
+ ['avatar-scaffold-service', serviceModule],
433
+ // Artifact IDs
434
+ ['avatar-template-api', `${projectName}-api`],
435
+ ['avatar-template-service', `${projectName}-service`],
436
+ ['avatar-template', projectName],
437
+ // Group ID
438
+ [DEFAULT_GROUP_ID, groupId],
439
+ // Package path in Java files
440
+ [`package ${DEFAULT_GROUP_ID}`, `package ${groupId}`],
441
+ [`import ${DEFAULT_GROUP_ID}`, `import ${groupId}`],
442
+ // Application name
443
+ [DEFAULT_APP_NAME, projectName],
444
+ // Nacos address
445
+ [DEFAULT_NACOS_ADDR, nacosAddr],
446
+ // Server port
447
+ ['port: 8888', `port: ${serverPort}`],
448
+ // Main class reference
449
+ [`${DEFAULT_GROUP_ID}.Application`, `${groupId}.Application`],
450
+ ];
451
+
452
+ for (const file of textFiles) {
453
+ replaceInFile(file, replacements);
454
+ }
455
+
456
+ // 6. Handle starter dependencies in service pom.xml
457
+ transformServicePom(path.join(projectDir, serviceModule, 'pom.xml'), starters);
458
+
459
+ // 7. Handle database URL in config
460
+ if (dbUrl && starters.includes('mysql')) {
461
+ const devYaml = path.join(projectDir, serviceModule, 'src/main/resources/application-dev.yaml');
462
+ if (fs.existsSync(devYaml)) {
463
+ replaceInFile(devYaml, [['localhost:3306/avatar', dbUrl]]);
464
+ }
465
+ }
466
+
467
+ // 8. Remove .DS_Store files
468
+ for (const file of allFiles) {
469
+ if (path.basename(file) === '.DS_Store') {
470
+ fs.unlinkSync(file);
471
+ }
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Manage starter dependencies in service pom.xml
477
+ * - Selected starters: uncomment if commented, add if missing
478
+ * - Unselected starters: comment out if present
479
+ */
480
+ function transformServicePom(pomPath, selectedStarters) {
481
+ if (!fs.existsSync(pomPath)) return;
482
+
483
+ let content = fs.readFileSync(pomPath, 'utf-8');
484
+
485
+ for (const starter of ALL_STARTERS) {
486
+ const artifactId = `avatar-boot-starter-${starter}`;
487
+ const depBlock = ` <dependency>\n <groupId>com.iflytek.avatar.boot</groupId>\n <artifactId>${artifactId}</artifactId>\n </dependency>`;
488
+ const commentedPattern = new RegExp(
489
+ `<!--\\s*<dependency>\\s*\\n\\s*<groupId>com\\.iflytek\\.avatar\\.boot</groupId>\\s*\\n\\s*<artifactId>${artifactId}</artifactId>\\s*\\n\\s*</dependency>\\s*-->`,
490
+ 's'
491
+ );
492
+ const uncommentedPattern = new RegExp(
493
+ `<dependency>\\s*\\n\\s*<groupId>com\\.iflytek\\.avatar\\.boot</groupId>\\s*\\n\\s*<artifactId>${artifactId}</artifactId>\\s*\\n\\s*</dependency>`,
494
+ 's'
495
+ );
496
+
497
+ if (selectedStarters.includes(starter)) {
498
+ // Uncomment if commented
499
+ const commentedMatch = content.match(commentedPattern);
500
+ if (commentedMatch) {
501
+ content = content.replace(commentedMatch[0], depBlock);
502
+ } else if (!uncommentedPattern.test(content)) {
503
+ // Add dependency if not present at all
504
+ const insertPoint = content.indexOf('</dependencies>');
505
+ if (insertPoint !== -1) {
506
+ content = content.slice(0, insertPoint) + depBlock + '\n' + content.slice(insertPoint);
507
+ }
508
+ }
509
+ } else {
510
+ // Comment out if uncommented
511
+ const uncommentedMatch = content.match(uncommentedPattern);
512
+ if (uncommentedMatch) {
513
+ content = content.replace(
514
+ uncommentedMatch[0],
515
+ `<!--${uncommentedMatch[0]}-->`
516
+ );
517
+ }
518
+ }
519
+ }
520
+
521
+ fs.writeFileSync(pomPath, content, 'utf-8');
522
+ }
523
+
524
+ /**
525
+ * Remove empty directories recursively (bottom-up)
526
+ */
527
+ function cleanEmptyDirs(dir) {
528
+ if (!fs.existsSync(dir)) return;
529
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
530
+ for (const entry of entries) {
531
+ if (entry.isDirectory()) {
532
+ cleanEmptyDirs(path.join(dir, entry.name));
533
+ }
534
+ }
535
+ // Re-read after potential child cleanup
536
+ const remaining = fs.readdirSync(dir);
537
+ if (remaining.length === 0) {
538
+ fs.rmdirSync(dir);
539
+ }
540
+ }
541
+ ```
542
+
543
+ **Step 3: Commit**
544
+
545
+ ```bash
546
+ git add src/utils.js src/transform.js
547
+ git commit -m "feat: add template transformation and string replacement logic"
548
+ ```
549
+
550
+ ---
551
+
552
+ ### Task 5: Wire everything together in src/index.js
553
+
554
+ **Files:**
555
+ - Modify: `src/index.js`
556
+
557
+ **Step 1: Update src/index.js with full init flow**
558
+
559
+ ```javascript
560
+ import { Command } from 'commander';
561
+ import chalk from 'chalk';
562
+ import ora from 'ora';
563
+ import { collectConfig } from './prompts.js';
564
+ import { cloneTemplate } from './clone.js';
565
+ import { transformProject } from './transform.js';
566
+
567
+ export function run() {
568
+ const program = new Command();
569
+
570
+ program
571
+ .name('avatar-boot-cli')
572
+ .description('CLI tool for scaffolding AvatarBoot Java microservice projects')
573
+ .version('1.0.0');
574
+
575
+ program
576
+ .command('init [project-name]')
577
+ .description('Initialize a new AvatarBoot project')
578
+ .action(async (projectName) => {
579
+ console.log('');
580
+ console.log(chalk.blue.bold(' Avatar Boot CLI'));
581
+ console.log(chalk.gray(' AvatarBoot Java 微服务项目脚手架工具'));
582
+ console.log('');
583
+
584
+ try {
585
+ // 1. Collect configuration
586
+ const config = await collectConfig(projectName);
587
+
588
+ console.log('');
589
+
590
+ // 2. Clone template
591
+ const spinner = ora('正在克隆模板仓库...').start();
592
+ try {
593
+ cloneTemplate(config.projectName, config.version || undefined);
594
+ spinner.succeed('模板仓库克隆完成');
595
+ } catch (err) {
596
+ spinner.fail('模板仓库克隆失败');
597
+ console.error(chalk.red(err.message));
598
+ process.exit(1);
599
+ }
600
+
601
+ // 3. Transform project
602
+ const transformSpinner = ora('正在生成项目...').start();
603
+ try {
604
+ transformProject(config.projectName, config);
605
+ transformSpinner.succeed('项目生成完成');
606
+ } catch (err) {
607
+ transformSpinner.fail('项目生成失败');
608
+ console.error(chalk.red(err.message));
609
+ process.exit(1);
610
+ }
611
+
612
+ // 4. Print success message
613
+ console.log('');
614
+ console.log(chalk.green.bold(' ✅ 项目创建成功!'));
615
+ console.log('');
616
+ console.log(chalk.white(' 开始开发:'));
617
+ console.log(chalk.cyan(` cd ${config.projectName}`));
618
+ console.log(chalk.cyan(' mvn clean compile'));
619
+ console.log('');
620
+ console.log(chalk.white(' 项目结构:'));
621
+ console.log(chalk.gray(` ${config.projectName}/`));
622
+ console.log(chalk.gray(` ├── ${config.projectName}-api/ # API 模块 (接口定义)`));
623
+ console.log(chalk.gray(` ├── ${config.projectName}-service/ # Service 模块 (业务实现)`));
624
+ console.log(chalk.gray(` └── pom.xml`));
625
+ console.log('');
626
+ } catch (err) {
627
+ if (err.name === 'ExitPromptError') {
628
+ console.log(chalk.yellow('\n 已取消'));
629
+ } else {
630
+ console.error(chalk.red(`\n 错误: ${err.message}`));
631
+ }
632
+ process.exit(1);
633
+ }
634
+ });
635
+
636
+ program.parse();
637
+ }
638
+ ```
639
+
640
+ **Step 2: Test full flow**
641
+
642
+ Run: `node bin/cli.js init`
643
+ Expected: Full interactive flow works — prompts → clone → transform → success message
644
+
645
+ **Step 3: Commit**
646
+
647
+ ```bash
648
+ git add src/index.js
649
+ git commit -m "feat: wire init command with prompts, clone, and transform"
650
+ ```
651
+
652
+ ---
653
+
654
+ ### Task 6: Add .gitignore and finalize
655
+
656
+ **Files:**
657
+ - Create: `.gitignore`
658
+
659
+ **Step 1: Create .gitignore**
660
+
661
+ ```
662
+ node_modules/
663
+ .DS_Store
664
+ ```
665
+
666
+ **Step 2: Final commit**
667
+
668
+ ```bash
669
+ git add .gitignore
670
+ git commit -m "chore: add .gitignore"
671
+ ```
672
+
673
+ ---
674
+
675
+ ## Task Dependencies
676
+
677
+ ```
678
+ Task 1 (npm + CLI entry) → Task 2 (clone) → Task 3 (prompts) → Task 4 (transform) → Task 5 (wire together) → Task 6 (finalize)
679
+ ```
680
+
681
+ All tasks are sequential — each builds on the previous.
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@llryiop/avatar-boot-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for scaffolding AvatarBoot Java microservice projects",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "avatar-boot-cli": "./bin/cli.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "test": "node --test src/__tests__/*.test.js"
12
+ },
13
+ "keywords": [
14
+ "avatar",
15
+ "boot",
16
+ "scaffold",
17
+ "java",
18
+ "microservice"
19
+ ],
20
+ "license": "ISC",
21
+ "dependencies": {
22
+ "chalk": "^5.6.2",
23
+ "commander": "^14.0.3",
24
+ "glob": "^13.0.6",
25
+ "inquirer": "^13.3.0",
26
+ "ora": "^9.3.0"
27
+ }
28
+ }