momo-ai 1.0.22 → 1.0.25
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/package.json +3 -3
- package/publish.sh +26 -0
- package/src/epic/history/ai.economy.impl.fn.execute.js +1 -1
- package/src/epic/lain.fn.execute.js +3 -1
- package/src/executor/executeDomain.js +79 -1
- package/src/executor/executeMmr0.js +7 -11
- package/src/executor/executeMmr2.js +7 -11
- package/src/python/r2mo_proto_database.py +65 -12
- package/src/python/r2mo_proto_domain.py +83 -13
- package/src/utils/momo-repo-spec.js +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "momo-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "Rachel Momo ( OpenSpec )",
|
|
5
5
|
"main": "src/momo.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"clipboard-copy": "^4.0.1",
|
|
35
35
|
"co": "^4.6.0",
|
|
36
36
|
"colors": "^1.4.0",
|
|
37
|
-
"commander": "^14.0.
|
|
37
|
+
"commander": "^14.0.3",
|
|
38
38
|
"crypto-js": "^4.2.0",
|
|
39
39
|
"dox": "^1.0.0",
|
|
40
40
|
"ejs": "^4.0.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"taffydb": "^2.7.3",
|
|
54
54
|
"underscore": "^1.13.7",
|
|
55
55
|
"uuid": "^13.0.0",
|
|
56
|
-
"zod": "^4.3.
|
|
56
|
+
"zod": "^4.3.6"
|
|
57
57
|
},
|
|
58
58
|
"homepage": "https://gitee.com/silentbalanceyh/r2mo-lain",
|
|
59
59
|
"github": "https://gitee.com/silentbalanceyh/r2mo-lain.git"
|
package/publish.sh
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# 1. 检查是否提供了提交注释 ($1)
|
|
4
|
+
if [ -z "$1" ]; then
|
|
5
|
+
echo -e "\033[31m❌ 错误: 请提供 Git 提交注释。\033[0m"
|
|
6
|
+
echo "用法: ./publish.sh \"你的提交注释\""
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# 设置发生错误时停止脚本
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
echo -e "\033[36m1️⃣ 正在升级版本号 (npm version patch)...\033[0m"
|
|
14
|
+
# --no-git-tag-version: 只修改 package.json 版本,不自动生成 git commit 和 tag
|
|
15
|
+
npm version patch --no-git-tag-version
|
|
16
|
+
|
|
17
|
+
echo -e "\033[36m2️⃣ 正在发布到 NPM (npm publish)...\033[0m"
|
|
18
|
+
# 【关键修改】显式指定 registry 为 npm 官方源,防止发布到淘宝镜像
|
|
19
|
+
npm publish --registry=https://registry.npmjs.org/
|
|
20
|
+
|
|
21
|
+
echo -e "\033[36m3️⃣ 正在提交代码并推送 (Git)...\033[0m"
|
|
22
|
+
git add .
|
|
23
|
+
git commit -m "$1"
|
|
24
|
+
git push
|
|
25
|
+
|
|
26
|
+
echo -e "\033[32m✅ 发布流程全部完成!\033[0m"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const program = require('commander');
|
|
1
|
+
const { program } = require('commander');
|
|
2
2
|
const U = require('underscore');
|
|
3
3
|
const co = require('co');
|
|
4
4
|
const Immutable = require('immutable');
|
|
@@ -96,6 +96,8 @@ const executeBody = (commanders = [], Executor = {}) => {
|
|
|
96
96
|
cmd.option(`--${option.name}`, option.description);
|
|
97
97
|
})
|
|
98
98
|
});
|
|
99
|
+
// 允许多余参数,避免 -e false / -d ./path 等被当作非法位置参数报错
|
|
100
|
+
cmd.allowExcessArguments(true);
|
|
99
101
|
cmd.action(() => co(() => executor(commander.options)));
|
|
100
102
|
});
|
|
101
103
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
const { spawn } = require('child_process');
|
|
1
|
+
const { spawn, execSync } = require('child_process');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const fsAsync = require('fs').promises;
|
|
4
5
|
const Ec = require('../epic');
|
|
5
6
|
const { parseOptional } = require('../utils/momo-args');
|
|
7
|
+
const { exists, gitClone } = require('../utils/momo-file-utils');
|
|
8
|
+
const { SPEC_REPO_URL, LOCAL_CACHE_DIR, GITIGNORE_ENTRY } = require('../utils/momo-repo-spec');
|
|
6
9
|
|
|
7
10
|
// 脚本路径(相对于项目根目录)
|
|
8
11
|
const SCRIPT_PATH_DOMAIN = 'src/python/r2mo_proto_domain.py';
|
|
@@ -49,6 +52,58 @@ const _parsePomXml = (pomPath) => {
|
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 检查仓库是否是最新的
|
|
57
|
+
* @param {string} repoPath 仓库路径
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
const _isRepositoryUpToDate = (repoPath) => {
|
|
61
|
+
try {
|
|
62
|
+
execSync(`cd "${repoPath}" && git fetch --quiet origin`, { stdio: 'ignore' });
|
|
63
|
+
const remoteCommit = execSync(`cd "${repoPath}" && git rev-parse origin/HEAD`, { encoding: 'utf8' }).trim();
|
|
64
|
+
const localCommit = execSync(`cd "${repoPath}" && git rev-parse HEAD`, { encoding: 'utf8' }).trim();
|
|
65
|
+
return remoteCommit === localCommit;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 克隆或更新 .r2mo/repo 仓库(与 mmr0/mmr2 共享)
|
|
73
|
+
* @param {string} projectDir 项目根目录
|
|
74
|
+
* @returns {Promise<string>} 仓库路径
|
|
75
|
+
*/
|
|
76
|
+
const _cloneOrUpdateRepository = async (projectDir) => {
|
|
77
|
+
const repoPath = path.join(projectDir, LOCAL_CACHE_DIR);
|
|
78
|
+
|
|
79
|
+
if (exists(repoPath)) {
|
|
80
|
+
Ec.waiting('正在检查仓库状态...');
|
|
81
|
+
if (_isRepositoryUpToDate(repoPath)) {
|
|
82
|
+
Ec.info('✓ 仓库已是最新版本,无需更新');
|
|
83
|
+
return repoPath;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
Ec.waiting('正在更新仓库...');
|
|
87
|
+
execSync(`cd "${repoPath}" && git pull --quiet`, { stdio: 'ignore' });
|
|
88
|
+
Ec.info('✓ 仓库已更新');
|
|
89
|
+
} catch (error) {
|
|
90
|
+
Ec.warn('⚠ 更新失败,尝试重新克隆...');
|
|
91
|
+
await fsAsync.rm(repoPath, { recursive: true, force: true });
|
|
92
|
+
Ec.waiting('正在克隆仓库...');
|
|
93
|
+
await fsAsync.mkdir(path.dirname(repoPath), { recursive: true });
|
|
94
|
+
gitClone(SPEC_REPO_URL, repoPath, { shallow: true });
|
|
95
|
+
Ec.info('✓ 仓库已克隆');
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
Ec.waiting('正在克隆仓库...');
|
|
99
|
+
await fsAsync.mkdir(path.dirname(repoPath), { recursive: true });
|
|
100
|
+
gitClone(SPEC_REPO_URL, repoPath, { shallow: true });
|
|
101
|
+
Ec.info('✓ 仓库已克隆');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return repoPath;
|
|
105
|
+
};
|
|
106
|
+
|
|
52
107
|
/**
|
|
53
108
|
* 验证 Maven 项目结构
|
|
54
109
|
* @param {string} targetDir 目标目录
|
|
@@ -225,6 +280,29 @@ module.exports = async (options) => {
|
|
|
225
280
|
Ec.info(`✓ Maven 项目验证通过`);
|
|
226
281
|
Ec.info(` 项目 ID: ${validation.artifactId.cyan}`);
|
|
227
282
|
Ec.info(` 找到模块: ${validation.artifactId}-domain`.green);
|
|
283
|
+
|
|
284
|
+
// 与 mmr0/mmr2 共享 .r2mo/repo:确保仓库存在并可选更新
|
|
285
|
+
Ec.waiting('正在检查 .r2mo/repo 仓库...');
|
|
286
|
+
const gitAvailable = await _isCommandAvailable('git');
|
|
287
|
+
if (!gitAvailable) {
|
|
288
|
+
Ec.warn('⚠ 未找到 git 命令,将跳过仓库拉取;若本地无 .r2mo/repo,文档注释可能缺失');
|
|
289
|
+
} else {
|
|
290
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
291
|
+
if (exists(gitignorePath)) {
|
|
292
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
293
|
+
const lines = content.split('\n');
|
|
294
|
+
const hasEntry = lines.some(line => line.trim() === GITIGNORE_ENTRY);
|
|
295
|
+
if (!hasEntry) {
|
|
296
|
+
const newContent = content.endsWith('\n') || content === ''
|
|
297
|
+
? content + GITIGNORE_ENTRY + '\n'
|
|
298
|
+
: content + '\n' + GITIGNORE_ENTRY + '\n';
|
|
299
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
fs.writeFileSync(gitignorePath, GITIGNORE_ENTRY + '\n');
|
|
303
|
+
}
|
|
304
|
+
await _cloneOrUpdateRepository(targetDir);
|
|
305
|
+
}
|
|
228
306
|
} else {
|
|
229
307
|
// Database 模式:检查是否有 flyway 目录
|
|
230
308
|
Ec.waiting('正在检查 Flyway SQL 文件...');
|
|
@@ -5,12 +5,9 @@ const fsAsync = require('fs').promises;
|
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
6
|
const { parseFile, exists, gitClone } = require('../utils/momo-file-utils');
|
|
7
7
|
const { selectSingle, selectMultiple } = require('../utils/momo-menu');
|
|
8
|
+
const { SPEC_REPO_URL, LOCAL_CACHE_DIR, GITIGNORE_ENTRY } = require('../utils/momo-repo-spec');
|
|
8
9
|
require('colors');
|
|
9
10
|
|
|
10
|
-
// 仓库配置
|
|
11
|
-
const SPEC_REPO_URL = 'https://gitee.com/silentbalanceyh/r2mo-spec.git';
|
|
12
|
-
const LOCAL_CACHE_DIR = '.r2mo/repo/r2mo-spec';
|
|
13
|
-
|
|
14
11
|
/**
|
|
15
12
|
* 检查命令是否可用
|
|
16
13
|
*/
|
|
@@ -280,21 +277,20 @@ module.exports = async (options) => {
|
|
|
280
277
|
process.exit(1);
|
|
281
278
|
}
|
|
282
279
|
|
|
283
|
-
// 2. 确保 .r2mo/repo 在 .gitignore
|
|
280
|
+
// 2. 确保 .r2mo/repo 在 .gitignore 中(与 domain/mmr2 共享目录一致)
|
|
284
281
|
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
285
|
-
const ignoreEntry = '.r2mo/repo';
|
|
286
282
|
if (exists(gitignorePath)) {
|
|
287
283
|
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
288
284
|
const lines = content.split('\n');
|
|
289
|
-
const hasEntry = lines.some(line => line.trim() ===
|
|
285
|
+
const hasEntry = lines.some(line => line.trim() === GITIGNORE_ENTRY);
|
|
290
286
|
if (!hasEntry) {
|
|
291
|
-
const newContent = content.endsWith('\n') || content === ''
|
|
292
|
-
? content +
|
|
293
|
-
: content + '\n' +
|
|
287
|
+
const newContent = content.endsWith('\n') || content === ''
|
|
288
|
+
? content + GITIGNORE_ENTRY + '\n'
|
|
289
|
+
: content + '\n' + GITIGNORE_ENTRY + '\n';
|
|
294
290
|
fs.writeFileSync(gitignorePath, newContent);
|
|
295
291
|
}
|
|
296
292
|
} else {
|
|
297
|
-
fs.writeFileSync(gitignorePath,
|
|
293
|
+
fs.writeFileSync(gitignorePath, GITIGNORE_ENTRY + '\n');
|
|
298
294
|
}
|
|
299
295
|
|
|
300
296
|
// 3. 克隆或更新仓库
|
|
@@ -5,12 +5,9 @@ const fsAsync = require('fs').promises;
|
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
6
|
const { parseFile, exists, gitClone, scanDir } = require('../utils/momo-file-utils');
|
|
7
7
|
const { selectSingle, selectMultiple } = require('../utils/momo-menu');
|
|
8
|
+
const { SPEC_REPO_URL, LOCAL_CACHE_DIR, GITIGNORE_ENTRY } = require('../utils/momo-repo-spec');
|
|
8
9
|
require('colors');
|
|
9
10
|
|
|
10
|
-
// 仓库配置
|
|
11
|
-
const SPEC_REPO_URL = 'https://gitee.com/silentbalanceyh/r2mo-spec.git';
|
|
12
|
-
const LOCAL_CACHE_DIR = '.r2mo/repo/r2mo-spec';
|
|
13
|
-
|
|
14
11
|
// BaseEntity 中已存在的字段(不需要重复生成)
|
|
15
12
|
// 注意:需要同时检查下划线命名(SQL 格式)和驼峰命名(Java 格式)
|
|
16
13
|
const BASE_ENTITY_FIELDS = new Set([
|
|
@@ -660,21 +657,20 @@ module.exports = async (options) => {
|
|
|
660
657
|
process.exit(1);
|
|
661
658
|
}
|
|
662
659
|
|
|
663
|
-
// 2. 确保 .r2mo/repo 在 .gitignore
|
|
660
|
+
// 2. 确保 .r2mo/repo 在 .gitignore 中(与 domain/mmr0 共享目录一致)
|
|
664
661
|
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
665
|
-
const ignoreEntry = '.r2mo/repo';
|
|
666
662
|
if (exists(gitignorePath)) {
|
|
667
663
|
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
668
664
|
const lines = content.split('\n');
|
|
669
|
-
const hasEntry = lines.some(line => line.trim() ===
|
|
665
|
+
const hasEntry = lines.some(line => line.trim() === GITIGNORE_ENTRY);
|
|
670
666
|
if (!hasEntry) {
|
|
671
|
-
const newContent = content.endsWith('\n') || content === ''
|
|
672
|
-
? content +
|
|
673
|
-
: content + '\n' +
|
|
667
|
+
const newContent = content.endsWith('\n') || content === ''
|
|
668
|
+
? content + GITIGNORE_ENTRY + '\n'
|
|
669
|
+
: content + '\n' + GITIGNORE_ENTRY + '\n';
|
|
674
670
|
fs.writeFileSync(gitignorePath, newContent);
|
|
675
671
|
}
|
|
676
672
|
} else {
|
|
677
|
-
fs.writeFileSync(gitignorePath,
|
|
673
|
+
fs.writeFileSync(gitignorePath, GITIGNORE_ENTRY + '\n');
|
|
678
674
|
}
|
|
679
675
|
|
|
680
676
|
// 3. 克隆或更新仓库
|
|
@@ -2,20 +2,19 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
R2MO SQL to Protobuf Converter (
|
|
5
|
+
R2MO SQL to Protobuf Converter (V4 - Shared .r2mo/repo & Note Properties)
|
|
6
6
|
从 Flyway SQL 脚本生成 Protobuf 文件
|
|
7
7
|
更新内容:
|
|
8
|
-
1.
|
|
9
|
-
2.
|
|
10
|
-
3.
|
|
11
|
-
4. 【强化】仅扫描 src/main/resources 下的 flyway/MYSQL(排除 target/classes)
|
|
12
|
-
5. 【优化】逐行解析,支持多行注释过滤和行尾 -- 补充描述
|
|
8
|
+
1. 【共享】与 momo mmr0/mmr2/domain 共用 .r2mo/repo 仓库;按 message 名称检索 XApp.md
|
|
9
|
+
2. 【注释】将文档头部的笔记属性(front-matter)完整追加到 message 的注释中(与 domain 一致)
|
|
10
|
+
3. 【保留】中文注释「javaProperty」- 描述、[Java:xxx]、约束规范化等 V3 特性
|
|
13
11
|
"""
|
|
14
12
|
|
|
15
13
|
import os
|
|
16
14
|
import re
|
|
17
15
|
import sys
|
|
18
16
|
import argparse
|
|
17
|
+
import yaml
|
|
19
18
|
|
|
20
19
|
# ================= 1. 配置与映射表 =================
|
|
21
20
|
|
|
@@ -55,6 +54,49 @@ def extract_sql_type(type_def):
|
|
|
55
54
|
match = re.match(r'([A-Z]+)', type_def.upper())
|
|
56
55
|
return match.group(1) if match else 'VARCHAR'
|
|
57
56
|
|
|
57
|
+
|
|
58
|
+
def _find_repo_doc(message_name, project_root):
|
|
59
|
+
"""
|
|
60
|
+
从 .r2mo/repo/r2mo-spec 仓库中查找 {message_name}.md 文档。
|
|
61
|
+
返回: (front-matter dict, 命中的文件名) 或 (None, None)
|
|
62
|
+
"""
|
|
63
|
+
repo_path = os.path.join(project_root, '.r2mo', 'repo', 'r2mo-spec')
|
|
64
|
+
if not os.path.isdir(repo_path):
|
|
65
|
+
return None, None
|
|
66
|
+
for root, dirs, files in os.walk(repo_path):
|
|
67
|
+
for f in files:
|
|
68
|
+
if f.lower() == f'{message_name.lower()}.md':
|
|
69
|
+
md_file = os.path.join(root, f)
|
|
70
|
+
try:
|
|
71
|
+
with open(md_file, 'r', encoding='utf-8') as fp:
|
|
72
|
+
content = fp.read()
|
|
73
|
+
m = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
|
|
74
|
+
if m:
|
|
75
|
+
yaml_text = m.group(1)
|
|
76
|
+
attrs = yaml.safe_load(yaml_text)
|
|
77
|
+
if isinstance(attrs, dict):
|
|
78
|
+
return attrs, os.path.basename(md_file)
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
return None, None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _build_message_comment(attrs):
|
|
85
|
+
"""
|
|
86
|
+
将文档头部的所有 front-matter 属性解析到注释中,每个属性一行,格式为 key: value。
|
|
87
|
+
与 r2mo_proto_domain 保持一致。
|
|
88
|
+
"""
|
|
89
|
+
if not attrs or not isinstance(attrs, dict):
|
|
90
|
+
return []
|
|
91
|
+
lines = []
|
|
92
|
+
for k, v in attrs.items():
|
|
93
|
+
if v is None or v == '':
|
|
94
|
+
continue
|
|
95
|
+
if isinstance(v, (list, dict)):
|
|
96
|
+
v = str(v)[:80]
|
|
97
|
+
lines.append(f"{k}: {v}")
|
|
98
|
+
return lines
|
|
99
|
+
|
|
58
100
|
def extract_comment_parts(comment):
|
|
59
101
|
"""
|
|
60
102
|
从 COMMENT 提取:Java属性名(「xxx」)、中文描述(- 后面的内容)
|
|
@@ -203,26 +245,37 @@ def parse_create_table(sql_content):
|
|
|
203
245
|
'fields': fields
|
|
204
246
|
}
|
|
205
247
|
|
|
206
|
-
def generate_proto_from_table(table_info, java_package="domain"):
|
|
248
|
+
def generate_proto_from_table(table_info, java_package="domain", project_root=None):
|
|
207
249
|
"""
|
|
208
250
|
从表信息生成 Proto 文件内容。
|
|
209
|
-
java_package: 与 jOOQ 生成的 domain pojos
|
|
251
|
+
java_package: 与 jOOQ 生成的 domain pojos 包一致。
|
|
252
|
+
project_root: 项目根目录,用于在 .r2mo/repo 中检索 {MessageName}.md 并追加笔记属性到注释;默认 getcwd()。
|
|
210
253
|
"""
|
|
211
254
|
table_name = table_info['table_name']
|
|
212
255
|
fields = table_info['fields']
|
|
213
256
|
message_name = snake_to_pascal(table_name)
|
|
214
|
-
|
|
257
|
+
root = project_root if project_root is not None else os.getcwd()
|
|
258
|
+
|
|
215
259
|
lines = [
|
|
216
260
|
'syntax = "proto3";',
|
|
217
261
|
'package domain;',
|
|
218
262
|
'',
|
|
219
263
|
f'// Generated from {java_package}.{message_name}',
|
|
264
|
+
]
|
|
265
|
+
# 从 .r2mo/repo 检索 {MessageName}.md,将文档头部的笔记属性追加到 message 注释(与 domain 一致)
|
|
266
|
+
doc_attrs, found_md = _find_repo_doc(message_name, root)
|
|
267
|
+
if doc_attrs and found_md:
|
|
268
|
+
for comment_line in _build_message_comment(doc_attrs):
|
|
269
|
+
if comment_line:
|
|
270
|
+
lines.append(f'// {comment_line}')
|
|
271
|
+
print(f" ✓ {message_name}: 已从 {found_md} 追加笔记属性到注释")
|
|
272
|
+
lines.extend([
|
|
220
273
|
f'option java_package = "{java_package}";',
|
|
221
274
|
'option java_multiple_files = true;',
|
|
222
275
|
'',
|
|
223
276
|
f'message {message_name} {{'
|
|
224
|
-
]
|
|
225
|
-
|
|
277
|
+
])
|
|
278
|
+
|
|
226
279
|
for idx, field in enumerate(fields, 1):
|
|
227
280
|
comment_suffix = f' // {field["comment"]}' if field["comment"] else ''
|
|
228
281
|
lines.append(f' {field["type"]} {field["name"]} = {idx};{comment_suffix}')
|
|
@@ -300,7 +353,7 @@ def main():
|
|
|
300
353
|
sys.exit(1)
|
|
301
354
|
|
|
302
355
|
# 查找所有 flyway 目录
|
|
303
|
-
print(f"🚀 R2MO Proto Generator (
|
|
356
|
+
print(f"🚀 R2MO Proto Generator (V4 - Flyway SQL Mode, .r2mo/repo docs)")
|
|
304
357
|
print(f" Scanning: {input_dir}")
|
|
305
358
|
print("-" * 40)
|
|
306
359
|
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
R2MO Java Domain to Protobuf Converter (
|
|
5
|
+
R2MO Java Domain to Protobuf Converter (V11 - Shared .r2mo/repo & Note Properties)
|
|
6
6
|
从 Java Domain 实体类生成 Protobuf 文件
|
|
7
7
|
更新内容:
|
|
8
|
-
1.
|
|
9
|
-
2.
|
|
10
|
-
3.
|
|
11
|
-
4.
|
|
12
|
-
5. 保留所有 V8 特性(jOOQ Getter 提取、BaseEntity 补全、自动导入)
|
|
8
|
+
1. 【共享】与 momo mmr0/mmr2 共用 .r2mo/repo 仓库;momo domain 执行前会克隆/更新该仓库
|
|
9
|
+
2. 【检索】在仓库中按 message 名称(如 XApp)查找对应 XApp.md 文件
|
|
10
|
+
3. 【注释】将文档头部的笔记属性(front-matter)完整追加到 message 的注释中(首行主描述,其余 key: value)
|
|
11
|
+
4. 【保留】Java 属性名 [Java:xxx]、约束规范化、V9/V10 特性
|
|
13
12
|
"""
|
|
14
13
|
|
|
15
14
|
import os
|
|
@@ -17,6 +16,7 @@ import re
|
|
|
17
16
|
import sys
|
|
18
17
|
import argparse
|
|
19
18
|
import xml.etree.ElementTree as ET
|
|
19
|
+
import yaml
|
|
20
20
|
|
|
21
21
|
# ================= 1. 配置与映射表 =================
|
|
22
22
|
|
|
@@ -73,6 +73,70 @@ def get_package_name(content):
|
|
|
73
73
|
match = re.search(r'package\s+([\w\.]+);', content)
|
|
74
74
|
return match.group(1) if match else "domain"
|
|
75
75
|
|
|
76
|
+
|
|
77
|
+
def _read_md_front_matter(md_path):
|
|
78
|
+
"""只提取 .md 文件头部 front-matter,返回 (attrs dict, 文件名) 或 (None, None)。"""
|
|
79
|
+
if not os.path.isfile(md_path):
|
|
80
|
+
return None, None
|
|
81
|
+
try:
|
|
82
|
+
with open(md_path, 'r', encoding='utf-8') as fp:
|
|
83
|
+
content = fp.read()
|
|
84
|
+
m = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
|
|
85
|
+
if m:
|
|
86
|
+
attrs = yaml.safe_load(m.group(1))
|
|
87
|
+
if isinstance(attrs, dict):
|
|
88
|
+
return attrs, os.path.basename(md_path)
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
return None, None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _find_doc_for_java(message_name, java_file_path, project_root):
|
|
95
|
+
"""
|
|
96
|
+
查找与 Java 类对应的 .md 文档,只提取头部 front-matter。
|
|
97
|
+
1)优先:与 Java 同目录的同名 .md(如 Order.java 同目录的 Order.md)
|
|
98
|
+
2)回退:.r2mo/repo/r2mo-spec 中 {message_name}.md 或去掉 Entity 的 {name}.md
|
|
99
|
+
返回: (front-matter dict, 命中的文件名) 或 (None, None)
|
|
100
|
+
"""
|
|
101
|
+
# 1)与 Java 同目录的 {ClassName}.md
|
|
102
|
+
if java_file_path and os.path.isfile(java_file_path):
|
|
103
|
+
same_dir = os.path.dirname(java_file_path)
|
|
104
|
+
same_dir_md = os.path.join(same_dir, f'{message_name}.md')
|
|
105
|
+
attrs, found = _read_md_front_matter(same_dir_md)
|
|
106
|
+
if attrs and found:
|
|
107
|
+
return attrs, found
|
|
108
|
+
# 2).r2mo/repo/r2mo-spec
|
|
109
|
+
repo_path = os.path.join(project_root, '.r2mo', 'repo', 'r2mo-spec')
|
|
110
|
+
if not os.path.isdir(repo_path):
|
|
111
|
+
return None, None
|
|
112
|
+
candidates = [f'{message_name}.md']
|
|
113
|
+
if message_name.endswith('Entity'):
|
|
114
|
+
candidates.append(f'{message_name[:-6]}.md')
|
|
115
|
+
for root, dirs, files in os.walk(repo_path):
|
|
116
|
+
for f in files:
|
|
117
|
+
if f.lower() in [c.lower() for c in candidates]:
|
|
118
|
+
md_file = os.path.join(root, f)
|
|
119
|
+
attrs, _ = _read_md_front_matter(md_file)
|
|
120
|
+
if attrs:
|
|
121
|
+
return attrs, os.path.basename(md_file)
|
|
122
|
+
return None, None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _build_message_comment(attrs):
|
|
126
|
+
"""
|
|
127
|
+
将文档头部的所有 front-matter 属性解析到注释中,每个属性一行,格式为 key: value。
|
|
128
|
+
"""
|
|
129
|
+
if not attrs or not isinstance(attrs, dict):
|
|
130
|
+
return []
|
|
131
|
+
lines = []
|
|
132
|
+
for k, v in attrs.items():
|
|
133
|
+
if v is None or v == '':
|
|
134
|
+
continue
|
|
135
|
+
if isinstance(v, (list, dict)):
|
|
136
|
+
v = str(v)[:80]
|
|
137
|
+
lines.append(f"{k}: {v}")
|
|
138
|
+
return lines
|
|
139
|
+
|
|
76
140
|
def extract_constraints(lines):
|
|
77
141
|
"""
|
|
78
142
|
从注解中提取约束信息(规范化格式),包括:
|
|
@@ -345,13 +409,12 @@ def _extract_fields_from_getters(content):
|
|
|
345
409
|
|
|
346
410
|
return fields
|
|
347
411
|
|
|
348
|
-
def generate_proto(name, pkg, content, ftype):
|
|
412
|
+
def generate_proto(name, pkg, content, ftype, java_file_path=None):
|
|
349
413
|
lines = [
|
|
350
414
|
'syntax = "proto3";',
|
|
351
415
|
'package domain;', ''
|
|
352
416
|
]
|
|
353
417
|
|
|
354
|
-
# 只有 class 类型才需要 import
|
|
355
418
|
imports = set()
|
|
356
419
|
fields = []
|
|
357
420
|
enum_items = []
|
|
@@ -360,11 +423,9 @@ def generate_proto(name, pkg, content, ftype):
|
|
|
360
423
|
enum_items = parse_java_enum(content, name)
|
|
361
424
|
if not enum_items: return None
|
|
362
425
|
else:
|
|
363
|
-
# 解析字段并获取依赖
|
|
364
426
|
fields, imports = parse_java_class(content)
|
|
365
427
|
if not fields: return None
|
|
366
428
|
|
|
367
|
-
# 生成 import 语句
|
|
368
429
|
self_import = f"{camel_to_snake(name)}.proto"
|
|
369
430
|
if self_import in imports:
|
|
370
431
|
imports.remove(self_import)
|
|
@@ -373,9 +434,19 @@ def generate_proto(name, pkg, content, ftype):
|
|
|
373
434
|
sorted_imports = sorted(list(imports))
|
|
374
435
|
for imp in sorted_imports:
|
|
375
436
|
lines.append(f'import "{imp}";')
|
|
376
|
-
lines.append('')
|
|
437
|
+
lines.append('')
|
|
377
438
|
|
|
378
439
|
lines.append(f'// Generated from {pkg}.{name}')
|
|
440
|
+
|
|
441
|
+
# 先查与 Java 同目录的同名 .md,再查 .r2mo/repo;只提取头部 front-matter,找到才打印
|
|
442
|
+
project_root = os.getcwd()
|
|
443
|
+
doc_attrs, found_md = _find_doc_for_java(name, java_file_path, project_root)
|
|
444
|
+
if doc_attrs and found_md:
|
|
445
|
+
for comment_line in _build_message_comment(doc_attrs):
|
|
446
|
+
if comment_line:
|
|
447
|
+
lines.append(f'// {comment_line}')
|
|
448
|
+
print(f" ✓ {name}: 已从 {found_md} 追加笔记属性到注释")
|
|
449
|
+
|
|
379
450
|
lines.append(f'option java_package = "{pkg}";')
|
|
380
451
|
lines.append('option java_multiple_files = true;')
|
|
381
452
|
lines.append('')
|
|
@@ -387,7 +458,6 @@ def generate_proto(name, pkg, content, ftype):
|
|
|
387
458
|
else:
|
|
388
459
|
lines.append(f'message {name} {{')
|
|
389
460
|
for idx, f in enumerate(fields, 1):
|
|
390
|
-
# BaseEntity 等字段用 java_name 拼注释;其余字段 comment 已含 [Java:xxx]
|
|
391
461
|
comment_str = f.get("comment") or ""
|
|
392
462
|
if f.get("java_name"):
|
|
393
463
|
comment_str = f"[Java:{f['java_name']}] {comment_str}".strip() if comment_str else f"[Java:{f['java_name']}]"
|
|
@@ -442,7 +512,7 @@ def main():
|
|
|
442
512
|
pkg = get_package_name(content)
|
|
443
513
|
ftype = 'enum' if is_enum else 'class'
|
|
444
514
|
|
|
445
|
-
proto = generate_proto(name, pkg, content, ftype)
|
|
515
|
+
proto = generate_proto(name, pkg, content, ftype, java_file_path=path)
|
|
446
516
|
if proto:
|
|
447
517
|
out_name = f"{camel_to_snake(name)}.proto"
|
|
448
518
|
with open(os.path.join(output_dir, out_name), 'w') as fh: fh.write(proto)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module momo-repo-spec
|
|
3
|
+
* 与 momo domain / momo mmr0 / momo mmr2 共享的 r2mo-spec 仓库配置。
|
|
4
|
+
* 仓库名为 r2mo-spec,直接从 .r2mo/repo/r2mo-spec 加载,不做 rename。
|
|
5
|
+
*/
|
|
6
|
+
const REPO_SPEC_NAME = 'r2mo-spec';
|
|
7
|
+
const SPEC_REPO_URL = 'https://gitee.com/silentbalanceyh/r2mo-spec.git';
|
|
8
|
+
/** 本地缓存目录:.r2mo/repo/{仓库名},与仓库名一致 */
|
|
9
|
+
const LOCAL_CACHE_DIR = `.r2mo/repo/${REPO_SPEC_NAME}`;
|
|
10
|
+
/** .gitignore 中写入的条目(共享目录根) */
|
|
11
|
+
const GITIGNORE_ENTRY = '.r2mo/repo';
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
REPO_SPEC_NAME,
|
|
15
|
+
SPEC_REPO_URL,
|
|
16
|
+
LOCAL_CACHE_DIR,
|
|
17
|
+
GITIGNORE_ENTRY
|
|
18
|
+
};
|