@leeguoo/yapi-mcp 0.3.26 → 0.4.0

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 (81) hide show
  1. package/dist/cli/commands/col.d.ts +2 -0
  2. package/dist/cli/commands/col.js +80 -0
  3. package/dist/cli/commands/col.js.map +1 -0
  4. package/dist/cli/commands/docs-sync.d.ts +3 -0
  5. package/dist/cli/commands/docs-sync.js +1011 -0
  6. package/dist/cli/commands/docs-sync.js.map +1 -0
  7. package/dist/cli/commands/env.d.ts +2 -0
  8. package/dist/cli/commands/env.js +13 -0
  9. package/dist/cli/commands/env.js.map +1 -0
  10. package/dist/cli/commands/export.d.ts +2 -0
  11. package/dist/cli/commands/export.js +86 -0
  12. package/dist/cli/commands/export.js.map +1 -0
  13. package/dist/cli/commands/follow.d.ts +2 -0
  14. package/dist/cli/commands/follow.js +8 -0
  15. package/dist/cli/commands/follow.js.map +1 -0
  16. package/dist/cli/commands/group.d.ts +2 -0
  17. package/dist/cli/commands/group.js +22 -0
  18. package/dist/cli/commands/group.js.map +1 -0
  19. package/dist/cli/commands/install-skill.d.ts +1 -0
  20. package/dist/cli/commands/install-skill.js +9 -0
  21. package/dist/cli/commands/install-skill.js.map +1 -0
  22. package/dist/cli/commands/interface.d.ts +2 -0
  23. package/dist/cli/commands/interface.js +99 -0
  24. package/dist/cli/commands/interface.js.map +1 -0
  25. package/dist/cli/commands/log.d.ts +2 -0
  26. package/dist/cli/commands/log.js +31 -0
  27. package/dist/cli/commands/log.js.map +1 -0
  28. package/dist/cli/commands/login.d.ts +2 -0
  29. package/dist/cli/commands/login.js +90 -0
  30. package/dist/cli/commands/login.js.map +1 -0
  31. package/dist/cli/commands/logout.d.ts +2 -0
  32. package/dist/cli/commands/logout.js +25 -0
  33. package/dist/cli/commands/logout.js.map +1 -0
  34. package/dist/cli/commands/member.d.ts +2 -0
  35. package/dist/cli/commands/member.js +25 -0
  36. package/dist/cli/commands/member.js.map +1 -0
  37. package/dist/cli/commands/project.d.ts +2 -0
  38. package/dist/cli/commands/project.js +49 -0
  39. package/dist/cli/commands/project.js.map +1 -0
  40. package/dist/cli/commands/request.d.ts +2 -0
  41. package/dist/cli/commands/request.js +189 -0
  42. package/dist/cli/commands/request.js.map +1 -0
  43. package/dist/cli/commands/search.d.ts +2 -0
  44. package/dist/cli/commands/search.js +39 -0
  45. package/dist/cli/commands/search.js.map +1 -0
  46. package/dist/cli/commands/self-update.d.ts +1 -0
  47. package/dist/cli/commands/self-update.js +19 -0
  48. package/dist/cli/commands/self-update.js.map +1 -0
  49. package/dist/cli/commands/user.d.ts +2 -0
  50. package/dist/cli/commands/user.js +27 -0
  51. package/dist/cli/commands/user.js.map +1 -0
  52. package/dist/cli/commands/whoami.d.ts +2 -0
  53. package/dist/cli/commands/whoami.js +8 -0
  54. package/dist/cli/commands/whoami.js.map +1 -0
  55. package/dist/cli/http.d.ts +9 -0
  56. package/dist/cli/http.js +44 -0
  57. package/dist/cli/http.js.map +1 -0
  58. package/dist/cli/simple-request.d.ts +2 -0
  59. package/dist/cli/simple-request.js +135 -0
  60. package/dist/cli/simple-request.js.map +1 -0
  61. package/dist/cli/types.d.ts +161 -0
  62. package/dist/cli/types.js +3 -0
  63. package/dist/cli/types.js.map +1 -0
  64. package/dist/cli/utils.d.ts +53 -0
  65. package/dist/cli/utils.js +610 -0
  66. package/dist/cli/utils.js.map +1 -0
  67. package/dist/yapi-cli.js +441 -3134
  68. package/dist/yapi-cli.js.map +1 -1
  69. package/package.json +12 -25
  70. package/dist/cli.d.ts +0 -2
  71. package/dist/cli.js +0 -32
  72. package/dist/cli.js.map +0 -1
  73. package/dist/config.d.ts +0 -28
  74. package/dist/config.js +0 -240
  75. package/dist/config.js.map +0 -1
  76. package/dist/index.d.ts +0 -5
  77. package/dist/index.js +0 -66
  78. package/dist/index.js.map +0 -1
  79. package/dist/server.d.ts +0 -28
  80. package/dist/server.js +0 -1004
  81. package/dist/server.js.map +0 -1
