momo-ai 1.0.21 → 1.0.22

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 (94) hide show
  1. package/.claude/skills/r2mo-rad-lain/SKILL.md +63 -374
  2. package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
  3. package/.trae/skills/algorithmic-art/SKILL.md +405 -0
  4. package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
  5. package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
  6. package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
  7. package/.trae/skills/frontend-design/LICENSE.txt +177 -0
  8. package/.trae/skills/frontend-design/SKILL.md +42 -0
  9. package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
  10. package/README.md +9 -32
  11. package/docs/images/r2mo-lain.png +0 -0
  12. package/package.json +11 -11
  13. package/skills/r2mo-rad-domain/SKILL.md +70 -0
  14. package/src/_skill/repositories.json +9 -3
  15. package/src/_template/LAIN/.obsidian/app.json +1 -0
  16. package/src/_template/LAIN/.obsidian/appearance.json +10 -0
  17. package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
  18. package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
  19. package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
  20. package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
  21. package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
  22. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
  23. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
  24. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
  25. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
  26. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
  27. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
  28. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
  29. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
  30. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
  31. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
  32. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
  33. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
  34. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
  35. package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
  36. package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
  37. package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
  38. package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
  39. package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
  40. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
  41. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
  42. package/src/_template/LAIN/.obsidian/types.json +28 -0
  43. package/src/_template/LAIN/.obsidian/workspace.json +184 -0
  44. package/src/_template/LAIN/AGENTS.md +170 -16
  45. package/src/_template/R2MO/domain-enhance.md +10 -0
  46. package/src/commander/app.json +13 -0
  47. package/src/commander/apply.json +13 -0
  48. package/src/commander/ask.json +6 -0
  49. package/src/commander/docs.json +13 -0
  50. package/src/commander/domain.json +19 -0
  51. package/src/commander/init.json +1 -1
  52. package/src/commander/mmr0.json +6 -0
  53. package/src/commander/mmr2.json +6 -0
  54. package/src/executor/executeApp.js +133 -0
  55. package/src/executor/{executeSkills.js → executeApply.js} +166 -302
  56. package/src/executor/executeAsk.js +274 -0
  57. package/src/executor/executeDocs.js +498 -0
  58. package/src/executor/executeDomain.js +293 -0
  59. package/src/executor/executeInit.js +159 -383
  60. package/src/executor/executeMcp.js +74 -1
  61. package/src/executor/executeMmr0.js +488 -0
  62. package/src/executor/executeMmr2.js +880 -0
  63. package/src/executor/index.js +15 -3
  64. package/src/python/r2mo_proto.py +418 -0
  65. package/src/python/r2mo_proto_database.py +369 -0
  66. package/src/python/r2mo_proto_domain.py +458 -0
  67. package/src/utils/momo-menu.js +43 -13
  68. package/.claude/skills/r2mo-rad-lain/PROMPT.md +0 -281
  69. package/.claude/skills/r2mo-rad-lain/README.md +0 -192
  70. package/.claude/skills/r2mo-rad-lain/examples/argument-parsing.js +0 -154
  71. package/.claude/skills/r2mo-rad-lain/examples/file-operations.js +0 -182
  72. package/.claude/skills/r2mo-rad-lain/file-utils-api.md +0 -281
  73. package/.claude/skills/r2mo-rad-lain/menu-api.md +0 -187
  74. package/.claude/skills/r2mo-rad-lain/scripts/file-utils.js +0 -223
  75. package/.claude/skills/r2mo-rad-lain/scripts/menu.js +0 -289
  76. package/.claude/skills/r2mo-rad-lain/scripts/yaml-parser.js +0 -209
  77. package/.claude/skills/r2mo-rad-lain/templates/command.json.template +0 -13
  78. package/.claude/skills/r2mo-rad-lain/templates/executor.js.template +0 -32
  79. package/.claude/skills/r2mo-rad-lain/templates/interactive-menu.js.template +0 -221
  80. package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
  81. package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
  82. package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
  83. package/src/_template/LAIN/changes/proposal.md +0 -39
  84. package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
  85. package/src/_template/LAIN/changes/tasks.md +0 -49
  86. package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
  87. package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
  88. package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
  89. package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
  90. package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
  91. package/src/_template/LAIN/specification/project-model.md +0 -13
  92. package/src/_template/LAIN/specification/project.md +0 -73
  93. package/src/_template/LAIN/specification/requirement.md +0 -25
  94. package/src/commander/skills.json +0 -20
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
+ const { execSync } = require('child_process');
4
5
  const Ec = require('../epic');
