Skip to content

Commit e3f9805

Browse files
committed
feat(yapi-cli): skip unchanged docs-sync files
1 parent 6beb463 commit e3f9805

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

packages/yapi-mcp/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ npx -y @leeguoo/yapi-mcp yapi docs-sync
104104
- 兼容旧方式:`--dir` 读取目录内 `.yapi.json``project_id/catid``source_files`
105105
- 管理绑定:`yapi docs-sync bind list|get|add|update|remove`
106106
- 可用 `--dry-run` 只做转换不更新
107+
- 默认只同步内容变更的文件,如需全量更新使用 `--force`
107108
- 如需跳过 Mermaid 渲染,使用 `--no-mermaid`
108109

109110
### 手动方式:使用 npx(无需安装)

packages/yapi-mcp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@leeguoo/yapi-mcp",
3-
"version": "0.3.8",
3+
"version": "0.3.9",
44
"description": "YApi Auto MCP Server - Model Context Protocol server for YApi integration, enables AI tools like Cursor to interact with YApi API documentation",
55
"main": "dist/index.js",
66
"bin": {

packages/yapi-mcp/src/skill/install.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const SKILL_MD = [
2525
"## Docs sync",
2626
"- Bind local docs to YApi category with `yapi docs-sync bind add --name <binding> --dir <path> --project-id <id> --catid <id>` (stored in `.yapi/docs-sync.json`).",
2727
"- Sync with `yapi docs-sync --binding <binding>` or run all bindings with `yapi docs-sync`.",
28+
"- Default syncs only changed files; use `--force` to sync everything.",
2829
"- Extra mappings (generated after docs-sync run in binding mode):",
2930
" - `.yapi/docs-sync.links.json`: local docs to YApi doc URLs.",
3031
" - `.yapi/docs-sync.projects.json`: cached project metadata/envs.",

packages/yapi-mcp/src/yapi-cli.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
22

3+
import crypto from "crypto";
34
import fs from "fs";
45
import os from "os";
56
import path from "path";
@@ -46,6 +47,7 @@ type DocsSyncOptions = {
4647
bindings: string[];
4748
dryRun?: boolean;
4849
noMermaid?: boolean;
50+
force?: boolean;
4951
help?: boolean;
5052
};
5153

@@ -55,6 +57,7 @@ type DocsSyncMapping = {
5557
template_id?: number;
5658
source_files?: string[];
5759
files?: Record<string, number>;
60+
file_hashes?: Record<string, string>;
5861
[key: string]: unknown;
5962
};
6063

@@ -362,6 +365,7 @@ function parseDocsSyncArgs(argv: string[]): DocsSyncOptions {
362365
if (arg.startsWith("--binding=")) { options.bindings.push(arg.slice(10)); continue; }
363366
if (arg === "--dry-run") { options.dryRun = true; continue; }
364367
if (arg === "--no-mermaid") { options.noMermaid = true; continue; }
368+
if (arg === "--force") { options.force = true; continue; }
365369
if (arg.startsWith("-")) continue;
366370
options.dirs.push(arg);
367371
}
@@ -428,6 +432,7 @@ function usage(): string {
428432
" --binding <name> use binding from .yapi/docs-sync.json (repeatable)",
429433
" --dry-run compute but do not update YApi or mapping files",
430434
" --no-mermaid do not render mermaid code blocks",
435+
" --force sync all files even if unchanged",
431436
"Docs-sync bind actions:",
432437
" list, get, add, update, remove",
433438
" -V, --version print version",
@@ -455,6 +460,7 @@ function docsSyncUsage(): string {
455460
" --binding <name> use binding from .yapi/docs-sync.json (repeatable)",
456461
" --dry-run compute but do not update YApi or mapping files",
457462
" --no-mermaid do not render mermaid code blocks",
463+
" --force sync all files even if unchanged",
458464
" -h, --help show help",
459465
].join("\n");
460466
}
@@ -818,6 +824,13 @@ function saveMapping(mapping: DocsSyncMapping, mappingPath: string): void {
818824
fs.writeFileSync(mappingPath, `${JSON.stringify(mapping, null, 2)}\n`, "utf8");
819825
}
820826

827+
function buildDocsSyncHash(markdown: string, options: DocsSyncOptions): string {
828+
const hash = crypto.createHash("sha1");
829+
hash.update(options.noMermaid ? "no-mermaid\n" : "mermaid\n");
830+
hash.update(markdown);
831+
return hash.digest("hex");
832+
}
833+
821834
function resolveSourceFiles(dirPath: string, mapping: DocsSyncMapping): string[] {
822835
const sources = Array.isArray(mapping.source_files) ? mapping.source_files : [];
823836
if (!sources.length) {
@@ -1014,8 +1027,13 @@ async function syncDocsDir(
10141027
mapping: DocsSyncMapping,
10151028
options: DocsSyncOptions,
10161029
request: YapiRequest,
1017-
): Promise<{ updated: number; created: number; files: Record<string, DocsSyncFileInfo> }> {
1018-
mapping.files = mapping.files || {};
1030+
): Promise<{ updated: number; created: number; skipped: number; files: Record<string, DocsSyncFileInfo> }> {
1031+
if (!mapping.files || typeof mapping.files !== "object") {
1032+
mapping.files = {};
1033+
}
1034+
if (!mapping.file_hashes || typeof mapping.file_hashes !== "object") {
1035+
mapping.file_hashes = {};
1036+
}
10191037

10201038
const envProjectId = process.env.YAPI_PROJECT_ID;
10211039
const envCatId = process.env.YAPI_CATID;
@@ -1032,6 +1050,7 @@ async function syncDocsDir(
10321050

10331051
let updated = 0;
10341052
let created = 0;
1053+
let skipped = 0;
10351054
const fileInfos: Record<string, DocsSyncFileInfo> = {};
10361055
const files = resolveSourceFiles(dirPath, mapping);
10371056
for (const mdPath of files) {
@@ -1059,6 +1078,13 @@ async function syncDocsDir(
10591078
}
10601079

10611080
const markdown = fs.readFileSync(mdPath, "utf8");
1081+
const contentHash = buildDocsSyncHash(markdown, options);
1082+
const previousHash = mapping.file_hashes[relName];
1083+
if (!options.force && docId && previousHash && previousHash === contentHash) {
1084+
skipped += 1;
1085+
continue;
1086+
}
1087+
10621088
const logPrefix = `[docs-sync:${relName}]`;
10631089
const html = renderMarkdownToHtml(markdown, {
10641090
noMermaid: options.noMermaid,
@@ -1068,10 +1094,13 @@ async function syncDocsDir(
10681094
if (!options.dryRun && docId) {
10691095
await updateInterface(docId, markdown, html, request);
10701096
}
1097+
if (docId) {
1098+
mapping.file_hashes[relName] = contentHash;
1099+
}
10711100
updated += 1;
10721101
}
10731102

1074-
return { updated, created, files: fileInfos };
1103+
return { updated, created, skipped, files: fileInfos };
10751104
}
10761105

10771106
function buildEnvUrls(
@@ -1267,6 +1296,7 @@ async function runDocsSyncBindings(rawArgs: string[]): Promise<number> {
12671296
template_id: existing.template_id,
12681297
source_files: existing.source_files ? [...existing.source_files] : undefined,
12691298
files: existing.files ? { ...existing.files } : {},
1299+
file_hashes: existing.file_hashes ? { ...existing.file_hashes } : {},
12701300
};
12711301

12721302
if (options.dir) {
@@ -1499,7 +1529,9 @@ async function runDocsSync(rawArgs: string[]): Promise<number> {
14991529
}
15001530

15011531
const result = await syncDocsDir(dirPath, binding, options, request);
1502-
console.log(`synced=${result.updated} created=${result.created} binding=${name} dir=${dirPath}`);
1532+
console.log(
1533+
`synced=${result.updated} created=${result.created} skipped=${result.skipped} binding=${name} dir=${dirPath}`,
1534+
);
15031535
bindingResults[name] = { binding, files: result.files };
15041536
}
15051537

@@ -1521,7 +1553,7 @@ async function runDocsSync(rawArgs: string[]): Promise<number> {
15211553
saveMapping(mapping, mappingPath);
15221554
}
15231555

1524-
console.log(`synced=${result.updated} created=${result.created} dir=${dirPath}`);
1556+
console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} dir=${dirPath}`);
15251557
}
15261558
}
15271559

0 commit comments

Comments
 (0)