momo-ai 1.0.20 → 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.
- package/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.claude/skills/algorithmic-art/SKILL.md +405 -0
- package/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.claude/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/.cursor/mcp.json +17 -0
- package/.obsidian/app.json +1 -0
- package/.obsidian/appearance.json +4 -0
- package/.obsidian/community-plugins.json +4 -0
- package/.obsidian/core-plugins.json +33 -0
- package/.obsidian/plugins/ai-agent/main.js +98495 -0
- package/.obsidian/plugins/ai-agent/manifest.json +11 -0
- package/.obsidian/plugins/ai-agent/styles.css +806 -0
- package/.obsidian/plugins/dataview/main.js +20876 -0
- package/.obsidian/plugins/dataview/manifest.json +11 -0
- package/.obsidian/plugins/dataview/styles.css +141 -0
- package/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
- package/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
- package/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
- package/.obsidian/plugins/templater-obsidian/main.js +45 -0
- package/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
- package/.obsidian/plugins/templater-obsidian/styles.css +226 -0
- package/.obsidian/plugins/terminal/main.js +200 -0
- package/.obsidian/plugins/terminal/manifest.json +14 -0
- package/.obsidian/plugins/terminal/styles.css +32 -0
- package/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
- package/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
- package/.obsidian/themes/Things/manifest.json +7 -0
- package/.obsidian/themes/Things/theme.css +1628 -0
- package/.obsidian/workspace.json +196 -0
- package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.trae/skills/algorithmic-art/SKILL.md +405 -0
- package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
- package/.trae/skills/frontend-design/LICENSE.txt +177 -0
- package/.trae/skills/frontend-design/SKILL.md +42 -0
- package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/README.md +12 -148
- package/docs/images/logo.jpeg +0 -0
- package/docs/images/r2mo-lain.png +0 -0
- package/install.sh +1 -0
- package/package.json +15 -11
- package/skills/r2mo-rad-domain/SKILL.md +70 -0
- package/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/src/_mcp/skills-server.mjs +70 -0
- package/src/_skill/repositories.json +22 -0
- package/src/_template/LAIN/.obsidian/app.json +1 -0
- package/src/_template/LAIN/.obsidian/appearance.json +10 -0
- package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
- package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
- package/src/_template/LAIN/.obsidian/types.json +28 -0
- package/src/_template/LAIN/.obsidian/workspace.json +184 -0
- package/src/_template/LAIN/AGENTS.md +170 -16
- package/src/_template/R2MO/domain-enhance.md +10 -0
- package/src/commander/app.json +13 -0
- package/src/commander/apply.json +13 -0
- package/src/commander/ask.json +6 -0
- package/src/commander/docs.json +13 -0
- package/src/commander/domain.json +19 -0
- package/src/commander/help.json +5 -0
- package/src/commander/init.json +1 -1
- package/src/commander/mcp.json +13 -0
- package/src/commander/mmr0.json +6 -0
- package/src/commander/mmr2.json +6 -0
- package/src/commander/open.json +8 -2
- package/src/executor/executeApp.js +133 -0
- package/src/executor/executeApply.js +611 -0
- package/src/executor/executeAsk.js +274 -0
- package/src/executor/executeDocs.js +498 -0
- package/src/executor/executeDomain.js +293 -0
- package/src/executor/executeEnv.js +48 -38
- package/src/executor/executeHelp.js +77 -16
- package/src/executor/executeInit.js +176 -346
- package/src/executor/executeMcp.js +363 -0
- package/src/executor/executeMmr0.js +488 -0
- package/src/executor/executeMmr2.js +880 -0
- package/src/executor/executeOpen.js +144 -125
- package/src/executor/index.js +17 -39
- package/src/momo.js +2 -1
- package/src/python/r2mo_proto.py +418 -0
- package/src/python/r2mo_proto_database.py +369 -0
- package/src/python/r2mo_proto_domain.py +458 -0
- package/src/utils/momo-args.js +39 -0
- package/src/utils/momo-file-utils.js +75 -0
- package/src/utils/momo-menu.js +84 -0
- package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
- package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
- package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
- package/src/_template/LAIN/changes/proposal.md +0 -39
- package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
- package/src/_template/LAIN/changes/tasks.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
- package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
- package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
- package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
- package/src/_template/LAIN/specification/project-model.md +0 -13
- package/src/_template/LAIN/specification/project.md +0 -73
- package/src/_template/LAIN/specification/requirement.md +0 -25
- package/src/commander/actor.json +0 -12
- package/src/commander/actors.json +0 -6
- package/src/commander/add.json +0 -12
- package/src/commander/agent.json +0 -12
- package/src/commander/agentcfg.json +0 -5
- package/src/commander/archive.json +0 -12
- package/src/commander/commit.json +0 -12
- package/src/commander/console.json +0 -7
- package/src/commander/lain.json +0 -7
- package/src/commander/list.json +0 -7
- package/src/commander/plan.json +0 -12
- package/src/commander/project.json +0 -12
- package/src/commander/pull.json +0 -6
- package/src/commander/push.json +0 -6
- package/src/commander/repo.json +0 -18
- package/src/commander/run.json +0 -18
- package/src/commander/show.json +0 -12
- package/src/commander/tasks.json +0 -18
- package/src/commander/unlock.json +0 -6
- package/src/commander/validate.json +0 -12
- package/src/executor/executeActor.js +0 -133
- package/src/executor/executeActors.js +0 -58
- package/src/executor/executeAdd.js +0 -307
- package/src/executor/executeAgent.js +0 -299
- package/src/executor/executeAgentCfg.js +0 -210
- package/src/executor/executeArchive.js +0 -124
- package/src/executor/executeCommit.js +0 -202
- package/src/executor/executeConsole.js +0 -142
- package/src/executor/executeList.js +0 -133
- package/src/executor/executePlan.js +0 -164
- package/src/executor/executeProject.js +0 -313
- package/src/executor/executePull.js +0 -127
- package/src/executor/executePush.js +0 -243
- package/src/executor/executeRepo.js +0 -238
- package/src/executor/executeRun.js +0 -644
- package/src/executor/executeShow.js +0 -164
- package/src/executor/executeTasks.js +0 -384
- package/src/executor/executeUnlock.js +0 -110
- package/src/executor/executeValidate.js +0 -210
|
@@ -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
|
+
};
|