@leeguoo/yapi-mcp 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -14
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +35 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/commands/config.d.ts +2 -0
- package/dist/cli/commands/config.js +66 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/docs-sync.js +168 -40
- package/dist/cli/commands/docs-sync.js.map +1 -1
- package/dist/cli/simple-request.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/docs/markdown.d.ts +13 -0
- package/dist/docs/markdown.js +77 -3
- package/dist/docs/markdown.js.map +1 -1
- package/dist/yapi-cli.js +19 -2
- package/dist/yapi-cli.js.map +1 -1
- package/package.json +4 -4
- package/skill-template/SKILL.md +112 -4
package/README.md
CHANGED
|
@@ -54,27 +54,46 @@ Yapi Auto MCP Server 是一个基于 [Model Context Protocol](https://modelconte
|
|
|
54
54
|
|
|
55
55
|
### 推荐方式:用 Cross Request Master 一键安装 Skill(免手动找 Token)
|
|
56
56
|
|
|
57
|
-
如果你日常就在浏览器里使用 YApi,推荐安装 Chrome 扩展 [cross-request-master](https://github.com/leeguooooo/cross-request-master)。它会在 YApi 接口详情页(基本信息区域右上角)提供 **「YApi 工具箱」** 按钮,包含 Skill
|
|
57
|
+
如果你日常就在浏览器里使用 YApi,推荐安装 Chrome 扩展 [cross-request-master](https://github.com/leeguooooo/cross-request-master)。它会在 YApi 接口详情页(基本信息区域右上角)提供 **「YApi 工具箱」** 按钮,包含 Skill 一键安装(推荐,支持 `npx skills add`)/MCP 配置(兼容)/CLI docs-sync 说明;另外保留 **「复制给 AI」** 一键复制接口 Markdown:
|
|
58
58
|
|
|
59
|
-
- Skill
|
|
59
|
+
- Skill 一键安装(推荐):优先生成 `npx skills add` 命令安装仓库导出的 Skill,再配合 `yapi config init` 初始化全局配置;如需一步写入配置,保留 `yapi install-skill` 兼容路径
|
|
60
60
|
- MCP 配置(兼容):使用 `--yapi-auth-mode=global`(账号密码),默认会自动懒登录;也可手动调用一次 `yapi_update_token` 预热缓存
|
|
61
61
|
- CLI 使用与 docs-sync:提供本地 CLI 安装命令和文档同步示例
|
|
62
62
|
|
|
63
63
|
### Skill 一键安装与 CLI
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
推荐先用 `skills` 安装仓库导出的 Skill:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
#
|
|
68
|
+
# 推荐:把仓库里的 yapi Skill 装到全局 agent 目录
|
|
69
|
+
npx skills add leeguooooo/cross-request-master -y -g
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
这条命令只负责安装 Skill 文件,不会写入 `~/.yapi/config.toml`。如果你还没有本地配置,继续执行下面任一方式:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 方式一:推荐单独初始化配置
|
|
69
76
|
npm install -g @leeguoo/yapi-mcp
|
|
77
|
+
yapi config init \
|
|
78
|
+
--base-url=https://your-yapi-domain.com \
|
|
79
|
+
--auth-mode=global \
|
|
80
|
+
--email=your_email@example.com
|
|
70
81
|
|
|
82
|
+
# 如未保存密码,首次再同步一次浏览器登录态
|
|
83
|
+
yapi login --base-url=https://your-yapi-domain.com --browser
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
也可以继续使用兼容命令,在安装 Skill 的同时写入 `~/.yapi/config.toml`:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g @leeguoo/yapi-mcp
|
|
71
90
|
yapi install-skill \
|
|
72
91
|
--yapi-base-url=https://your-yapi-domain.com \
|
|
73
92
|
--yapi-email=your_email@example.com \
|
|
74
93
|
--yapi-password=your_password
|
|
75
94
|
```
|
|
76
95
|
|
|
77
|
-
也可以用 npx
|
|
96
|
+
也可以用 npx 临时运行兼容命令(不全局安装):
|
|
78
97
|
|
|
79
98
|
```bash
|
|
80
99
|
npx -y -p @leeguoo/yapi-mcp yapi install-skill \
|
|
@@ -83,22 +102,20 @@ npx -y -p @leeguoo/yapi-mcp yapi install-skill \
|
|
|
83
102
|
--yapi-password=your_password
|
|
84
103
|
```
|
|
85
104
|
|
|
86
|
-
|
|
87
|
-
- Codex: `~/.codex/skills/yapi/`
|
|
88
|
-
- Claude Code: `~/.claude/skills/yapi/`
|
|
89
|
-
- Cursor: `~/.cursor/skills/yapi/`
|
|
105
|
+
`skills` CLI 会维护一份规范化安装副本,并按目标 agent 建立链接/映射。当前全局安装通常可在 `~/.agents/skills/yapi/` 看到 canonical copy,具体 agent 侧落点由 `skills` CLI 决定。
|
|
90
106
|
|
|
91
|
-
Skill
|
|
107
|
+
仓库导出的 Skill 来源:`skills/yapi/SKILL.md`(供 `npx skills add` 发现)。
|
|
108
|
+
npm 包内安装模板来源:`packages/yapi-mcp/skill-template/SKILL.md`(供 `yapi install-skill` 复制到技能目录)。
|
|
92
109
|
后续当 CLI 升级而本地 Skill 仍是旧版本时,`yapi` 会自动提示:
|
|
93
110
|
|
|
94
111
|
```bash
|
|
95
|
-
skill update available: installed Codex@0.3.24, current 0.3.25. Run:
|
|
112
|
+
skill update available: installed Codex@0.3.24, current 0.3.25. Run: npx skills add leeguooooo/cross-request-master -y -g
|
|
96
113
|
```
|
|
97
114
|
|
|
98
115
|
也可以手动重装 Skill:
|
|
99
116
|
|
|
100
117
|
```bash
|
|
101
|
-
|
|
118
|
+
npx skills add leeguooooo/cross-request-master -y -g
|
|
102
119
|
```
|
|
103
120
|
|
|
104
121
|
### CLI 使用
|
|
@@ -106,13 +123,14 @@ yapi install-skill --force
|
|
|
106
123
|
推荐全局安装后直接使用 `yapi` 命令(走同一份 `~/.yapi/config.toml`):
|
|
107
124
|
|
|
108
125
|
当前 CLI 能力补充:
|
|
126
|
+
- 支持 `yapi config init`:单独初始化/更新 `~/.yapi/config.toml`,适合和 `npx skills add` 组合使用
|
|
109
127
|
- 支持 `yapi login --browser`:通过 `agent-browser-stealth` 打开页面,登录后自动同步 `_yapi_token/_yapi_uid` 到本地缓存
|
|
110
128
|
- 默认打开 `base-url` 首页(不强制 `/login`),适配“已登录可直接拿 Cookie”的场景
|
|
111
129
|
- 支持 `yapi login --login-url <url>` 指定登录页
|
|
112
130
|
- 支持 `yapi logout` 清理当前 `base_url` 对应的全局会话缓存
|
|
113
131
|
- 适用于 SSO/额外验证体系:无法使用账号密码时可只走浏览器登录
|
|
114
132
|
- 支持 `yapi self-update` 升级全局 CLI
|
|
115
|
-
- 当已安装的 Skill 版本落后于当前 CLI 时,会自动提示重新执行 `
|
|
133
|
+
- 当已安装的 Skill 版本落后于当前 CLI 时,会自动提示重新执行 `npx skills add leeguooooo/cross-request-master -y -g`
|
|
116
134
|
|
|
117
135
|
```bash
|
|
118
136
|
# 检查版本
|
|
@@ -194,12 +212,14 @@ yapi docs-sync
|
|
|
194
212
|
- 绑定模式同步后会写入 `.yapi/docs-sync.deployments.json`(本地文档 → 已部署 URL)
|
|
195
213
|
- 兼容旧方式:`--dir` 读取目录内 `.yapi.json` 的 `project_id/catid` 与 `source_files`
|
|
196
214
|
- 管理绑定:`yapi docs-sync bind list|get|add|update|remove`
|
|
215
|
+
- 也可以在运行时临时过滤文件:`yapi docs-sync --binding projectA --source-file architecture.md`;优先级是 `--source-file` > 绑定里的 `source_files` > 目录全量扫描
|
|
197
216
|
- `--query` 支持像 curl 一样写成单个字符串:`--query "catid=4631&limit=50&page=1"`
|
|
198
217
|
- 可用 `--dry-run` 只做预览不更新;现在会输出每个文件的 Markdown/HTML/请求体大小,并提前暴露超大文档风险
|
|
199
218
|
- 默认只同步内容变更的文件,如需全量更新使用 `--force`
|
|
200
219
|
- 普通同步命中相同 `file_hashes` 时会在渲染前直接跳过,不再重复渲染 Mermaid / PlantUML / Graphviz / D2;`--dry-run` 仍会保留预览渲染
|
|
201
|
-
- 如果上传返回 `413 Payload Too Large`,CLI
|
|
220
|
+
- 如果上传返回 `413 Payload Too Large`,CLI 会按 `默认 Mermaid -> --mermaid-classic -> --no-mermaid` 自动降级;某个文件一旦降级成功,会把该模式记住,后续同步优先直接使用,避免每次先撞一次 413
|
|
202
221
|
- Mermaid 预渲染依赖 `mmdc`(默认手绘风格;安装时会尝试拉取,失败不影响同步)
|
|
222
|
+
- 如果 `mmdc` 已安装但提示缺少 `chrome-headless-shell` / Puppeteer 浏览器,执行:`npx puppeteer browsers install chrome-headless-shell`
|
|
203
223
|
- PlantUML 预渲染依赖 `plantuml`(需要本机 Java 环境)
|
|
204
224
|
- Graphviz 预渲染依赖 `dot`(graphviz)
|
|
205
225
|
- D2 预渲染依赖 `d2`(默认手绘风格输出)
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const REQUIRED_DEPS = ["yargs", "axios"];
|
|
5
|
+
const missing = [];
|
|
6
|
+
for (const dep of REQUIRED_DEPS) {
|
|
7
|
+
try {
|
|
8
|
+
require.resolve(dep);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
missing.push(dep);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (missing.length > 0) {
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const pkgDir = path.resolve(__dirname, "..");
|
|
17
|
+
const lines = [
|
|
18
|
+
`[yapi-cli] Missing runtime dependencies: ${missing.join(", ")}`,
|
|
19
|
+
"",
|
|
20
|
+
"This usually means the package directory has no node_modules installed.",
|
|
21
|
+
"",
|
|
22
|
+
"To fix, run one of the following in the package directory:",
|
|
23
|
+
` cd "${pkgDir}"`,
|
|
24
|
+
" pnpm install # if using pnpm (recommended for this repo)",
|
|
25
|
+
" # or",
|
|
26
|
+
" npm install # if using npm",
|
|
27
|
+
"",
|
|
28
|
+
"If you installed @leeguoo/yapi-mcp globally, try reinstalling:",
|
|
29
|
+
" npm i -g @leeguoo/yapi-mcp",
|
|
30
|
+
];
|
|
31
|
+
console.error(lines.join("\n"));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
require("./yapi-cli");
|
|
35
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";;;AAQA,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAEzC,MAAM,OAAO,GAAa,EAAE,CAAC;AAC7B,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAA0B,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG;QACZ,4CAA4C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAChE,EAAE;QACF,yEAAyE;QACzE,EAAE;QACF,4DAA4D;QAC5D,SAAS,MAAM,GAAG;QAClB,sEAAsE;QACtE,QAAQ;QACR,yCAAyC;QACzC,EAAE;QACF,gEAAgE;QAChE,8BAA8B;KAC/B,CAAC;IAEF,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAGD,OAAO,CAAC,YAAY,CAAC,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runConfig = runConfig;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
function resolveAuthMode(explicit, existing, merged) {
|
|
10
|
+
const normalized = String(explicit || existing.auth_mode || "").trim().toLowerCase();
|
|
11
|
+
if (!normalized) {
|
|
12
|
+
return merged.token ? "token" : "global";
|
|
13
|
+
}
|
|
14
|
+
if (normalized === "token" || normalized === "global")
|
|
15
|
+
return normalized;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
async function runConfig(action, options) {
|
|
19
|
+
const normalizedAction = String(action || "init").trim().toLowerCase() || "init";
|
|
20
|
+
if (normalizedAction !== "init") {
|
|
21
|
+
console.error(`unknown config action: ${normalizedAction}`);
|
|
22
|
+
return 2;
|
|
23
|
+
}
|
|
24
|
+
const configPath = options.config || (0, utils_1.globalConfigPath)();
|
|
25
|
+
const existing = fs_1.default.existsSync(configPath)
|
|
26
|
+
? (0, utils_1.parseSimpleToml)(fs_1.default.readFileSync(configPath, "utf8"))
|
|
27
|
+
: {};
|
|
28
|
+
const merged = { ...existing };
|
|
29
|
+
if (options.baseUrl !== undefined)
|
|
30
|
+
merged.base_url = options.baseUrl;
|
|
31
|
+
if (options.email !== undefined)
|
|
32
|
+
merged.email = options.email;
|
|
33
|
+
if (options.password !== undefined)
|
|
34
|
+
merged.password = options.password;
|
|
35
|
+
if (options.token !== undefined)
|
|
36
|
+
merged.token = options.token;
|
|
37
|
+
if (options.projectId !== undefined)
|
|
38
|
+
merged.project_id = options.projectId;
|
|
39
|
+
if (!merged.base_url) {
|
|
40
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
41
|
+
console.error("missing --base-url");
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
merged.base_url = await (0, utils_1.promptRequired)("YApi base URL: ", false);
|
|
45
|
+
}
|
|
46
|
+
const authMode = resolveAuthMode(options.authMode || "", existing, merged);
|
|
47
|
+
if (!authMode) {
|
|
48
|
+
console.error("invalid --auth-mode (use token or global)");
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
merged.auth_mode = authMode;
|
|
52
|
+
merged.email = merged.email || "";
|
|
53
|
+
merged.password = merged.password || "";
|
|
54
|
+
merged.token = merged.token || "";
|
|
55
|
+
merged.project_id = merged.project_id || "";
|
|
56
|
+
(0, utils_1.writeConfig)(configPath, merged);
|
|
57
|
+
console.log(`Config written to: ${configPath}`);
|
|
58
|
+
if (authMode === "global" && !merged.password) {
|
|
59
|
+
console.log("Global auth configured without saved password. Run `yapi login --base-url <url> --browser` once to sync cookie, or rerun with --password to enable password relogin.");
|
|
60
|
+
}
|
|
61
|
+
if (authMode === "token" && !merged.token) {
|
|
62
|
+
console.log("Token mode configured without token. Pass --token now or set it later before requests.");
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":";;;;;AAsBA,8BAmDC;AAzED,4CAAoB;AAEpB,oCAKkB;AAElB,SAAS,eAAe,CACtB,QAAgB,EAChB,QAAgC,EAChC,MAA8B;IAE9B,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrF,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC3C,CAAC;IACD,IAAI,UAAU,KAAK,OAAO,IAAI,UAAU,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAC;IACzE,OAAO,IAAI,CAAC;AACd,CAAC;AAEM,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,OAAgB;IAC9D,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC;IACjF,IAAI,gBAAgB,KAAK,MAAM,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,0BAA0B,gBAAgB,EAAE,CAAC,CAAC;QAC5D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,IAAI,IAAA,wBAAgB,GAAE,CAAC;IACxD,MAAM,QAAQ,GAAG,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QACxC,CAAC,CAAC,IAAA,uBAAe,EAAC,YAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,MAAM,GAA2B,EAAE,GAAG,QAAQ,EAAE,CAAC;IAEvD,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IACrE,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9D,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS;QAAE,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IACvE,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9D,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;QAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAE3E,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClD,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;YACpC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,CAAC,QAAQ,GAAG,MAAM,IAAA,sBAAc,EAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3E,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC3D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IAClC,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;IACxC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IAClC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;IAE5C,IAAA,mBAAW,EAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAEhC,OAAO,CAAC,GAAG,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;IAChD,IAAI,QAAQ,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CACT,sKAAsK,CACvK,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,wFAAwF,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -149,20 +149,6 @@ function saveDocsSyncDeployments(homeDir, config) {
|
|
|
149
149
|
const configPath = docsSyncDeploymentsPath(homeDir);
|
|
150
150
|
fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
151
151
|
}
|
|
152
|
-
function resolveBindingDir(rootDir, bindingDir) {
|
|
153
|
-
if (!bindingDir)
|
|
154
|
-
return rootDir;
|
|
155
|
-
return path_1.default.isAbsolute(bindingDir) ? bindingDir : path_1.default.resolve(rootDir, bindingDir);
|
|
156
|
-
}
|
|
157
|
-
function normalizeBindingDir(rootDir, bindingDir) {
|
|
158
|
-
const resolved = resolveBindingDir(rootDir, bindingDir);
|
|
159
|
-
const relative = path_1.default.relative(rootDir, resolved);
|
|
160
|
-
if (!relative || relative === ".")
|
|
161
|
-
return ".";
|
|
162
|
-
if (relative.startsWith("..") || path_1.default.isAbsolute(relative))
|
|
163
|
-
return resolved;
|
|
164
|
-
return relative;
|
|
165
|
-
}
|
|
166
152
|
function getBindingBaseDir(homeDir, rootDir, cwd) {
|
|
167
153
|
if (!isGlobalDocsSyncHome(homeDir)) {
|
|
168
154
|
return { baseDir: rootDir, gitRoot: (0, utils_1.findGitRoot)(cwd), usedGitRoot: false };
|
|
@@ -241,6 +227,26 @@ function resolveSourceFiles(dirPath, mapping) {
|
|
|
241
227
|
return resolved;
|
|
242
228
|
});
|
|
243
229
|
}
|
|
230
|
+
function cloneMappingForSync(mapping, sourceFiles) {
|
|
231
|
+
const next = {
|
|
232
|
+
...mapping,
|
|
233
|
+
files: mapping.files ? { ...mapping.files } : {},
|
|
234
|
+
file_hashes: mapping.file_hashes ? { ...mapping.file_hashes } : {},
|
|
235
|
+
file_render_modes: mapping.file_render_modes ? { ...mapping.file_render_modes } : {},
|
|
236
|
+
};
|
|
237
|
+
if (sourceFiles?.length) {
|
|
238
|
+
next.source_files = [...sourceFiles];
|
|
239
|
+
}
|
|
240
|
+
return next;
|
|
241
|
+
}
|
|
242
|
+
function mergeSyncState(target, source) {
|
|
243
|
+
target.project_id = source.project_id;
|
|
244
|
+
target.catid = source.catid;
|
|
245
|
+
target.template_id = source.template_id;
|
|
246
|
+
target.files = source.files ? { ...source.files } : {};
|
|
247
|
+
target.file_hashes = source.file_hashes ? { ...source.file_hashes } : {};
|
|
248
|
+
target.file_render_modes = source.file_render_modes ? { ...source.file_render_modes } : {};
|
|
249
|
+
}
|
|
244
250
|
async function listExistingInterfaces(catId, request) {
|
|
245
251
|
const resp = await request("/api/interface/list_cat", "GET", {
|
|
246
252
|
catid: catId,
|
|
@@ -319,7 +325,7 @@ function buildDocsSyncPreviewLine(item) {
|
|
|
319
325
|
}
|
|
320
326
|
return `preview ${parts.join(" ")}`;
|
|
321
327
|
}
|
|
322
|
-
function buildDocsSyncPayloadTooLargeMessage(fileName, preview, error) {
|
|
328
|
+
function buildDocsSyncPayloadTooLargeMessage(fileName, preview, error, attempts = []) {
|
|
323
329
|
const lines = [
|
|
324
330
|
`413 Payload Too Large while syncing ${fileName}`,
|
|
325
331
|
`- request payload: ${(0, utils_1.formatBytes)(preview.payloadBytes)}`,
|
|
@@ -339,10 +345,85 @@ function buildDocsSyncPayloadTooLargeMessage(fileName, preview, error) {
|
|
|
339
345
|
else {
|
|
340
346
|
lines.push("- largest Mermaid block: none");
|
|
341
347
|
}
|
|
348
|
+
attempts.forEach((attempt) => {
|
|
349
|
+
if (attempt.mode === "default")
|
|
350
|
+
return;
|
|
351
|
+
const label = attempt.mode === "classic" ? "--mermaid-classic" : "--no-mermaid";
|
|
352
|
+
if (attempt.payloadBytes && attempt.htmlBytes) {
|
|
353
|
+
lines.push(`- retry with ${label}: payload=${(0, utils_1.formatBytes)(attempt.payloadBytes)} html=${(0, utils_1.formatBytes)(attempt.htmlBytes)}`);
|
|
354
|
+
}
|
|
355
|
+
if (attempt.message) {
|
|
356
|
+
lines.push(`- ${label} retry result: ${attempt.message}`);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
342
359
|
lines.push("- suggestion: run `yapi docs-sync --dry-run ...` to preview all files before upload");
|
|
343
360
|
lines.push("- suggestion: split oversized Mermaid diagrams or move them into separate docs");
|
|
361
|
+
lines.push("- suggestion: if even `--no-mermaid` still hits 413, the remaining limit is on the YApi server/proxy side");
|
|
344
362
|
return lines.join("\n");
|
|
345
363
|
}
|
|
364
|
+
function resolveRenderModeLabel(mode) {
|
|
365
|
+
if (mode === "classic")
|
|
366
|
+
return "--mermaid-classic";
|
|
367
|
+
if (mode === "no-mermaid")
|
|
368
|
+
return "--no-mermaid";
|
|
369
|
+
return "default Mermaid rendering";
|
|
370
|
+
}
|
|
371
|
+
function resolveDocsSyncOptionsForMode(options, mode) {
|
|
372
|
+
if (mode === "classic") {
|
|
373
|
+
return {
|
|
374
|
+
...options,
|
|
375
|
+
noMermaid: false,
|
|
376
|
+
mermaidLook: "classic",
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
if (mode === "no-mermaid") {
|
|
380
|
+
return {
|
|
381
|
+
...options,
|
|
382
|
+
noMermaid: true,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
return { ...options };
|
|
386
|
+
}
|
|
387
|
+
function resolveInitialRenderMode(options, rememberedMode) {
|
|
388
|
+
if (options.noMermaid)
|
|
389
|
+
return "no-mermaid";
|
|
390
|
+
if (options.mermaidLook === "classic")
|
|
391
|
+
return "classic";
|
|
392
|
+
if (rememberedMode)
|
|
393
|
+
return rememberedMode;
|
|
394
|
+
return "default";
|
|
395
|
+
}
|
|
396
|
+
function resolveRetryModes(mode, hasMermaidBlocks) {
|
|
397
|
+
if (!hasMermaidBlocks)
|
|
398
|
+
return [];
|
|
399
|
+
if (mode === "default")
|
|
400
|
+
return ["classic", "no-mermaid"];
|
|
401
|
+
if (mode === "classic")
|
|
402
|
+
return ["no-mermaid"];
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
function renderDocsSyncHtml(markdown, options, logPrefix) {
|
|
406
|
+
let mermaidFailed = false;
|
|
407
|
+
let diagramFailed = false;
|
|
408
|
+
const diagramMetrics = [];
|
|
409
|
+
const html = (0, markdown_1.renderMarkdownToHtml)(markdown, {
|
|
410
|
+
noMermaid: options.noMermaid,
|
|
411
|
+
logMermaid: true,
|
|
412
|
+
mermaidLook: options.mermaidLook,
|
|
413
|
+
mermaidHandDrawnSeed: options.mermaidHandDrawnSeed,
|
|
414
|
+
logger: (message) => console.log(`${logPrefix} ${message}`),
|
|
415
|
+
onDiagramRendered: (metric) => {
|
|
416
|
+
diagramMetrics.push(metric);
|
|
417
|
+
},
|
|
418
|
+
onMermaidError: () => {
|
|
419
|
+
mermaidFailed = true;
|
|
420
|
+
},
|
|
421
|
+
onDiagramError: () => {
|
|
422
|
+
diagramFailed = true;
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
return { html, mermaidFailed, diagramFailed, diagramMetrics };
|
|
426
|
+
}
|
|
346
427
|
async function addInterface(title, apiPath, mapping, request) {
|
|
347
428
|
const projectId = Number(mapping.project_id || 0);
|
|
348
429
|
const catId = Number(mapping.catid || 0);
|
|
@@ -432,7 +513,17 @@ async function syncDocsDir(dirPath, mapping, options, request) {
|
|
|
432
513
|
const resolvedPath = byId[String(docId)]?.path || apiPath;
|
|
433
514
|
fileInfos[relName] = { docId: Number(docId), apiPath: resolvedPath };
|
|
434
515
|
}
|
|
435
|
-
const
|
|
516
|
+
const logPrefix = `[docs-sync:${relName}]`;
|
|
517
|
+
const hasMermaidBlocks = /```mermaid\s*\r?\n/i.test(markdown);
|
|
518
|
+
const rememberedMode = !options.noMermaid && options.mermaidLook === "handDrawn"
|
|
519
|
+
? mapping.file_render_modes?.[relName]
|
|
520
|
+
: undefined;
|
|
521
|
+
let appliedMode = resolveInitialRenderMode(options, rememberedMode);
|
|
522
|
+
let effectiveOptions = resolveDocsSyncOptionsForMode(options, appliedMode);
|
|
523
|
+
if (rememberedMode) {
|
|
524
|
+
console.log(`${logPrefix} using remembered fallback: ${resolveRenderModeLabel(appliedMode)}.`);
|
|
525
|
+
}
|
|
526
|
+
let contentHash = (0, utils_1.buildDocsSyncHash)(markdown, effectiveOptions);
|
|
436
527
|
const previousHash = mapping.file_hashes[relName];
|
|
437
528
|
const currentTitle = docId ? byId[String(docId)]?.title : "";
|
|
438
529
|
const titleToUpdate = !docId
|
|
@@ -450,26 +541,7 @@ async function syncDocsDir(dirPath, mapping, options, request) {
|
|
|
450
541
|
skipped += 1;
|
|
451
542
|
continue;
|
|
452
543
|
}
|
|
453
|
-
|
|
454
|
-
let mermaidFailed = false;
|
|
455
|
-
let diagramFailed = false;
|
|
456
|
-
const diagramMetrics = [];
|
|
457
|
-
const html = (0, markdown_1.renderMarkdownToHtml)(markdown, {
|
|
458
|
-
noMermaid: options.noMermaid,
|
|
459
|
-
logMermaid: true,
|
|
460
|
-
mermaidLook: options.mermaidLook,
|
|
461
|
-
mermaidHandDrawnSeed: options.mermaidHandDrawnSeed,
|
|
462
|
-
logger: (message) => console.log(`${logPrefix} ${message}`),
|
|
463
|
-
onDiagramRendered: (metric) => {
|
|
464
|
-
diagramMetrics.push(metric);
|
|
465
|
-
},
|
|
466
|
-
onMermaidError: () => {
|
|
467
|
-
mermaidFailed = true;
|
|
468
|
-
},
|
|
469
|
-
onDiagramError: () => {
|
|
470
|
-
diagramFailed = true;
|
|
471
|
-
},
|
|
472
|
-
});
|
|
544
|
+
let { html, mermaidFailed, diagramFailed, diagramMetrics } = renderDocsSyncHtml(markdown, effectiveOptions, logPrefix);
|
|
473
545
|
if (shouldSkipUnchanged) {
|
|
474
546
|
action = "skip";
|
|
475
547
|
skipped += 1;
|
|
@@ -494,11 +566,54 @@ async function syncDocsDir(dirPath, mapping, options, request) {
|
|
|
494
566
|
}
|
|
495
567
|
catch (error) {
|
|
496
568
|
if (error instanceof http_1.HttpStatusError && error.status === 413) {
|
|
497
|
-
|
|
569
|
+
const attempts = [];
|
|
570
|
+
let resolved = false;
|
|
571
|
+
for (const retryMode of resolveRetryModes(appliedMode, hasMermaidBlocks)) {
|
|
572
|
+
console.warn(`${logPrefix} 413 received, retrying with ${resolveRenderModeLabel(retryMode)} for this file.`);
|
|
573
|
+
const retryAttempt = { mode: retryMode };
|
|
574
|
+
attempts.push(retryAttempt);
|
|
575
|
+
try {
|
|
576
|
+
const retryOptions = resolveDocsSyncOptionsForMode(options, retryMode);
|
|
577
|
+
const retryResult = renderDocsSyncHtml(markdown, retryOptions, logPrefix);
|
|
578
|
+
const retryPayload = buildUpdatePayload(docId, titleToUpdate, markdown, retryResult.html);
|
|
579
|
+
retryAttempt.payloadBytes = Buffer.byteLength(JSON.stringify(retryPayload), "utf8");
|
|
580
|
+
retryAttempt.htmlBytes = Buffer.byteLength(retryResult.html, "utf8");
|
|
581
|
+
await updateInterface(docId, titleToUpdate, markdown, retryResult.html, request);
|
|
582
|
+
html = retryResult.html;
|
|
583
|
+
mermaidFailed = retryResult.mermaidFailed;
|
|
584
|
+
diagramFailed = retryResult.diagramFailed;
|
|
585
|
+
diagramMetrics = retryResult.diagramMetrics;
|
|
586
|
+
appliedMode = retryMode;
|
|
587
|
+
effectiveOptions = retryOptions;
|
|
588
|
+
contentHash = (0, utils_1.buildDocsSyncHash)(markdown, effectiveOptions);
|
|
589
|
+
resolved = true;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
catch (retryError) {
|
|
593
|
+
retryAttempt.message =
|
|
594
|
+
retryError instanceof Error ? retryError.message : String(retryError);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (!resolved) {
|
|
598
|
+
throw new Error(buildDocsSyncPayloadTooLargeMessage(relName, preview, error, attempts));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
throw error;
|
|
498
603
|
}
|
|
499
|
-
throw error;
|
|
500
604
|
}
|
|
501
605
|
}
|
|
606
|
+
if (appliedMode === "default") {
|
|
607
|
+
if (mapping.file_render_modes) {
|
|
608
|
+
delete mapping.file_render_modes[relName];
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
if (!mapping.file_render_modes || typeof mapping.file_render_modes !== "object") {
|
|
613
|
+
mapping.file_render_modes = {};
|
|
614
|
+
}
|
|
615
|
+
mapping.file_render_modes[relName] = appliedMode;
|
|
616
|
+
}
|
|
502
617
|
if (docId && !mermaidFailed && !diagramFailed) {
|
|
503
618
|
mapping.file_hashes[relName] = contentHash;
|
|
504
619
|
}
|
|
@@ -740,6 +855,15 @@ async function runDocsSync(options) {
|
|
|
740
855
|
console.warn("mmdc not found, Mermaid blocks will stay as code.");
|
|
741
856
|
console.warn("Install mermaid-cli: npm i -g @mermaid-js/mermaid-cli");
|
|
742
857
|
}
|
|
858
|
+
else if (!options.noMermaid) {
|
|
859
|
+
const mermaidRuntime = (0, markdown_1.probeMermaidRuntime)({
|
|
860
|
+
look: options.mermaidLook,
|
|
861
|
+
handDrawnSeed: options.mermaidHandDrawnSeed,
|
|
862
|
+
});
|
|
863
|
+
if (!mermaidRuntime.ok) {
|
|
864
|
+
console.warn(`mermaid runtime check failed: ${mermaidRuntime.message}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
743
867
|
if (!(0, markdown_1.isPlantUmlAvailable)()) {
|
|
744
868
|
console.warn("plantuml not found, PlantUML blocks will be removed from HTML.");
|
|
745
869
|
console.warn("Install PlantUML (macOS): brew install plantuml");
|
|
@@ -970,12 +1094,14 @@ async function runDocsSync(options) {
|
|
|
970
1094
|
if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
|
|
971
1095
|
throw new Error(`dir not found for binding ${name}: ${dirPath}`);
|
|
972
1096
|
}
|
|
973
|
-
const
|
|
1097
|
+
const runtimeBinding = cloneMappingForSync(binding, options.sourceFiles);
|
|
1098
|
+
const result = await syncDocsDir(dirPath, runtimeBinding, options, request);
|
|
974
1099
|
if (options.dryRun) {
|
|
975
1100
|
console.log(`dry-run preview binding=${name}`);
|
|
976
1101
|
result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
|
|
977
1102
|
}
|
|
978
1103
|
console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} binding=${name} dir=${dirPath}`);
|
|
1104
|
+
mergeSyncState(binding, runtimeBinding);
|
|
979
1105
|
bindingResults[name] = { binding, files: result.files };
|
|
980
1106
|
}
|
|
981
1107
|
if (!options.dryRun) {
|
|
@@ -990,12 +1116,14 @@ async function runDocsSync(options) {
|
|
|
990
1116
|
throw new Error(`dir not found: ${dirPath}`);
|
|
991
1117
|
}
|
|
992
1118
|
const { mapping, mappingPath } = loadMapping(dirPath);
|
|
993
|
-
const
|
|
1119
|
+
const runtimeMapping = cloneMappingForSync(mapping, options.sourceFiles);
|
|
1120
|
+
const result = await syncDocsDir(dirPath, runtimeMapping, options, request);
|
|
994
1121
|
if (options.dryRun) {
|
|
995
1122
|
console.log(`dry-run preview dir=${dirPath}`);
|
|
996
1123
|
result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
|
|
997
1124
|
}
|
|
998
1125
|
if (!options.dryRun) {
|
|
1126
|
+
mergeSyncState(mapping, runtimeMapping);
|
|
999
1127
|
saveMapping(mapping, mappingPath);
|
|
1000
1128
|
}
|
|
1001
1129
|
console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} dir=${dirPath}`);
|