@@ -0,0 +1,1011 @@
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.runDocsSyncBindings = runDocsSyncBindings;
7
+ exports.runDocsSync = runDocsSync;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const utils_1 = require("../utils");
11
+ const http_1 = require("../http");
12
+ const markdown_1 = require("../../docs/markdown");
13
+ const auth_1 = require("../../services/yapi/auth");
14
+ function findDocsSyncHome(startDir) {
15
+ let current = path_1.default.resolve(startDir);
16
+ while (true) {
17
+ const candidate = path_1.default.join(current, ".yapi");
18
+ if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isDirectory()) {
19
+ return candidate;
20
+ }
21
+ const parent = path_1.default.dirname(current);
22
+ if (parent === current)
23
+ return null;
24
+ current = parent;
25
+ }
26
+ }
27
+ function resolveDocsSyncHome(startDir, ensure) {
28
+ const found = findDocsSyncHome(startDir);
29
+ if (found)
30
+ return found;
31
+ if (!ensure)
32
+ return null;
33
+ const home = path_1.default.join(path_1.default.resolve(startDir), ".yapi");
34
+ fs_1.default.mkdirSync(home, { recursive: true });
35
+ ensureDocsSyncReadme(home);
36
+ return home;
37
+ }
38
+ function globalYapiHomeDir() {
39
+ return path_1.default.resolve(process.env.YAPI_HOME || path_1.default.join(require("os").homedir(), ".yapi"));
40
+ }
41
+ function normalizeComparablePath(targetPath) {
42
+ try {
43
+ return fs_1.default.realpathSync.native(targetPath);
44
+ }
45
+ catch {
46
+ return path_1.default.resolve(targetPath);
47
+ }
48
+ }
49
+ function isGlobalDocsSyncHome(homeDir) {
50
+ return normalizeComparablePath(homeDir) === normalizeComparablePath(globalYapiHomeDir());
51
+ }
52
+ function docsSyncConfigPath(homeDir) {
53
+ return path_1.default.join(homeDir, "docs-sync.json");
54
+ }
55
+ function docsSyncLinksPath(homeDir) {
56
+ return path_1.default.join(homeDir, "docs-sync.links.json");
57
+ }
58
+ function docsSyncProjectsPath(homeDir) {
59
+ return path_1.default.join(homeDir, "docs-sync.projects.json");
60
+ }
61
+ function docsSyncDeploymentsPath(homeDir) {
62
+ return path_1.default.join(homeDir, "docs-sync.deployments.json");
63
+ }
64
+ const DOCS_SYNC_README = [
65
+ "# YApi Docs Sync",
66
+ "",
67
+ "This folder is generated by the yapi docs-sync tool.",
68
+ "Extra caches are written when running docs-sync with bindings.",
69
+ "",
70
+ "Project: https://github.com/leeguooooo/cross-request-master",
71
+ "",
72
+ "Files:",
73
+ "- docs-sync.json: bindings and file-to-doc ID mapping.",
74
+ "- docs-sync.links.json: local docs to YApi doc URLs.",
75
+ "- docs-sync.projects.json: cached project metadata/envs.",
76
+ "- docs-sync.deployments.json: local docs to deployed URLs.",
77
+ ].join("\n");
78
+ function ensureDocsSyncReadme(homeDir) {
79
+ const readmePath = path_1.default.join(homeDir, "README.md");
80
+ if (fs_1.default.existsSync(readmePath))
81
+ return;
82
+ fs_1.default.writeFileSync(readmePath, `${DOCS_SYNC_README}\n`, "utf8");
83
+ }
84
+ function loadDocsSyncConfig(homeDir) {
85
+ const configPath = docsSyncConfigPath(homeDir);
86
+ if (!fs_1.default.existsSync(configPath)) {
87
+ return { version: 1, bindings: {} };
88
+ }
89
+ const raw = fs_1.default.readFileSync(configPath, "utf8");
90
+ const parsed = JSON.parse(raw);
91
+ const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
92
+ return {
93
+ version: typeof parsed.version === "number" ? parsed.version : 1,
94
+ bindings: bindings,
95
+ };
96
+ }
97
+ function saveDocsSyncConfig(homeDir, config) {
98
+ const configPath = docsSyncConfigPath(homeDir);
99
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
100
+ }
101
+ function loadDocsSyncLinks(homeDir) {
102
+ const configPath = docsSyncLinksPath(homeDir);
103
+ if (!fs_1.default.existsSync(configPath)) {
104
+ return { version: 1, bindings: {} };
105
+ }
106
+ const raw = fs_1.default.readFileSync(configPath, "utf8");
107
+ const parsed = JSON.parse(raw);
108
+ const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
109
+ return {
110
+ version: typeof parsed.version === "number" ? parsed.version : 1,
111
+ bindings: bindings,
112
+ };
113
+ }
114
+ function saveDocsSyncLinks(homeDir, config) {
115
+ const configPath = docsSyncLinksPath(homeDir);
116
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
117
+ }
118
+ function loadDocsSyncProjects(homeDir) {
119
+ const configPath = docsSyncProjectsPath(homeDir);
120
+ if (!fs_1.default.existsSync(configPath)) {
121
+ return { version: 1, projects: {} };
122
+ }
123
+ const raw = fs_1.default.readFileSync(configPath, "utf8");
124
+ const parsed = JSON.parse(raw);
125
+ const projects = parsed.projects && typeof parsed.projects === "object" ? parsed.projects : {};
126
+ return {
127
+ version: typeof parsed.version === "number" ? parsed.version : 1,
128
+ projects: projects,
129
+ };
130
+ }
131
+ function saveDocsSyncProjects(homeDir, config) {
132
+ const configPath = docsSyncProjectsPath(homeDir);
133
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
134
+ }
135
+ function loadDocsSyncDeployments(homeDir) {
136
+ const configPath = docsSyncDeploymentsPath(homeDir);
137
+ if (!fs_1.default.existsSync(configPath)) {
138
+ return { version: 1, bindings: {} };
139
+ }
140
+ const raw = fs_1.default.readFileSync(configPath, "utf8");
141
+ const parsed = JSON.parse(raw);
142
+ const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
143
+ return {
144
+ version: typeof parsed.version === "number" ? parsed.version : 1,
145
+ bindings: bindings,
146
+ };
147
+ }
148
+ function saveDocsSyncDeployments(homeDir, config) {
149
+ const configPath = docsSyncDeploymentsPath(homeDir);
150
+ fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
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
+ function getBindingBaseDir(homeDir, rootDir, cwd) {
167
+ if (!isGlobalDocsSyncHome(homeDir)) {
168
+ return { baseDir: rootDir, gitRoot: (0, utils_1.findGitRoot)(cwd), usedGitRoot: false };
169
+ }
170
+ const gitRoot = (0, utils_1.findGitRoot)(cwd);
171
+ if (gitRoot) {
172
+ return { baseDir: gitRoot, gitRoot, usedGitRoot: true };
173
+ }
174
+ return { baseDir: path_1.default.resolve(cwd), gitRoot: null, usedGitRoot: false };
175
+ }
176
+ function normalizeBindingDirForContext(homeDir, rootDir, cwd, bindingDir) {
177
+ const context = getBindingBaseDir(homeDir, rootDir, cwd);
178
+ const resolved = path_1.default.isAbsolute(bindingDir)
179
+ ? bindingDir
180
+ : path_1.default.resolve(context.baseDir, bindingDir);
181
+ const relative = path_1.default.relative(rootDir, resolved);
182
+ if (!relative || relative === ".")
183
+ return ".";
184
+ if (relative.startsWith("..") || path_1.default.isAbsolute(relative))
185
+ return resolved;
186
+ return relative;
187
+ }
188
+ function resolveBindingDirForContext(homeDir, rootDir, cwd, bindingDir) {
189
+ if (!bindingDir)
190
+ return rootDir;
191
+ if (path_1.default.isAbsolute(bindingDir))
192
+ return bindingDir;
193
+ const direct = path_1.default.resolve(rootDir, bindingDir);
194
+ if (!isGlobalDocsSyncHome(homeDir))
195
+ return direct;
196
+ if (fs_1.default.existsSync(direct))
197
+ return direct;
198
+ const { baseDir } = getBindingBaseDir(homeDir, rootDir, cwd);
199
+ const contextual = path_1.default.resolve(baseDir, bindingDir);
200
+ if (fs_1.default.existsSync(contextual))
201
+ return contextual;
202
+ return direct;
203
+ }
204
+ function suggestDocsSyncDir(startDir) {
205
+ const candidates = ["docs", "doc", "documentation", "release-notes"];
206
+ for (const candidate of candidates) {
207
+ const candidatePath = path_1.default.resolve(startDir, candidate);
208
+ if (fs_1.default.existsSync(candidatePath) && fs_1.default.statSync(candidatePath).isDirectory()) {
209
+ const relative = path_1.default.relative(startDir, candidatePath);
210
+ return relative && relative !== "." ? relative : candidate;
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ function loadMapping(dirPath) {
216
+ const mappingPath = path_1.default.join(dirPath, ".yapi.json");
217
+ if (!fs_1.default.existsSync(mappingPath)) {
218
+ return { mapping: {}, mappingPath };
219
+ }
220
+ const raw = fs_1.default.readFileSync(mappingPath, "utf8");
221
+ const mapping = JSON.parse(raw);
222
+ return { mapping, mappingPath };
223
+ }
224
+ function saveMapping(mapping, mappingPath) {
225
+ fs_1.default.writeFileSync(mappingPath, `${JSON.stringify(mapping, null, 2)}\n`, "utf8");
226
+ }
227
+ function resolveSourceFiles(dirPath, mapping) {
228
+ const sources = Array.isArray(mapping.source_files) ? mapping.source_files : [];
229
+ if (!sources.length) {
230
+ return fs_1.default
231
+ .readdirSync(dirPath)
232
+ .filter((name) => name.endsWith(".md") && name !== "README.md")
233
+ .map((name) => path_1.default.join(dirPath, name))
234
+ .sort((a, b) => a.localeCompare(b));
235
+ }
236
+ return sources.map((source) => {
237
+ const resolved = path_1.default.isAbsolute(source) ? source : path_1.default.resolve(dirPath, source);
238
+ if (!fs_1.default.existsSync(resolved) || !fs_1.default.statSync(resolved).isFile()) {
239
+ throw new Error(`source file not found: ${resolved}`);
240
+ }
241
+ return resolved;
242
+ });
243
+ }
244
+ async function listExistingInterfaces(catId, request) {
245
+ const resp = await request("/api/interface/list_cat", "GET", {
246
+ catid: catId,
247
+ page: 1,
248
+ limit: 10000,
249
+ });
250
+ if (resp?.errcode !== 0) {
251
+ throw new Error(`list_cat failed: ${resp?.errmsg || "unknown error"}`);
252
+ }
253
+ const items = resp?.data?.list || [];
254
+ const byPath = {};
255
+ const byTitle = {};
256
+ const byId = {};
257
+ for (const item of items) {
258
+ const apiPath = item?.path;
259
+ const title = item?.title;
260
+ const itemId = item?._id;
261
+ if (apiPath && itemId)
262
+ byPath[apiPath] = Number(itemId);
263
+ if (title && itemId)
264
+ byTitle[title] = Number(itemId);
265
+ if (itemId) {
266
+ byId[String(itemId)] = { path: apiPath || "", title: title || "" };
267
+ }
268
+ }
269
+ return { byPath, byTitle, byId };
270
+ }
271
+ function buildAddPayload(template, title, apiPath, catId, projectId) {
272
+ return {
273
+ title,
274
+ path: apiPath,
275
+ method: template.method || "GET",
276
+ catid: catId,
277
+ project_id: projectId,
278
+ status: template.status || "undone",
279
+ type: template.type || "static",
280
+ api_opened: template.api_opened || false,
281
+ req_query: template.req_query || [],
282
+ req_headers: template.req_headers || [],
283
+ req_params: template.req_params || [],
284
+ req_body_type: template.req_body_type || "json",
285
+ req_body_form: template.req_body_form || [],
286
+ req_body_is_json_schema: template.req_body_is_json_schema ?? true,
287
+ res_body_type: template.res_body_type || "json",
288
+ res_body: template.res_body || '{"type":"object","title":"title","properties":{}}',
289
+ res_body_is_json_schema: template.res_body_is_json_schema ?? true,
290
+ tag: template.tag || [],
291
+ };
292
+ }
293
+ function buildUpdatePayload(docId, title, markdown, html) {
294
+ const payload = { id: docId, markdown, desc: html };
295
+ if (title) {
296
+ payload.title = title;
297
+ }
298
+ return payload;
299
+ }
300
+ function pickLargestMermaid(metrics) {
301
+ return metrics
302
+ .filter((item) => item.renderer === "mermaid")
303
+ .sort((a, b) => b.renderedBytes - a.renderedBytes)[0];
304
+ }
305
+ function buildDocsSyncPreviewLine(item) {
306
+ const parts = [
307
+ `file=${item.fileName}`,
308
+ `action=${item.action}`,
309
+ `markdown=${(0, utils_1.formatBytes)(item.markdownBytes)}`,
310
+ `html=${(0, utils_1.formatBytes)(item.htmlBytes)}`,
311
+ `payload=${(0, utils_1.formatBytes)(item.payloadBytes)}`,
312
+ `path=${item.apiPath}`,
313
+ ];
314
+ if (item.docId) {
315
+ parts.push(`doc_id=${item.docId}`);
316
+ }
317
+ if (item.largestMermaid) {
318
+ parts.push(`largest_mermaid=#${item.largestMermaid.index}`, `largest_mermaid_svg=${(0, utils_1.formatBytes)(item.largestMermaid.renderedBytes)}`);
319
+ }
320
+ return `preview ${parts.join(" ")}`;
321
+ }
322
+ function buildDocsSyncPayloadTooLargeMessage(fileName, preview, error) {
323
+ const lines = [
324
+ `413 Payload Too Large while syncing ${fileName}`,
325
+ `- request payload: ${(0, utils_1.formatBytes)(preview.payloadBytes)}`,
326
+ `- markdown size: ${(0, utils_1.formatBytes)(preview.markdownBytes)}`,
327
+ `- rendered html size: ${(0, utils_1.formatBytes)(preview.htmlBytes)}`,
328
+ ];
329
+ const limitBytes = (0, utils_1.parsePayloadLimit)(error.body || error.message);
330
+ if (limitBytes) {
331
+ lines.push(`- server limit: ${(0, utils_1.formatBytes)(limitBytes)}`);
332
+ }
333
+ else {
334
+ lines.push("- server limit: unknown (response did not expose an exact value)");
335
+ }
336
+ if (preview.largestMermaid) {
337
+ lines.push(`- largest Mermaid block: #${preview.largestMermaid.index} -> ${(0, utils_1.formatBytes)(preview.largestMermaid.renderedBytes)}`);
338
+ }
339
+ else {
340
+ lines.push("- largest Mermaid block: none");
341
+ }
342
+ lines.push("- suggestion: run `yapi docs-sync --dry-run ...` to preview all files before upload");
343
+ lines.push("- suggestion: split oversized Mermaid diagrams or move them into separate docs");
344
+ return lines.join("\n");
345
+ }
346
+ async function addInterface(title, apiPath, mapping, request) {
347
+ const projectId = Number(mapping.project_id || 0);
348
+ const catId = Number(mapping.catid || 0);
349
+ if (!projectId || !catId) {
350
+ throw new Error("project_id and catid are required to create new docs");
351
+ }
352
+ let template = {};
353
+ if (mapping.template_id) {
354
+ const resp = await request("/api/interface/get", "GET", { id: mapping.template_id });
355
+ if (resp?.errcode !== 0) {
356
+ throw new Error(`interface get failed: ${resp?.errmsg || "unknown error"}`);
357
+ }
358
+ template = resp?.data || {};
359
+ }
360
+ const payload = buildAddPayload(template, title, apiPath, catId, projectId);
361
+ const resp = await request("/api/interface/add", "POST", {}, payload);
362
+ if (resp?.errcode !== 0) {
363
+ throw new Error(`interface add failed: ${resp?.errmsg || "unknown error"}`);
364
+ }
365
+ const newId = resp?.data?._id;
366
+ if (!newId) {
367
+ throw new Error("interface add succeeded but missing id");
368
+ }
369
+ return Number(newId);
370
+ }
371
+ async function updateInterface(docId, title, markdown, html, request) {
372
+ const payload = buildUpdatePayload(docId, title, markdown, html);
373
+ const resp = await request("/api/interface/up", "POST", {}, payload);
374
+ if (resp?.errcode !== 0) {
375
+ throw new Error(`interface up failed: ${resp?.errmsg || "unknown error"}`);
376
+ }
377
+ }
378
+ async function syncDocsDir(dirPath, mapping, options, request) {
379
+ if (!mapping.files || typeof mapping.files !== "object") {
380
+ mapping.files = {};
381
+ }
382
+ if (!mapping.file_hashes || typeof mapping.file_hashes !== "object") {
383
+ mapping.file_hashes = {};
384
+ }
385
+ const envProjectId = process.env.YAPI_PROJECT_ID;
386
+ const envCatId = process.env.YAPI_CATID;
387
+ const envTemplateId = process.env.YAPI_TEMPLATE_ID;
388
+ if (!mapping.project_id && envProjectId)
389
+ mapping.project_id = Number(envProjectId);
390
+ if (!mapping.catid && envCatId)
391
+ mapping.catid = Number(envCatId);
392
+ if (!mapping.template_id && envTemplateId)
393
+ mapping.template_id = Number(envTemplateId);
394
+ const hasTarget = Boolean(mapping.project_id && mapping.catid);
395
+ if (!hasTarget && !options.dryRun) {
396
+ throw new Error("缺少 project_id/catid。请先绑定或配置:yapi docs-sync bind add --name <binding> --dir <path> --project-id <id> --catid <id>,或在目录下添加 .yapi.json,或设置环境变量 YAPI_PROJECT_ID/YAPI_CATID。");
397
+ }
398
+ const { byPath, byTitle, byId } = hasTarget
399
+ ? await listExistingInterfaces(Number(mapping.catid), request)
400
+ : { byPath: {}, byTitle: {}, byId: {} };
401
+ let updated = 0;
402
+ let created = 0;
403
+ let skipped = 0;
404
+ let previewOnly = 0;
405
+ const fileInfos = {};
406
+ const previews = [];
407
+ const files = resolveSourceFiles(dirPath, mapping);
408
+ for (const mdPath of files) {
409
+ const stem = path_1.default.parse(mdPath).name;
410
+ const relName = path_1.default.basename(mdPath);
411
+ const apiPath = `/${stem}`;
412
+ const markdown = fs_1.default.readFileSync(mdPath, "utf8");
413
+ const desiredTitle = (0, markdown_1.extractFirstMarkdownH1Title)(markdown).trim() || stem;
414
+ let docId = mapping.files[relName];
415
+ if (!docId) {
416
+ docId = byPath[apiPath] || byTitle[desiredTitle] || byTitle[stem];
417
+ if (docId)
418
+ mapping.files[relName] = docId;
419
+ }
420
+ let action = docId ? "update" : hasTarget ? "create" : "preview-only";
421
+ if (!docId && hasTarget) {
422
+ created += 1;
423
+ if (!options.dryRun) {
424
+ docId = await addInterface(desiredTitle, apiPath, mapping, request);
425
+ mapping.files[relName] = docId;
426
+ }
427
+ }
428
+ if (!docId && !hasTarget) {
429
+ previewOnly += 1;
430
+ }
431
+ if (docId) {
432
+ const resolvedPath = byId[String(docId)]?.path || apiPath;
433
+ fileInfos[relName] = { docId: Number(docId), apiPath: resolvedPath };
434
+ }
435
+ const contentHash = (0, utils_1.buildDocsSyncHash)(markdown, options);
436
+ const previousHash = mapping.file_hashes[relName];
437
+ const currentTitle = docId ? byId[String(docId)]?.title : "";
438
+ const titleToUpdate = !docId
439
+ ? undefined
440
+ : !currentTitle || currentTitle !== desiredTitle
441
+ ? desiredTitle
442
+ : undefined;
443
+ const shouldSyncTitle = Boolean(titleToUpdate);
444
+ const shouldSkipUnchanged = !options.force &&
445
+ docId &&
446
+ previousHash &&
447
+ previousHash === contentHash &&
448
+ !shouldSyncTitle;
449
+ if (shouldSkipUnchanged && !options.dryRun) {
450
+ skipped += 1;
451
+ continue;
452
+ }
453
+ const logPrefix = `[docs-sync:${relName}]`;
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
+ });
473
+ if (shouldSkipUnchanged) {
474
+ action = "skip";
475
+ skipped += 1;
476
+ }
477
+ const payloadObject = docId && action !== "create"
478
+ ? buildUpdatePayload(docId, titleToUpdate, markdown, html)
479
+ : buildAddPayload({}, desiredTitle, apiPath, Number(mapping.catid || 0), Number(mapping.project_id || 0));
480
+ const preview = {
481
+ fileName: relName,
482
+ action,
483
+ markdownBytes: Buffer.byteLength(markdown, "utf8"),
484
+ htmlBytes: Buffer.byteLength(html, "utf8"),
485
+ payloadBytes: Buffer.byteLength(JSON.stringify(payloadObject), "utf8"),
486
+ apiPath: docId ? fileInfos[relName]?.apiPath || apiPath : apiPath,
487
+ docId: docId ? Number(docId) : undefined,
488
+ largestMermaid: pickLargestMermaid(diagramMetrics),
489
+ };
490
+ previews.push(preview);
491
+ if (!options.dryRun && docId && action !== "skip") {
492
+ try {
493
+ await updateInterface(docId, titleToUpdate, markdown, html, request);
494
+ }
495
+ catch (error) {
496
+ if (error instanceof http_1.HttpStatusError && error.status === 413) {
497
+ throw new Error(buildDocsSyncPayloadTooLargeMessage(relName, preview, error));
498
+ }
499
+ throw error;
500
+ }
501
+ }
502
+ if (docId && !mermaidFailed && !diagramFailed) {
503
+ mapping.file_hashes[relName] = contentHash;
504
+ }
505
+ if (action !== "skip") {
506
+ updated += 1;
507
+ }
508
+ }
509
+ return { updated, created, skipped, previewOnly, files: fileInfos, previews };
510
+ }
511
+ async function fetchProjectInfo(projectId, baseUrl, request) {
512
+ if (!projectId)
513
+ return null;
514
+ const resp = await request("/api/project/get", "GET", { id: projectId });
515
+ if (resp?.errcode !== 0) {
516
+ throw new Error(`project get failed: ${resp?.errmsg || "unknown error"}`);
517
+ }
518
+ const data = resp?.data || {};
519
+ const name = typeof data.name === "string" ? data.name : "";
520
+ const basepath = typeof data.basepath === "string" ? data.basepath : "";
521
+ const envs = (0, utils_1.normalizeProjectEnvs)(data.env || data.envs);
522
+ return {
523
+ project_id: Number(projectId),
524
+ name,
525
+ basepath,
526
+ envs,
527
+ base_url: baseUrl,
528
+ };
529
+ }
530
+ function buildEnvUrls(projectInfo, apiPath) {
531
+ const urls = {};
532
+ if (!projectInfo || !projectInfo.envs || !projectInfo.envs.length)
533
+ return urls;
534
+ const basepath = projectInfo.basepath || "";
535
+ projectInfo.envs.forEach((env, index) => {
536
+ const rawKey = env.name || env.domain || `env${index + 1}`;
537
+ const url = (0, utils_1.buildEnvUrl)(env.domain, basepath, apiPath);
538
+ if (!url)
539
+ return;
540
+ let key = rawKey;
541
+ let suffix = 1;
542
+ while (urls[key]) {
543
+ suffix += 1;
544
+ key = `${rawKey}-${suffix}`;
545
+ }
546
+ urls[key] = url;
547
+ });
548
+ return urls;
549
+ }
550
+ async function updateDocsSyncCaches(homeDir, baseUrl, bindingResults, request) {
551
+ const linksConfig = loadDocsSyncLinks(homeDir);
552
+ const projectsConfig = loadDocsSyncProjects(homeDir);
553
+ const deploymentsConfig = loadDocsSyncDeployments(homeDir);
554
+ const projectCache = new Map();
555
+ const resolveProjectInfo = async (projectId) => {
556
+ if (!projectId)
557
+ return null;
558
+ if (projectCache.has(projectId))
559
+ return projectCache.get(projectId) || null;
560
+ const existing = projectsConfig.projects[String(projectId)] || null;
561
+ try {
562
+ const fresh = await fetchProjectInfo(projectId, baseUrl, request);
563
+ if (fresh) {
564
+ projectsConfig.projects[String(projectId)] = fresh;
565
+ projectCache.set(projectId, fresh);
566
+ return fresh;
567
+ }
568
+ }
569
+ catch (error) {
570
+ const message = error instanceof Error ? error.message : String(error);
571
+ console.warn(`project get failed (project_id=${projectId}): ${message}`);
572
+ }
573
+ projectCache.set(projectId, existing);
574
+ return existing;
575
+ };
576
+ for (const [name, result] of Object.entries(bindingResults)) {
577
+ const binding = result.binding;
578
+ const projectId = Number(binding.project_id || 0);
579
+ const linkFiles = {};
580
+ const deploymentFiles = {};
581
+ const projectInfo = await resolveProjectInfo(projectId);
582
+ for (const [fileName, fileInfo] of Object.entries(result.files)) {
583
+ const docUrl = projectId ? (0, utils_1.buildInterfaceWebUrl)(baseUrl, projectId, fileInfo.docId) : "";
584
+ linkFiles[fileName] = {
585
+ doc_id: fileInfo.docId,
586
+ api_path: fileInfo.apiPath,
587
+ url: docUrl,
588
+ };
589
+ deploymentFiles[fileName] = {
590
+ api_path: fileInfo.apiPath,
591
+ env_urls: buildEnvUrls(projectInfo, fileInfo.apiPath),
592
+ };
593
+ }
594
+ linksConfig.bindings[name] = {
595
+ dir: binding.dir,
596
+ project_id: projectId || undefined,
597
+ catid: binding.catid,
598
+ files: linkFiles,
599
+ };
600
+ deploymentsConfig.bindings[name] = {
601
+ dir: binding.dir,
602
+ project_id: projectId || undefined,
603
+ files: deploymentFiles,
604
+ };
605
+ }
606
+ saveDocsSyncLinks(homeDir, linksConfig);
607
+ saveDocsSyncProjects(homeDir, projectsConfig);
608
+ saveDocsSyncDeployments(homeDir, deploymentsConfig);
609
+ }
610
+ async function runDocsSyncBindings(action, args) {
611
+ if (args.help) {
612
+ return 0;
613
+ }
614
+ const writeActions = new Set(["add", "update", "remove", "rm", "delete", "del"]);
615
+ const readActions = new Set(["list", "get"]);
616
+ if (!writeActions.has(action) && !readActions.has(action)) {
617
+ console.error(`unknown docs-sync bind action: ${action}`);
618
+ return 2;
619
+ }
620
+ const homeDir = resolveDocsSyncHome(process.cwd(), writeActions.has(action));
621
+ if (!homeDir) {
622
+ if (action === "list") {
623
+ console.log("no docs-sync bindings (missing .yapi/docs-sync.json)");
624
+ return 0;
625
+ }
626
+ console.error("missing .yapi/docs-sync.json (run in project root or create one with docs-sync bind add)");
627
+ return 2;
628
+ }
629
+ const rootDir = path_1.default.dirname(homeDir);
630
+ const config = loadDocsSyncConfig(homeDir);
631
+ if (action === "list") {
632
+ const entries = Object.entries(config.bindings).sort(([a], [b]) => a.localeCompare(b));
633
+ if (!entries.length) {
634
+ console.log("no docs-sync bindings");
635
+ return 0;
636
+ }
637
+ for (const [name, binding] of entries) {
638
+ const filesCount = binding.files ? Object.keys(binding.files).length : 0;
639
+ console.log(`${name} dir=${binding.dir} project_id=${binding.project_id ?? ""} catid=${binding.catid ?? ""}` +
640
+ (binding.template_id ? ` template_id=${binding.template_id}` : "") +
641
+ ` files=${filesCount}`);
642
+ }
643
+ return 0;
644
+ }
645
+ if (!args.name) {
646
+ console.error("missing --name for docs-sync bind action");
647
+ return 2;
648
+ }
649
+ if (action === "get") {
650
+ const binding = config.bindings[args.name];
651
+ if (!binding) {
652
+ console.error(`binding not found: ${args.name}`);
653
+ return 2;
654
+ }
655
+ console.log(JSON.stringify(binding, null, 2));
656
+ return 0;
657
+ }
658
+ if (action === "remove" || action === "rm" || action === "delete" || action === "del") {
659
+ if (!config.bindings[args.name]) {
660
+ console.error(`binding not found: ${args.name}`);
661
+ return 2;
662
+ }
663
+ delete config.bindings[args.name];
664
+ saveDocsSyncConfig(homeDir, config);
665
+ console.log(`binding removed: ${args.name}`);
666
+ return 0;
667
+ }
668
+ const hasExisting = Boolean(config.bindings[args.name]);
669
+ if (action === "add" && hasExisting) {
670
+ console.error(`binding already exists: ${args.name}`);
671
+ return 2;
672
+ }
673
+ if (action === "update" && !hasExisting) {
674
+ console.error(`binding not found: ${args.name}`);
675
+ return 2;
676
+ }
677
+ if (action === "add") {
678
+ if (!args.dir ||
679
+ !Number.isFinite(args.projectId ?? NaN) ||
680
+ !Number.isFinite(args.catId ?? NaN)) {
681
+ console.error("add requires --dir, --project-id, and --catid");
682
+ return 2;
683
+ }
684
+ }
685
+ const existing = config.bindings[args.name] || {};
686
+ const next = {
687
+ dir: existing.dir || "",
688
+ project_id: existing.project_id,
689
+ catid: existing.catid,
690
+ template_id: existing.template_id,
691
+ source_files: existing.source_files ? [...existing.source_files] : undefined,
692
+ files: existing.files ? { ...existing.files } : {},
693
+ file_hashes: existing.file_hashes ? { ...existing.file_hashes } : {},
694
+ };
695
+ if (args.dir) {
696
+ next.dir = normalizeBindingDirForContext(homeDir, rootDir, process.cwd(), args.dir);
697
+ }
698
+ if (args.projectId !== undefined && Number.isFinite(args.projectId)) {
699
+ next.project_id = Number(args.projectId);
700
+ }
701
+ if (args.catId !== undefined && Number.isFinite(args.catId)) {
702
+ next.catid = Number(args.catId);
703
+ }
704
+ if (args.templateId !== undefined && Number.isFinite(args.templateId)) {
705
+ next.template_id = Number(args.templateId);
706
+ }
707
+ if (args.clearSourceFiles) {
708
+ next.source_files = [];
709
+ }
710
+ else if (args.sourceFiles && args.sourceFiles.length) {
711
+ next.source_files = args.sourceFiles;
712
+ }
713
+ if (!next.dir || !next.project_id || !next.catid) {
714
+ console.error("binding requires dir/project_id/catid");
715
+ return 2;
716
+ }
717
+ config.bindings[args.name] = next;
718
+ saveDocsSyncConfig(homeDir, config);
719
+ const resolvedDir = resolveBindingDirForContext(homeDir, rootDir, process.cwd(), next.dir);
720
+ console.log(`${action === "add" ? "binding added" : "binding updated"}: ${args.name}`);
721
+ console.log(`stored_dir=${next.dir}`);
722
+ console.log(`resolved_dir=${resolvedDir}`);
723
+ const gitRoot = (0, utils_1.findGitRoot)(process.cwd());
724
+ if (gitRoot) {
725
+ console.log(`git_root=${gitRoot}`);
726
+ }
727
+ else if (isGlobalDocsSyncHome(homeDir)) {
728
+ console.warn("warning: no git root detected, relative --dir was resolved from current working directory");
729
+ }
730
+ return 0;
731
+ }
732
+ async function runDocsSync(options) {
733
+ try {
734
+ if (!(0, markdown_1.isPandocAvailable)()) {
735
+ console.warn("pandoc not found, fallback to markdown-it renderer.");
736
+ console.warn("Install pandoc (macOS): brew install pandoc");
737
+ console.warn("More info: https://pandoc.org/installing.html");
738
+ }
739
+ if (!options.noMermaid && !(0, markdown_1.isMmdcAvailable)()) {
740
+ console.warn("mmdc not found, Mermaid blocks will stay as code.");
741
+ console.warn("Install mermaid-cli: npm i -g @mermaid-js/mermaid-cli");
742
+ }
743
+ if (!(0, markdown_1.isPlantUmlAvailable)()) {
744
+ console.warn("plantuml not found, PlantUML blocks will be removed from HTML.");
745
+ console.warn("Install PlantUML (macOS): brew install plantuml");
746
+ }
747
+ if (!(0, markdown_1.isGraphvizAvailable)()) {
748
+ console.warn("graphviz (dot) not found, Graphviz blocks will be removed from HTML.");
749
+ console.warn("Install Graphviz (macOS): brew install graphviz");
750
+ }
751
+ if (!(0, markdown_1.isD2Available)()) {
752
+ console.warn("d2 not found, D2 blocks will be removed from HTML.");
753
+ console.warn("Install D2 (macOS): brew install d2");
754
+ }
755
+ if (options.bindings.length && options.dirs.length) {
756
+ console.error("use --binding or --dir, not both");
757
+ return 2;
758
+ }
759
+ const docsSyncHome = resolveDocsSyncHome(process.cwd(), false);
760
+ const docsSyncConfig = docsSyncHome ? loadDocsSyncConfig(docsSyncHome) : null;
761
+ let bindingNames = options.bindings;
762
+ let useBindings = bindingNames.length > 0;
763
+ if (!useBindings &&
764
+ !options.dirs.length &&
765
+ docsSyncConfig &&
766
+ Object.keys(docsSyncConfig.bindings).length) {
767
+ useBindings = true;
768
+ bindingNames = Object.keys(docsSyncConfig.bindings);
769
+ }
770
+ if (useBindings && (!docsSyncHome || !docsSyncConfig)) {
771
+ console.error("missing .yapi/docs-sync.json (run docs-sync bind add or use --dir)");
772
+ return 2;
773
+ }
774
+ if (useBindings && !bindingNames.length) {
775
+ console.error("no docs-sync bindings found (run docs-sync bind add or use --dir)");
776
+ return 2;
777
+ }
778
+ const usingDefaultDir = !useBindings && !options.dirs.length;
779
+ const dirs = useBindings ? [] : options.dirs.length ? options.dirs : ["docs/release-notes"];
780
+ if (!useBindings) {
781
+ const missingDirs = dirs.filter((dir) => {
782
+ const dirPath = path_1.default.resolve(dir);
783
+ return !fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory();
784
+ });
785
+ if (missingDirs.length) {
786
+ const firstMissing = path_1.default.resolve(missingDirs[0]);
787
+ console.error(`dir not found: ${firstMissing}`);
788
+ if (usingDefaultDir) {
789
+ const suggestion = suggestDocsSyncDir(process.cwd());
790
+ if (suggestion) {
791
+ console.error(`hint: pass --dir ${suggestion}`);
792
+ }
793
+ console.error("hint: or bind a directory with yapi docs-sync bind add");
794
+ }
795
+ else {
796
+ console.error("hint: pass --dir <existing_dir> or bind a directory");
797
+ }
798
+ return 2;
799
+ }
800
+ }
801
+ let config = {};
802
+ let configPath = options.config || "";
803
+ if (options.config) {
804
+ if (fs_1.default.existsSync(configPath)) {
805
+ config = (0, utils_1.parseSimpleToml)(fs_1.default.readFileSync(configPath, "utf8"));
806
+ }
807
+ else {
808
+ const init = await (0, utils_1.initConfigIfMissing)(options);
809
+ if (init) {
810
+ config = init.config;
811
+ configPath = init.configPath;
812
+ }
813
+ else {
814
+ console.error(`missing config file: ${configPath}`);
815
+ return 2;
816
+ }
817
+ }
818
+ }
819
+ else {
820
+ const globalPath = (0, utils_1.globalConfigPath)();
821
+ if (fs_1.default.existsSync(globalPath)) {
822
+ configPath = globalPath;
823
+ config = (0, utils_1.parseSimpleToml)(fs_1.default.readFileSync(globalPath, "utf8"));
824
+ }
825
+ else {
826
+ const init = await (0, utils_1.initConfigIfMissing)(options);
827
+ if (init) {
828
+ config = init.config;
829
+ configPath = init.configPath;
830
+ }
831
+ else {
832
+ console.error("missing config: create ~/.yapi/config.toml or pass --config");
833
+ return 2;
834
+ }
835
+ }
836
+ }
837
+ const baseUrl = options.baseUrl || config.base_url || "";
838
+ if (!baseUrl) {
839
+ console.error("missing --base-url or config base_url");
840
+ return 2;
841
+ }
842
+ const projectId = options.projectId || config.project_id || "";
843
+ const rawToken = options.token || config.token || "";
844
+ const token = (0, utils_1.resolveToken)(rawToken, projectId);
845
+ let authMode = (options.authMode || config.auth_mode || "").trim().toLowerCase();
846
+ if (!authMode) {
847
+ authMode = token
848
+ ? "token"
849
+ : options.email || options.password || config.email || config.password
850
+ ? "global"
851
+ : "token";
852
+ }
853
+ if (authMode !== "token" && authMode !== "global") {
854
+ console.error("invalid --auth-mode (use token or global)");
855
+ return 2;
856
+ }
857
+ const headers = {};
858
+ const email = options.email || config.email || "";
859
+ const password = options.password || config.password || "";
860
+ const authService = authMode === "global"
861
+ ? new auth_1.YApiAuthService(baseUrl, email || "", password || "", "warn", {
862
+ timeoutMs: options.timeout || 30000,
863
+ })
864
+ : null;
865
+ const canRelogin = authMode === "global" &&
866
+ Boolean(authService) &&
867
+ Boolean(email) &&
868
+ Boolean(password) &&
869
+ !options.cookie;
870
+ if (options.cookie) {
871
+ headers.Cookie = options.cookie;
872
+ }
873
+ else if (authMode === "global") {
874
+ const cachedCookie = authService?.getCachedCookieHeader();
875
+ if (cachedCookie) {
876
+ headers.Cookie = cachedCookie;
877
+ }
878
+ else if (email && password && authService) {
879
+ try {
880
+ headers.Cookie = await authService.getCookieHeaderWithLogin();
881
+ }
882
+ catch (error) {
883
+ console.error(error instanceof Error ? error.message : String(error));
884
+ return 2;
885
+ }
886
+ }
887
+ else {
888
+ console.error("missing email/password for global auth");
889
+ return 2;
890
+ }
891
+ }
892
+ const request = async (endpoint, method, query = {}, data) => {
893
+ const queryItems = [];
894
+ for (const [key, value] of Object.entries(query)) {
895
+ if (value !== undefined && value !== null) {
896
+ queryItems.push([key, String(value)]);
897
+ }
898
+ }
899
+ const url = (0, http_1.buildUrl)(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
900
+ let body;
901
+ if (data !== undefined) {
902
+ body = JSON.stringify(data);
903
+ }
904
+ const sendOnce = async () => {
905
+ const requestHeaders = { ...headers };
906
+ if (body !== undefined) {
907
+ requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
908
+ }
909
+ const response = await (0, http_1.fetchWithTimeout)(url, {
910
+ method,
911
+ headers: requestHeaders,
912
+ body,
913
+ }, options.timeout || 30000);
914
+ const text = await response.text();
915
+ return { response, text, json: (0, utils_1.parseJsonMaybe)(text) };
916
+ };
917
+ let result = await sendOnce();
918
+ if (canRelogin && (0, utils_1.looksLikeAuthError)(result.response.status, result.json)) {
919
+ try {
920
+ headers.Cookie = await authService.getCookieHeaderWithLogin({ forceLogin: true });
921
+ }
922
+ catch (error) {
923
+ throw new Error(error instanceof Error ? error.message : String(error));
924
+ }
925
+ result = await sendOnce();
926
+ }
927
+ if (!result.response.ok) {
928
+ throw new http_1.HttpStatusError(endpoint, result.response.status, result.response.statusText, result.text);
929
+ }
930
+ if (!result.json) {
931
+ throw new Error(`invalid JSON response from ${endpoint}`);
932
+ }
933
+ return result.json;
934
+ };
935
+ if (useBindings) {
936
+ const rootDir = path_1.default.dirname(docsSyncHome);
937
+ const configForBindings = docsSyncConfig;
938
+ const dirToBindings = new Map();
939
+ for (const name of bindingNames) {
940
+ const binding = configForBindings.bindings[name];
941
+ if (!binding) {
942
+ throw new Error(`binding not found: ${name}`);
943
+ }
944
+ const dirPath = resolveBindingDirForContext(docsSyncHome, rootDir, process.cwd(), binding.dir);
945
+ const existing = dirToBindings.get(dirPath) || [];
946
+ existing.push(name);
947
+ dirToBindings.set(dirPath, existing);
948
+ }
949
+ const duplicates = Array.from(dirToBindings.entries()).filter(([, names]) => names.length > 1);
950
+ if (duplicates.length) {
951
+ const lines = [];
952
+ lines.push("invalid docs-sync bindings: multiple bindings share the same dir");
953
+ duplicates.forEach(([dirPath, names]) => {
954
+ lines.push(`- dir=${dirPath} bindings=${names.join(", ")}`);
955
+ });
956
+ lines.push("");
957
+ lines.push("Fix: split docs into separate directories (recommended).");
958
+ lines.push("Example:");
959
+ lines.push(" yapi docs-sync bind update --name <bindingA> --dir docs/yapi-sync/<bindingA>");
960
+ lines.push(" yapi docs-sync bind update --name <bindingB> --dir docs/yapi-sync/<bindingB>");
961
+ throw new Error(lines.join("\n"));
962
+ }
963
+ const bindingResults = {};
964
+ for (const name of bindingNames) {
965
+ const binding = configForBindings.bindings[name];
966
+ if (!binding) {
967
+ throw new Error(`binding not found: ${name}`);
968
+ }
969
+ const dirPath = resolveBindingDirForContext(docsSyncHome, rootDir, process.cwd(), binding.dir);
970
+ if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
971
+ throw new Error(`dir not found for binding ${name}: ${dirPath}`);
972
+ }
973
+ const result = await syncDocsDir(dirPath, binding, options, request);
974
+ if (options.dryRun) {
975
+ console.log(`dry-run preview binding=${name}`);
976
+ result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
977
+ }
978
+ console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} binding=${name} dir=${dirPath}`);
979
+ bindingResults[name] = { binding, files: result.files };
980
+ }
981
+ if (!options.dryRun) {
982
+ saveDocsSyncConfig(docsSyncHome, configForBindings);
983
+ await updateDocsSyncCaches(docsSyncHome, baseUrl, bindingResults, request);
984
+ }
985
+ }
986
+ else {
987
+ for (const dir of dirs) {
988
+ const dirPath = path_1.default.resolve(dir);
989
+ if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
990
+ throw new Error(`dir not found: ${dirPath}`);
991
+ }
992
+ const { mapping, mappingPath } = loadMapping(dirPath);
993
+ const result = await syncDocsDir(dirPath, mapping, options, request);
994
+ if (options.dryRun) {
995
+ console.log(`dry-run preview dir=${dirPath}`);
996
+ result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
997
+ }
998
+ if (!options.dryRun) {
999
+ saveMapping(mapping, mappingPath);
1000
+ }
1001
+ console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} dir=${dirPath}`);
1002
+ }
1003
+ }
1004
+ return 0;
1005
+ }
1006
+ catch (error) {
1007
+ console.error(error instanceof Error ? error.message : String(error));
1008
+ return 2;
1009
+ }
1010
+ }
1011
+ //# sourceMappingURL=docs-sync.js.map