5
6
 
6
7
  // 源 MCP 脚本路径
@@ -227,6 +228,48 @@ const _ensureGitIgnore = (projectDir) => {
227
228
  }
228
229
  };
229
230
 
231
+ /**
232
+ * 在 .r2mo/mcpserver 目录中安装依赖
233
+ * @param {string} projectDir 项目目录
234
+ */
235
+ const _installDependencies = (projectDir) => {
236
+ const mcpServerDir = path.join(projectDir, '.r2mo', 'mcpserver');
237
+
238
+ // 确保目录存在
239
+ if (!fs.existsSync(mcpServerDir)) {
240
+ fs.mkdirSync(mcpServerDir, { recursive: true });
241
+ }
242
+
243
+ // 检查是否存在 package.json,不存在则创建
244
+ const packageJsonPath = path.join(mcpServerDir, 'package.json');
245
+ if (!fs.existsSync(packageJsonPath)) {
246
+ const packageJson = {
247
+ name: 'momo-mcp-server',
248
+ version: '1.0.0',
249
+ type: 'module',
250
+ dependencies: {
251
+ '@modelcontextprotocol/sdk': '^1.0.0',
252
+ 'front-matter': '^4.0.3'
253
+ }
254
+ };
255
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
256
+ Ec.waiting(`创建 package.json: ${packageJsonPath}`);
257
+ }
258
+
259
+ // 执行 npm install
260
+ try {
261
+ Ec.waiting('正在安装 MCP 依赖...');
262
+ execSync('npm install @modelcontextprotocol/sdk front-matter', {
263
+ cwd: mcpServerDir,
264
+ stdio: 'inherit'
265
+ });
266
+ Ec.info('✓ MCP 依赖安装完成');
267
+ } catch (error) {
268
+ Ec.warn(`⚠ 依赖安装失败: ${error.message}`);
269
+ Ec.warn(' 请手动执行: cd .r2mo/mcpserver && npm install @modelcontextprotocol/sdk front-matter');
270
+ }
271
+ };
272
+
230
273
  module.exports = async (options) => {
231
274
  try {
232
275
  const projectDir = process.cwd();
@@ -256,7 +299,11 @@ module.exports = async (options) => {
256
299
  // 5. 更新 .gitignore
257
300
  _ensureGitIgnore(projectDir);
258
301
 
259
- // 6. 显示结果
302
+ // 6. 安装 MCP 依赖
303
+ console.log('');
304
+ _installDependencies(projectDir);
305
+
306
+ // 7. 显示结果
260
307
  // 将项目目录内的绝对路径转换为 {ROOT} 相对路径
261
308
  const formatPath = (absPath) => {
262
309
  if (absPath.startsWith(projectDir)) {
@@ -280,6 +327,32 @@ module.exports = async (options) => {
280
327
  console.log(` {ROOT} = 当前目录`.gray);
281
328
  console.log('');
282
329
 
330
+ // 8. 读取并打印 mcp.json 内容,复制到剪切板
331
+ try {
332
+ const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
333
+ const mcpConfigJson = JSON.parse(mcpConfigContent);
334
+ const formattedJson = JSON.stringify(mcpConfigJson, null, 2);
335
+
336
+ console.log('─'.repeat(60));
337
+ console.log(' MCP 配置文件内容'.green);
338
+ console.log('─'.repeat(60));
339
+ console.log(formattedJson);
340
+ console.log('─'.repeat(60));
341
+ console.log('');
342
+
343
+ // 复制到剪切板
344
+ try {
345
+ await Ec.outCopy(formattedJson);
346
+ Ec.info('✓ 配置内容已复制到剪切板');
347
+ } catch (copyError) {
348
+ Ec.warn(`⚠ 复制到剪切板失败: ${copyError.message}`);
349
+ Ec.warn(' 请手动复制上述配置内容');
350
+ }
351
+ } catch (readError) {
352
+ Ec.warn(`⚠ 读取配置文件失败: ${readError.message}`);
353
+ }
354
+
355
+ console.log('');
283
356
  Ec.info('🎉 配置完成!重启 Cursor 后生效');
284
357
 
285
358
  process.exit(0);
@@ -0,0 +1,488 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const Ec = require('../epic');
4
+ const fsAsync = require('fs').promises;
5
+ const { execSync } = require('child_process');
6
+ const { parseFile, exists, gitClone } = require('../utils/momo-file-utils');
7
+ const { selectSingle, selectMultiple } = require('../utils/momo-menu');
8
+ require('colors');
9
+
10
+ // 仓库配置
11
+ const SPEC_REPO_URL = 'https://gitee.com/silentbalanceyh/r2mo-spec.git';
12
+ const LOCAL_CACHE_DIR = '.r2mo/repo/r2mo-spec';
13
+
14
+ /**
15
+ * 检查命令是否可用
16
+ */
17
+ const _isCommandAvailable = (command) => {
18
+ try {
19
+ const whereCmd = process.platform === 'win32' ? 'where' : 'which';
20
+ execSync(`${whereCmd} ${command}`, { stdio: 'ignore' });
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ };
26
+
27
+ /**
28
+ * 解析 pom.xml 提取 artifactId(排除 parent 节点)
29
+ */
30
+ const _parsePomXml = (pomPath) => {
31
+ try {
32
+ const content = fs.readFileSync(pomPath, 'utf8');
33
+ const withoutParent = content.replace(/<parent>[\s\S]*?<\/parent>/gi, '');
34
+ const match = withoutParent.match(/<artifactId>([^<]+)<\/artifactId>/);
35
+ return match && match[1] ? match[1].trim() : null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ };
40
+
41
+ /**
42
+ * 检查仓库是否是最新的
43
+ */
44
+ const _isRepositoryUpToDate = (repoPath) => {
45
+ try {
46
+ execSync(`cd "${repoPath}" && git fetch --quiet origin`, { stdio: 'ignore' });
47
+ const remoteCommit = execSync(`cd "${repoPath}" && git rev-parse origin/HEAD`, { encoding: 'utf8' }).trim();
48
+ const localCommit = execSync(`cd "${repoPath}" && git rev-parse HEAD`, { encoding: 'utf8' }).trim();
49
+ return remoteCommit === localCommit;
50
+ } catch (error) {
51
+ return false;
52
+ }
53
+ };
54
+
55
+ /**
56
+ * 克隆或更新仓库
57
+ */
58
+ const _cloneOrUpdateRepository = async (projectDir) => {
59
+ const repoPath = path.join(projectDir, LOCAL_CACHE_DIR);
60
+
61
+ if (exists(repoPath)) {
62
+ Ec.waiting('正在检查仓库状态...');
63
+ if (_isRepositoryUpToDate(repoPath)) {
64
+ Ec.info('✓ 仓库已是最新版本,无需更新');
65
+ return repoPath;
66
+ }
67
+
68
+ try {
69
+ Ec.waiting('正在更新仓库...');
70
+ execSync(`cd "${repoPath}" && git pull --quiet`, { stdio: 'ignore' });
71
+ Ec.info('✓ 仓库已更新');
72
+ } catch (error) {
73
+ Ec.warn('⚠ 更新失败,尝试重新克隆...');
74
+ await fsAsync.rm(repoPath, { recursive: true, force: true });
75
+ Ec.waiting('正在克隆仓库...');
76
+ gitClone(SPEC_REPO_URL, repoPath, { shallow: true });
77
+ Ec.info('✓ 仓库已克隆');
78
+ }
79
+ } else {
80
+ Ec.waiting('正在克隆仓库...');
81
+ await fsAsync.mkdir(path.dirname(repoPath), { recursive: true });
82
+ gitClone(SPEC_REPO_URL, repoPath, { shallow: true });
83
+ Ec.info('✓ 仓库已克隆');
84
+ }
85
+
86
+ return repoPath;
87
+ };
88
+
89
+ /**
90
+ * 递归扫描所有 .md 文件
91
+ */
92
+ const _scanMarkdownFiles = (dir) => {
93
+ const files = [];
94
+
95
+ const scan = (currentDir) => {
96
+ if (!exists(currentDir)) return;
97
+
98
+ const items = fs.readdirSync(currentDir);
99
+ for (const item of items) {
100
+ const itemPath = path.join(currentDir, item);
101
+ const stat = fs.statSync(itemPath);
102
+
103
+ if (stat.isDirectory()) {
104
+ scan(itemPath);
105
+ } else if (item.endsWith('.md')) {
106
+ files.push(itemPath);
107
+ }
108
+ }
109
+ };
110
+
111
+ scan(dir);
112
+ return files;
113
+ };
114
+
115
+ /**
116
+ * 解析 MD 文件的 front-matter
117
+ */
118
+ const _parseMarkdownFile = (filePath) => {
119
+ const parsed = parseFile(filePath);
120
+ if (!parsed || !parsed.attributes) {
121
+ return null;
122
+ }
123
+
124
+ const attrs = parsed.attributes;
125
+ // 检查是否包含必需的字段(mmr0 需要 sql 字段)
126
+ if (!attrs.name || !attrs.java || !attrs.table || !attrs.sql) {
127
+ return null;
128
+ }
129
+
130
+ return {
131
+ name: attrs.name,
132
+ alias: attrs.alias || '',
133
+ identifier: attrs.identifier || '',
134
+ table: attrs.table,
135
+ java: attrs.java,
136
+ sql: attrs.sql || '',
137
+ body: parsed.body || '',
138
+ filePath: filePath
139
+ };
140
+ };
141
+
142
+ /**
143
+ * 读取 metadata.json 文件
144
+ */
145
+ const _loadMetadata = (repoPath) => {
146
+ const metadataPath = path.join(repoPath, 'metadata', 'io', 'metadata', 'domain', 'metadata.json');
147
+ if (!exists(metadataPath)) {
148
+ return {};
149
+ }
150
+
151
+ try {
152
+ const content = fs.readFileSync(metadataPath, 'utf8');
153
+ return JSON.parse(content);
154
+ } catch (error) {
155
+ Ec.warn(`⚠ 无法解析 metadata.json: ${error.message}`);
156
+ return {};
157
+ }
158
+ };
159
+
160
+ /**
161
+ * 获取目录的备注信息
162
+ */
163
+ const _getDirectoryNote = (dirName, metadata) => {
164
+ if (!metadata || typeof metadata !== 'object') {
165
+ return '';
166
+ }
167
+
168
+ // 尝试多种可能的键名格式
169
+ const possibleKeys = [
170
+ dirName,
171
+ dirName.toLowerCase(),
172
+ dirName.toUpperCase(),
173
+ path.basename(dirName)
174
+ ];
175
+
176
+ for (const key of possibleKeys) {
177
+ if (metadata[key] && typeof metadata[key] === 'object' && metadata[key].note) {
178
+ return metadata[key].note;
179
+ }
180
+ if (metadata[key] && typeof metadata[key] === 'string') {
181
+ return metadata[key];
182
+ }
183
+ }
184
+
185
+ return '';
186
+ };
187
+
188
+ /**
189
+ * 按目录组织 MD 文件
190
+ */
191
+ const _organizeByDirectory = (files, metadata = {}) => {
192
+ const dirMap = new Map();
193
+
194
+ for (const file of files) {
195
+ const dir = path.dirname(file);
196
+ if (!dirMap.has(dir)) {
197
+ dirMap.set(dir, []);
198
+ }
199
+ dirMap.get(dir).push(file);
200
+ }
201
+
202
+ return Array.from(dirMap.entries()).map(([dir, files]) => {
203
+ const dirName = path.basename(dir);
204
+ const note = _getDirectoryNote(dirName, metadata);
205
+ return {
206
+ name: dirName,
207
+ path: dir,
208
+ files: files,
209
+ note: note
210
+ };
211
+ });
212
+ };
213
+
214
+ /**
215
+ * 显示规范列表表格(四列:identifier, name, table, alias)
216
+ */
217
+ const _displaySpecsTable = (specs) => {
218
+ if (specs.length === 0) {
219
+ return;
220
+ }
221
+
222
+ const identifierWidth = 20;
223
+ const nameWidth = 25;
224
+ const tableWidth = 25;
225
+ const aliasWidth = 20;
226
+
227
+ console.log('');
228
+ console.log('─'.repeat(95));
229
+ console.log(' 规范列表'.green.bold);
230
+ console.log('─'.repeat(95));
231
+
232
+ const header = ` ${'Identifier'.padEnd(identifierWidth)}│${'Name'.padEnd(nameWidth)}│${'Table'.padEnd(tableWidth)}│${'Alias'.padEnd(aliasWidth)}`;
233
+ console.log(header.cyan);
234
+ console.log('─'.repeat(95));
235
+
236
+ specs.forEach((spec, index) => {
237
+ const identifier = (spec.identifier || '').substring(0, identifierWidth).padEnd(identifierWidth);
238
+ const name = (spec.name || '').substring(0, nameWidth).padEnd(nameWidth);
239
+ const table = (spec.table || '').substring(0, tableWidth).padEnd(tableWidth);
240
+ const alias = (spec.alias || '').substring(0, aliasWidth).padEnd(aliasWidth);
241
+ console.log(` ${identifier}│${name}│${table}│${alias}`);
242
+ });
243
+
244
+ console.log('─'.repeat(95));
245
+ console.log('');
246
+ };
247
+
248
+ /**
249
+ * 从 body 中提取 SQL 内容
250
+ */
251
+ const _extractSqlContent = (body) => {
252
+ if (!body || !body.trim()) {
253
+ return null;
254
+ }
255
+
256
+ // 查找 SQL 代码块
257
+ const sqlBlockMatch = body.match(/```(?:sql|mysql)?\s*\n([\s\S]*?)\n```/i);
258
+ if (sqlBlockMatch) {
259
+ return sqlBlockMatch[1].trim();
260
+ }
261
+
262
+ // 如果没有代码块,尝试查找 CREATE TABLE 语句
263
+ const createTableMatch = body.match(/(CREATE\s+TABLE[\s\S]*?;)/i);
264
+ if (createTableMatch) {
265
+ return createTableMatch[1].trim();
266
+ }
267
+
268
+ // 如果都没有,返回整个 body(可能整个文件就是 SQL)
269
+ return body.trim();
270
+ };
271
+
272
+ module.exports = async (options) => {
273
+ try {
274
+ const projectDir = process.cwd();
275
+
276
+ // 1. 检查 git 命令
277
+ if (!_isCommandAvailable('git')) {
278
+ Ec.error('❌ 未找到 git 命令');
279
+ Ec.waiting('请先安装 Git');
280
+ process.exit(1);
281
+ }
282
+
283
+ // 2. 确保 .r2mo/repo 在 .gitignore 中
284
+ const gitignorePath = path.join(projectDir, '.gitignore');
285
+ const ignoreEntry = '.r2mo/repo';
286
+ if (exists(gitignorePath)) {
287
+ const content = fs.readFileSync(gitignorePath, 'utf8');
288
+ const lines = content.split('\n');
289
+ const hasEntry = lines.some(line => line.trim() === ignoreEntry);
290
+ if (!hasEntry) {
291
+ const newContent = content.endsWith('\n') || content === ''
292
+ ? content + ignoreEntry + '\n'
293
+ : content + '\n' + ignoreEntry + '\n';
294
+ fs.writeFileSync(gitignorePath, newContent);
295
+ }
296
+ } else {
297
+ fs.writeFileSync(gitignorePath, ignoreEntry + '\n');
298
+ }
299
+
300
+ // 3. 克隆或更新仓库
301
+ const repoPath = await _cloneOrUpdateRepository(projectDir);
302
+
303
+ // 4. 扫描所有 MD 文件
304
+ Ec.waiting('正在扫描 Markdown 文件...');
305
+ const allMdFiles = _scanMarkdownFiles(repoPath);
306
+
307
+ if (allMdFiles.length === 0) {
308
+ Ec.error('❌ 未找到任何 Markdown 文件');
309
+ process.exit(1);
310
+ }
311
+
312
+ Ec.info(`✓ 找到 ${allMdFiles.length} 个 Markdown 文件`);
313
+
314
+ // 5. 解析所有 MD 文件
315
+ Ec.waiting('正在解析文件...');
316
+ const specs = [];
317
+ for (const file of allMdFiles) {
318
+ const spec = _parseMarkdownFile(file);
319
+ if (spec) {
320
+ specs.push(spec);
321
+ }
322
+ }
323
+
324
+ if (specs.length === 0) {
325
+ Ec.error('❌ 未找到有效的规范文件(需要包含 name, java, table, sql 字段)');
326
+ process.exit(1);
327
+ }
328
+
329
+ Ec.info(`✓ 找到 ${specs.length} 个有效规范`);
330
+
331
+ // 6. 加载 metadata.json
332
+ const metadata = _loadMetadata(repoPath);
333
+
334
+ // 7. 按目录组织并显示目录清单
335
+ const dirs = _organizeByDirectory(specs.map(s => s.filePath), metadata);
336
+
337
+ // 显示目录表格(包含备注)
338
+ console.log('');
339
+ console.log('─'.repeat(100));
340
+ console.log(' 目录列表'.green.bold);
341
+ console.log('─'.repeat(100));
342
+
343
+ const nameWidth = Math.max('目录名'.length, ...dirs.map(d => d.name.length));
344
+ const fileCountWidth = Math.max('文件数'.length, 8);
345
+ const noteWidth = 40;
346
+
347
+ const header = ` ${'目录名'.padEnd(nameWidth)}│${'文件数'.padEnd(fileCountWidth)}│${'备注'.padEnd(noteWidth)}`;
348
+ console.log(header.cyan);
349
+ console.log('─'.repeat(100));
350
+
351
+ dirs.forEach((dir, idx) => {
352
+ const name = dir.name.padEnd(nameWidth);
353
+ const fileCount = `${dir.files.length} 个文件`.padEnd(fileCountWidth);
354
+ const note = (dir.note || '').substring(0, noteWidth).padEnd(noteWidth);
355
+ console.log(` ${name}│${fileCount}│${note}`);
356
+ });
357
+
358
+ console.log('─'.repeat(100));
359
+ console.log('');
360
+
361
+ const dirItems = dirs.map((dir, idx) => ({
362
+ name: `${dir.name.padEnd(nameWidth)}│${`${dir.files.length} 个文件`.padEnd(fileCountWidth)}│${(dir.note || '').substring(0, noteWidth).padEnd(noteWidth)}`,
363
+ description: '',
364
+ index: idx,
365
+ dir: dir
366
+ }));
367
+
368
+ // 一级菜单:选择目录(组)
369
+ const selectedDir = await selectSingle(dirItems, '请选择目录(组)');
370
+ if (selectedDir === null || selectedDir === undefined) {
371
+ Ec.warn('已取消操作');
372
+ process.exit(0);
373
+ }
374
+
375
+ // 获取该目录下的所有规范
376
+ const dirSpecs = dirs[selectedDir.index].files.map(file =>
377
+ specs.find(s => s.filePath === file)
378
+ ).filter(Boolean);
379
+
380
+ if (dirSpecs.length === 0) {
381
+ Ec.warn('该目录下没有有效的规范文件');
382
+ process.exit(0);
383
+ }
384
+
385
+ // 二级菜单:显示该组中的所有模型,让用户选择
386
+ _displaySpecsTable(dirSpecs);
387
+
388
+ const specItems = dirSpecs.map((spec, idx) => ({
389
+ name: `${(spec.identifier || '').padEnd(20)}│${(spec.name || '').padEnd(25)}│${(spec.table || '').padEnd(25)}│${(spec.alias || '').padEnd(20)}`,
390
+ description: '',
391
+ index: idx
392
+ }));
393
+
394
+ const selected = await selectMultiple(specItems, `请选择要安装的规范(可多选)- 组: ${dirs[selectedDir.index].name}`);
395
+
396
+ // 处理退出情况
397
+ if (!selected || (selected.indices && selected.indices.length === 0) ||
398
+ (Array.isArray(selected) && selected.length === 0)) {
399
+ Ec.warn('已取消操作');
400
+ process.exit(0);
401
+ }
402
+
403
+ // 处理返回格式
404
+ let selectedSpecs = [];
405
+ if (selected.indices && Array.isArray(selected.indices)) {
406
+ selectedSpecs = selected.indices.map(idx => dirSpecs[idx]).filter(Boolean);
407
+ } else if (Array.isArray(selected)) {
408
+ selectedSpecs = selected.map(item => {
409
+ if (typeof item === 'object' && item.index !== undefined) {
410
+ return dirSpecs[item.index];
411
+ }
412
+ return null;
413
+ }).filter(Boolean);
414
+ } else {
415
+ Ec.error('❌ 无效的选择结果');
416
+ process.exit(1);
417
+ }
418
+
419
+ Ec.info(`✓ 已选择 ${selectedSpecs.length} 个规范`);
420
+
421
+ // 7. 解析 pom.xml 获取 artifactId
422
+ const pomPath = path.join(projectDir, 'pom.xml');
423
+ if (!exists(pomPath)) {
424
+ Ec.error('❌ 当前目录未找到 pom.xml');
425
+ Ec.waiting('请确保在 Maven 项目根目录执行此命令');
426
+ process.exit(1);
427
+ }
428
+
429
+ const artifactId = _parsePomXml(pomPath);
430
+ if (!artifactId) {
431
+ Ec.error('❌ 无法从 pom.xml 中提取 artifactId');
432
+ process.exit(1);
433
+ }
434
+
435
+ Ec.info(`✓ 项目 ID: ${artifactId}`);
436
+
437
+ // 8. 检查 domain 模块
438
+ const domainModulePath = path.join(projectDir, `${artifactId}-domain`);
439
+ if (!exists(domainModulePath)) {
440
+ Ec.error(`❌ 未找到 ${artifactId}-domain 模块`);
441
+ Ec.waiting('请先创建 domain 模块');
442
+ process.exit(1);
443
+ }
444
+
445
+ // 9. 创建 SQL 文件目录
446
+ const sqlDir = path.join(domainModulePath, 'src', 'main', 'resources', 'plugins', artifactId, 'flyway', 'MYSQL');
447
+ await fsAsync.mkdir(sqlDir, { recursive: true });
448
+
449
+ Ec.info(`✓ SQL 文件目录: ${sqlDir}`);
450
+
451
+ // 10. 处理每个选中的规范,生成 SQL 文件
452
+ for (const spec of selectedSpecs) {
453
+ Ec.waiting(`正在处理: ${spec.name} (${spec.table})...`);
454
+
455
+ // 从 body 中提取 SQL 内容
456
+ const sqlContent = _extractSqlContent(spec.body);
457
+
458
+ if (!sqlContent) {
459
+ Ec.warn(`⚠ ${spec.name} 未找到 SQL 内容,跳过`);
460
+ continue;
461
+ }
462
+
463
+ // 生成 SQL 文件名:{sql属性}__{表名}.sql
464
+ const sqlFileName = `${spec.sql}__${spec.table}.sql`;
465
+ const sqlFilePath = path.join(sqlDir, sqlFileName);
466
+
467
+ // 检查文件是否已存在
468
+ const fileExists = exists(sqlFilePath);
469
+
470
+ // 写入 SQL 文件
471
+ await fsAsync.writeFile(sqlFilePath, sqlContent, 'utf8');
472
+
473
+ if (fileExists) {
474
+ Ec.info(`✓ 已覆盖: ${sqlFileName}`.yellow);
475
+ } else {
476
+ Ec.info(`✓ 已生成: ${sqlFileName}`.green);
477
+ }
478
+ }
479
+
480
+ Ec.info('🎉 所有 SQL 文件生成完成!');
481
+ process.exit(0);
482
+
483
+ } catch (error) {
484
+ Ec.error(`❌ 执行失败: ${error.message}`);
485
+ console.error(error);
486
+ process.exit(1);
487
+ }
488
+ };