@leeguoo/yapi-mcp 0.3.23 → 0.3.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/README.md +40 -3
- package/dist/docs/markdown.d.ts +9 -0
- package/dist/docs/markdown.js +16 -0
- package/dist/docs/markdown.js.map +1 -1
- package/dist/skill/install.js +8 -16
- package/dist/skill/install.js.map +1 -1
- package/dist/skill/metadata.d.ts +29 -0
- package/dist/skill/metadata.js +101 -0
- package/dist/skill/metadata.js.map +1 -0
- package/dist/yapi-cli.js +314 -35
- package/dist/yapi-cli.js.map +1 -1
- package/package.json +1 -1
- package/skill-template/SKILL.md +12 -2
package/dist/yapi-cli.js
CHANGED
|
@@ -12,14 +12,40 @@ const path_1 = __importDefault(require("path"));
|
|
|
12
12
|
const readline_1 = __importDefault(require("readline"));
|
|
13
13
|
const markdown_1 = require("./docs/markdown");
|
|
14
14
|
const install_1 = require("./skill/install");
|
|
15
|
+
const metadata_1 = require("./skill/metadata");
|
|
15
16
|
const auth_1 = require("./services/yapi/auth");
|
|
16
17
|
const authCache_1 = require("./services/yapi/authCache");
|
|
18
|
+
class HttpStatusError extends Error {
|
|
19
|
+
status;
|
|
20
|
+
statusText;
|
|
21
|
+
body;
|
|
22
|
+
endpoint;
|
|
23
|
+
constructor(endpoint, status, statusText, body) {
|
|
24
|
+
super(`request failed: ${status} ${statusText} ${body}`);
|
|
25
|
+
this.name = "HttpStatusError";
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.statusText = statusText;
|
|
28
|
+
this.body = body;
|
|
29
|
+
this.endpoint = endpoint;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
17
32
|
function parseKeyValue(raw) {
|
|
18
33
|
if (!raw || !raw.includes("="))
|
|
19
34
|
throw new Error("expected key=value");
|
|
20
35
|
const idx = raw.indexOf("=");
|
|
21
36
|
return [raw.slice(0, idx), raw.slice(idx + 1)];
|
|
22
37
|
}
|
|
38
|
+
function parseQueryArg(raw) {
|
|
39
|
+
const trimmed = String(raw || "").trim().replace(/^\?/, "");
|
|
40
|
+
if (!trimmed)
|
|
41
|
+
throw new Error("expected key=value");
|
|
42
|
+
if (!trimmed.includes("&"))
|
|
43
|
+
return [parseKeyValue(trimmed)];
|
|
44
|
+
const items = Array.from(new URLSearchParams(trimmed).entries()).filter(([key]) => Boolean(key));
|
|
45
|
+
if (items.length)
|
|
46
|
+
return items;
|
|
47
|
+
return [parseKeyValue(trimmed)];
|
|
48
|
+
}
|
|
23
49
|
function parseHeader(raw) {
|
|
24
50
|
if (!raw || !raw.includes(":"))
|
|
25
51
|
throw new Error("expected Header:Value");
|
|
@@ -34,6 +60,58 @@ function parseJsonMaybe(text) {
|
|
|
34
60
|
return null;
|
|
35
61
|
}
|
|
36
62
|
}
|
|
63
|
+
function formatBytes(bytes) {
|
|
64
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
65
|
+
return "0 B";
|
|
66
|
+
const units = ["B", "KiB", "MiB", "GiB"];
|
|
67
|
+
let value = bytes;
|
|
68
|
+
let index = 0;
|
|
69
|
+
while (value >= 1024 && index < units.length - 1) {
|
|
70
|
+
value /= 1024;
|
|
71
|
+
index += 1;
|
|
72
|
+
}
|
|
73
|
+
return `${value >= 10 || index === 0 ? value.toFixed(0) : value.toFixed(2)} ${units[index]}`;
|
|
74
|
+
}
|
|
75
|
+
function parseByteSize(raw) {
|
|
76
|
+
const text = String(raw || "");
|
|
77
|
+
const bytesMatch = text.match(/(\d+)\s*bytes?/i);
|
|
78
|
+
if (bytesMatch)
|
|
79
|
+
return Number(bytesMatch[1]);
|
|
80
|
+
const unitMatch = text.match(/(\d+(?:\.\d+)?)\s*(kib|kb|mib|mb|gib|gb)\b/i);
|
|
81
|
+
if (!unitMatch)
|
|
82
|
+
return null;
|
|
83
|
+
const value = Number(unitMatch[1]);
|
|
84
|
+
if (!Number.isFinite(value))
|
|
85
|
+
return null;
|
|
86
|
+
const unit = unitMatch[2].toLowerCase();
|
|
87
|
+
const factors = {
|
|
88
|
+
kb: 1000,
|
|
89
|
+
kib: 1024,
|
|
90
|
+
mb: 1000 * 1000,
|
|
91
|
+
mib: 1024 * 1024,
|
|
92
|
+
gb: 1000 * 1000 * 1000,
|
|
93
|
+
gib: 1024 * 1024 * 1024,
|
|
94
|
+
};
|
|
95
|
+
return Math.round(value * (factors[unit] || 1));
|
|
96
|
+
}
|
|
97
|
+
function parsePayloadLimit(text) {
|
|
98
|
+
const match = String(text || "").match(/(?:limit|max(?:imum)?(?:\s+body)?(?:\s+size)?)[^0-9]{0,12}(\d+(?:\.\d+)?\s*(?:bytes?|kib|kb|mib|mb|gib|gb))/i);
|
|
99
|
+
if (match)
|
|
100
|
+
return parseByteSize(match[1]);
|
|
101
|
+
return parseByteSize(text);
|
|
102
|
+
}
|
|
103
|
+
function findGitRoot(startDir) {
|
|
104
|
+
let current = path_1.default.resolve(startDir);
|
|
105
|
+
while (true) {
|
|
106
|
+
const candidate = path_1.default.join(current, ".git");
|
|
107
|
+
if (fs_1.default.existsSync(candidate))
|
|
108
|
+
return current;
|
|
109
|
+
const parent = path_1.default.dirname(current);
|
|
110
|
+
if (parent === current)
|
|
111
|
+
return null;
|
|
112
|
+
current = parent;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
37
115
|
function getPayloadMessage(payload) {
|
|
38
116
|
if (!payload || typeof payload !== "object")
|
|
39
117
|
return "";
|
|
@@ -814,6 +892,7 @@ function usage() {
|
|
|
814
892
|
" yapi whoami [options]",
|
|
815
893
|
" yapi search [options]",
|
|
816
894
|
" yapi install-skill [options]",
|
|
895
|
+
" yapi self-update",
|
|
817
896
|
"Options:",
|
|
818
897
|
" --config <path> config file path (default: ~/.yapi/config.toml)",
|
|
819
898
|
" --base-url <url> YApi base URL",
|
|
@@ -858,6 +937,9 @@ function usage() {
|
|
|
858
937
|
" -h, --help show help",
|
|
859
938
|
].join("\n");
|
|
860
939
|
}
|
|
940
|
+
function selfUpdateUsage() {
|
|
941
|
+
return ["Usage:", " yapi self-update", "", "Install the latest @leeguoo/yapi-mcp globally with npm."].join("\n");
|
|
942
|
+
}
|
|
861
943
|
function docsSyncUsage() {
|
|
862
944
|
return [
|
|
863
945
|
"Usage:",
|
|
@@ -1705,6 +1787,20 @@ function resolveDocsSyncHome(startDir, ensure) {
|
|
|
1705
1787
|
ensureDocsSyncReadme(home);
|
|
1706
1788
|
return home;
|
|
1707
1789
|
}
|
|
1790
|
+
function globalYapiHomeDir() {
|
|
1791
|
+
return path_1.default.resolve(process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi"));
|
|
1792
|
+
}
|
|
1793
|
+
function normalizeComparablePath(targetPath) {
|
|
1794
|
+
try {
|
|
1795
|
+
return fs_1.default.realpathSync.native(targetPath);
|
|
1796
|
+
}
|
|
1797
|
+
catch {
|
|
1798
|
+
return path_1.default.resolve(targetPath);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
function isGlobalDocsSyncHome(homeDir) {
|
|
1802
|
+
return normalizeComparablePath(homeDir) === normalizeComparablePath(globalYapiHomeDir());
|
|
1803
|
+
}
|
|
1708
1804
|
function docsSyncConfigPath(homeDir) {
|
|
1709
1805
|
return path_1.default.join(homeDir, "docs-sync.json");
|
|
1710
1806
|
}
|
|
@@ -1819,6 +1915,44 @@ function normalizeBindingDir(rootDir, bindingDir) {
|
|
|
1819
1915
|
return resolved;
|
|
1820
1916
|
return relative;
|
|
1821
1917
|
}
|
|
1918
|
+
function getBindingBaseDir(homeDir, rootDir, cwd) {
|
|
1919
|
+
if (!isGlobalDocsSyncHome(homeDir)) {
|
|
1920
|
+
return { baseDir: rootDir, gitRoot: findGitRoot(cwd), usedGitRoot: false };
|
|
1921
|
+
}
|
|
1922
|
+
const gitRoot = findGitRoot(cwd);
|
|
1923
|
+
if (gitRoot) {
|
|
1924
|
+
return { baseDir: gitRoot, gitRoot, usedGitRoot: true };
|
|
1925
|
+
}
|
|
1926
|
+
return { baseDir: path_1.default.resolve(cwd), gitRoot: null, usedGitRoot: false };
|
|
1927
|
+
}
|
|
1928
|
+
function normalizeBindingDirForContext(homeDir, rootDir, cwd, bindingDir) {
|
|
1929
|
+
const context = getBindingBaseDir(homeDir, rootDir, cwd);
|
|
1930
|
+
const resolved = path_1.default.isAbsolute(bindingDir)
|
|
1931
|
+
? bindingDir
|
|
1932
|
+
: path_1.default.resolve(context.baseDir, bindingDir);
|
|
1933
|
+
const relative = path_1.default.relative(rootDir, resolved);
|
|
1934
|
+
if (!relative || relative === ".")
|
|
1935
|
+
return ".";
|
|
1936
|
+
if (relative.startsWith("..") || path_1.default.isAbsolute(relative))
|
|
1937
|
+
return resolved;
|
|
1938
|
+
return relative;
|
|
1939
|
+
}
|
|
1940
|
+
function resolveBindingDirForContext(homeDir, rootDir, cwd, bindingDir) {
|
|
1941
|
+
if (!bindingDir)
|
|
1942
|
+
return rootDir;
|
|
1943
|
+
if (path_1.default.isAbsolute(bindingDir))
|
|
1944
|
+
return bindingDir;
|
|
1945
|
+
const direct = path_1.default.resolve(rootDir, bindingDir);
|
|
1946
|
+
if (!isGlobalDocsSyncHome(homeDir))
|
|
1947
|
+
return direct;
|
|
1948
|
+
if (fs_1.default.existsSync(direct))
|
|
1949
|
+
return direct;
|
|
1950
|
+
const { baseDir } = getBindingBaseDir(homeDir, rootDir, cwd);
|
|
1951
|
+
const contextual = path_1.default.resolve(baseDir, bindingDir);
|
|
1952
|
+
if (fs_1.default.existsSync(contextual))
|
|
1953
|
+
return contextual;
|
|
1954
|
+
return direct;
|
|
1955
|
+
}
|
|
1822
1956
|
function suggestDocsSyncDir(startDir) {
|
|
1823
1957
|
const candidates = ["docs", "doc", "documentation", "release-notes"];
|
|
1824
1958
|
for (const candidate of candidates) {
|
|
@@ -2028,6 +2162,37 @@ async function checkForUpdates(options) {
|
|
|
2028
2162
|
}
|
|
2029
2163
|
writeUpdateCache(cache);
|
|
2030
2164
|
}
|
|
2165
|
+
function warnIfInstalledSkillsOutdated(options) {
|
|
2166
|
+
if (options.skip)
|
|
2167
|
+
return;
|
|
2168
|
+
const currentVersion = readVersion();
|
|
2169
|
+
if (!currentVersion || currentVersion === "unknown")
|
|
2170
|
+
return;
|
|
2171
|
+
const outdated = (0, metadata_1.findOutdatedSkillInstalls)(currentVersion);
|
|
2172
|
+
if (!outdated.length)
|
|
2173
|
+
return;
|
|
2174
|
+
const summary = outdated
|
|
2175
|
+
.map((item) => `${item.label}@${item.installedVersion || "unknown"}`)
|
|
2176
|
+
.join(", ");
|
|
2177
|
+
console.warn(`skill update available: installed ${summary}, current ${currentVersion}. Run: yapi install-skill --force`);
|
|
2178
|
+
}
|
|
2179
|
+
function runSelfUpdate(rawArgs) {
|
|
2180
|
+
if (rawArgs.includes("-h") || rawArgs.includes("--help")) {
|
|
2181
|
+
console.log(selfUpdateUsage());
|
|
2182
|
+
return 0;
|
|
2183
|
+
}
|
|
2184
|
+
try {
|
|
2185
|
+
(0, child_process_1.execFileSync)(resolveLocalBin("npm"), ["install", "-g", "@leeguoo/yapi-mcp@latest"], {
|
|
2186
|
+
stdio: "inherit",
|
|
2187
|
+
});
|
|
2188
|
+
console.log("Updated yapi CLI to latest.");
|
|
2189
|
+
return 0;
|
|
2190
|
+
}
|
|
2191
|
+
catch (error) {
|
|
2192
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2193
|
+
return 2;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2031
2196
|
async function fetchProjectInfo(projectId, baseUrl, request) {
|
|
2032
2197
|
if (!projectId)
|
|
2033
2198
|
return null;
|
|
@@ -2096,6 +2261,59 @@ function buildAddPayload(template, title, apiPath, catId, projectId) {
|
|
|
2096
2261
|
tag: template.tag || [],
|
|
2097
2262
|
};
|
|
2098
2263
|
}
|
|
2264
|
+
function buildUpdatePayload(docId, title, markdown, html) {
|
|
2265
|
+
const payload = { id: docId, markdown, desc: html };
|
|
2266
|
+
if (title) {
|
|
2267
|
+
payload.title = title;
|
|
2268
|
+
}
|
|
2269
|
+
return payload;
|
|
2270
|
+
}
|
|
2271
|
+
function pickLargestMermaid(metrics) {
|
|
2272
|
+
return metrics
|
|
2273
|
+
.filter((item) => item.renderer === "mermaid")
|
|
2274
|
+
.sort((a, b) => b.renderedBytes - a.renderedBytes)[0];
|
|
2275
|
+
}
|
|
2276
|
+
function buildDocsSyncPreviewLine(item) {
|
|
2277
|
+
const parts = [
|
|
2278
|
+
`file=${item.fileName}`,
|
|
2279
|
+
`action=${item.action}`,
|
|
2280
|
+
`markdown=${formatBytes(item.markdownBytes)}`,
|
|
2281
|
+
`html=${formatBytes(item.htmlBytes)}`,
|
|
2282
|
+
`payload=${formatBytes(item.payloadBytes)}`,
|
|
2283
|
+
`path=${item.apiPath}`,
|
|
2284
|
+
];
|
|
2285
|
+
if (item.docId) {
|
|
2286
|
+
parts.push(`doc_id=${item.docId}`);
|
|
2287
|
+
}
|
|
2288
|
+
if (item.largestMermaid) {
|
|
2289
|
+
parts.push(`largest_mermaid=#${item.largestMermaid.index}`, `largest_mermaid_svg=${formatBytes(item.largestMermaid.renderedBytes)}`);
|
|
2290
|
+
}
|
|
2291
|
+
return `preview ${parts.join(" ")}`;
|
|
2292
|
+
}
|
|
2293
|
+
function buildDocsSyncPayloadTooLargeMessage(fileName, preview, error) {
|
|
2294
|
+
const lines = [
|
|
2295
|
+
`413 Payload Too Large while syncing ${fileName}`,
|
|
2296
|
+
`- request payload: ${formatBytes(preview.payloadBytes)}`,
|
|
2297
|
+
`- markdown size: ${formatBytes(preview.markdownBytes)}`,
|
|
2298
|
+
`- rendered html size: ${formatBytes(preview.htmlBytes)}`,
|
|
2299
|
+
];
|
|
2300
|
+
const limitBytes = parsePayloadLimit(error.body || error.message);
|
|
2301
|
+
if (limitBytes) {
|
|
2302
|
+
lines.push(`- server limit: ${formatBytes(limitBytes)}`);
|
|
2303
|
+
}
|
|
2304
|
+
else {
|
|
2305
|
+
lines.push("- server limit: unknown (response did not expose an exact value)");
|
|
2306
|
+
}
|
|
2307
|
+
if (preview.largestMermaid) {
|
|
2308
|
+
lines.push(`- largest Mermaid block: #${preview.largestMermaid.index} -> ${formatBytes(preview.largestMermaid.renderedBytes)}`);
|
|
2309
|
+
}
|
|
2310
|
+
else {
|
|
2311
|
+
lines.push("- largest Mermaid block: none");
|
|
2312
|
+
}
|
|
2313
|
+
lines.push("- suggestion: run `yapi docs-sync --dry-run ...` to preview all files before upload");
|
|
2314
|
+
lines.push("- suggestion: split oversized Mermaid diagrams or move them into separate docs");
|
|
2315
|
+
return lines.join("\n");
|
|
2316
|
+
}
|
|
2099
2317
|
async function addInterface(title, apiPath, mapping, request) {
|
|
2100
2318
|
const projectId = Number(mapping.project_id || 0);
|
|
2101
2319
|
const catId = Number(mapping.catid || 0);
|
|
@@ -2122,10 +2340,7 @@ async function addInterface(title, apiPath, mapping, request) {
|
|
|
2122
2340
|
return Number(newId);
|
|
2123
2341
|
}
|
|
2124
2342
|
async function updateInterface(docId, title, markdown, html, request) {
|
|
2125
|
-
const payload =
|
|
2126
|
-
if (title) {
|
|
2127
|
-
payload.title = title;
|
|
2128
|
-
}
|
|
2343
|
+
const payload = buildUpdatePayload(docId, title, markdown, html);
|
|
2129
2344
|
const resp = await request("/api/interface/up", "POST", {}, payload);
|
|
2130
2345
|
if (resp?.errcode !== 0) {
|
|
2131
2346
|
throw new Error(`interface up failed: ${resp?.errmsg || "unknown error"}`);
|
|
@@ -2147,14 +2362,19 @@ async function syncDocsDir(dirPath, mapping, options, request) {
|
|
|
2147
2362
|
mapping.catid = Number(envCatId);
|
|
2148
2363
|
if (!mapping.template_id && envTemplateId)
|
|
2149
2364
|
mapping.template_id = Number(envTemplateId);
|
|
2150
|
-
|
|
2365
|
+
const hasTarget = Boolean(mapping.project_id && mapping.catid);
|
|
2366
|
+
if (!hasTarget && !options.dryRun) {
|
|
2151
2367
|
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。");
|
|
2152
2368
|
}
|
|
2153
|
-
const { byPath, byTitle, byId } =
|
|
2369
|
+
const { byPath, byTitle, byId } = hasTarget
|
|
2370
|
+
? await listExistingInterfaces(Number(mapping.catid), request)
|
|
2371
|
+
: { byPath: {}, byTitle: {}, byId: {} };
|
|
2154
2372
|
let updated = 0;
|
|
2155
2373
|
let created = 0;
|
|
2156
2374
|
let skipped = 0;
|
|
2375
|
+
let previewOnly = 0;
|
|
2157
2376
|
const fileInfos = {};
|
|
2377
|
+
const previews = [];
|
|
2158
2378
|
const files = resolveSourceFiles(dirPath, mapping);
|
|
2159
2379
|
for (const mdPath of files) {
|
|
2160
2380
|
const stem = path_1.default.parse(mdPath).name;
|
|
@@ -2168,43 +2388,34 @@ async function syncDocsDir(dirPath, mapping, options, request) {
|
|
|
2168
2388
|
if (docId)
|
|
2169
2389
|
mapping.files[relName] = docId;
|
|
2170
2390
|
}
|
|
2171
|
-
|
|
2391
|
+
let action = docId ? "update" : hasTarget ? "create" : "preview-only";
|
|
2392
|
+
if (!docId && hasTarget) {
|
|
2172
2393
|
created += 1;
|
|
2173
2394
|
if (!options.dryRun) {
|
|
2174
2395
|
docId = await addInterface(desiredTitle, apiPath, mapping, request);
|
|
2175
2396
|
mapping.files[relName] = docId;
|
|
2176
2397
|
}
|
|
2177
2398
|
}
|
|
2399
|
+
if (!docId && !hasTarget) {
|
|
2400
|
+
previewOnly += 1;
|
|
2401
|
+
}
|
|
2178
2402
|
if (docId) {
|
|
2179
2403
|
const resolvedPath = byId[String(docId)]?.path || apiPath;
|
|
2180
2404
|
fileInfos[relName] = { docId: Number(docId), apiPath: resolvedPath };
|
|
2181
2405
|
}
|
|
2182
|
-
const contentHash = buildDocsSyncHash(markdown, options);
|
|
2183
|
-
const previousHash = mapping.file_hashes[relName];
|
|
2184
|
-
const currentTitle = docId ? byId[String(docId)]?.title : "";
|
|
2185
|
-
const titleToUpdate = !docId
|
|
2186
|
-
? undefined
|
|
2187
|
-
: !currentTitle || currentTitle !== desiredTitle
|
|
2188
|
-
? desiredTitle
|
|
2189
|
-
: undefined;
|
|
2190
|
-
const shouldSyncTitle = Boolean(titleToUpdate);
|
|
2191
|
-
if (!options.force &&
|
|
2192
|
-
docId &&
|
|
2193
|
-
previousHash &&
|
|
2194
|
-
previousHash === contentHash &&
|
|
2195
|
-
!shouldSyncTitle) {
|
|
2196
|
-
skipped += 1;
|
|
2197
|
-
continue;
|
|
2198
|
-
}
|
|
2199
2406
|
const logPrefix = `[docs-sync:${relName}]`;
|
|
2200
2407
|
let mermaidFailed = false;
|
|
2201
2408
|
let diagramFailed = false;
|
|
2409
|
+
const diagramMetrics = [];
|
|
2202
2410
|
const html = (0, markdown_1.renderMarkdownToHtml)(markdown, {
|
|
2203
2411
|
noMermaid: options.noMermaid,
|
|
2204
2412
|
logMermaid: true,
|
|
2205
2413
|
mermaidLook: options.mermaidLook,
|
|
2206
2414
|
mermaidHandDrawnSeed: options.mermaidHandDrawnSeed,
|
|
2207
2415
|
logger: (message) => console.log(`${logPrefix} ${message}`),
|
|
2416
|
+
onDiagramRendered: (metric) => {
|
|
2417
|
+
diagramMetrics.push(metric);
|
|
2418
|
+
},
|
|
2208
2419
|
onMermaidError: () => {
|
|
2209
2420
|
mermaidFailed = true;
|
|
2210
2421
|
},
|
|
@@ -2212,15 +2423,56 @@ async function syncDocsDir(dirPath, mapping, options, request) {
|
|
|
2212
2423
|
diagramFailed = true;
|
|
2213
2424
|
},
|
|
2214
2425
|
});
|
|
2215
|
-
|
|
2216
|
-
|
|
2426
|
+
const contentHash = buildDocsSyncHash(markdown, options);
|
|
2427
|
+
const previousHash = mapping.file_hashes[relName];
|
|
2428
|
+
const currentTitle = docId ? byId[String(docId)]?.title : "";
|
|
2429
|
+
const titleToUpdate = !docId
|
|
2430
|
+
? undefined
|
|
2431
|
+
: !currentTitle || currentTitle !== desiredTitle
|
|
2432
|
+
? desiredTitle
|
|
2433
|
+
: undefined;
|
|
2434
|
+
const shouldSyncTitle = Boolean(titleToUpdate);
|
|
2435
|
+
if (!options.force &&
|
|
2436
|
+
docId &&
|
|
2437
|
+
previousHash &&
|
|
2438
|
+
previousHash === contentHash &&
|
|
2439
|
+
!shouldSyncTitle) {
|
|
2440
|
+
action = "skip";
|
|
2441
|
+
skipped += 1;
|
|
2442
|
+
}
|
|
2443
|
+
const payloadObject = docId && action !== "create"
|
|
2444
|
+
? buildUpdatePayload(docId, titleToUpdate, markdown, html)
|
|
2445
|
+
: buildAddPayload({}, desiredTitle, apiPath, Number(mapping.catid || 0), Number(mapping.project_id || 0));
|
|
2446
|
+
const preview = {
|
|
2447
|
+
fileName: relName,
|
|
2448
|
+
action,
|
|
2449
|
+
markdownBytes: Buffer.byteLength(markdown, "utf8"),
|
|
2450
|
+
htmlBytes: Buffer.byteLength(html, "utf8"),
|
|
2451
|
+
payloadBytes: Buffer.byteLength(JSON.stringify(payloadObject), "utf8"),
|
|
2452
|
+
apiPath: docId ? fileInfos[relName]?.apiPath || apiPath : apiPath,
|
|
2453
|
+
docId: docId ? Number(docId) : undefined,
|
|
2454
|
+
largestMermaid: pickLargestMermaid(diagramMetrics),
|
|
2455
|
+
};
|
|
2456
|
+
previews.push(preview);
|
|
2457
|
+
if (!options.dryRun && docId && action !== "skip") {
|
|
2458
|
+
try {
|
|
2459
|
+
await updateInterface(docId, titleToUpdate, markdown, html, request);
|
|
2460
|
+
}
|
|
2461
|
+
catch (error) {
|
|
2462
|
+
if (error instanceof HttpStatusError && error.status === 413) {
|
|
2463
|
+
throw new Error(buildDocsSyncPayloadTooLargeMessage(relName, preview, error));
|
|
2464
|
+
}
|
|
2465
|
+
throw error;
|
|
2466
|
+
}
|
|
2217
2467
|
}
|
|
2218
2468
|
if (docId && !mermaidFailed && !diagramFailed) {
|
|
2219
2469
|
mapping.file_hashes[relName] = contentHash;
|
|
2220
2470
|
}
|
|
2221
|
-
|
|
2471
|
+
if (action !== "skip") {
|
|
2472
|
+
updated += 1;
|
|
2473
|
+
}
|
|
2222
2474
|
}
|
|
2223
|
-
return { updated, created, skipped, files: fileInfos };
|
|
2475
|
+
return { updated, created, skipped, previewOnly, files: fileInfos, previews };
|
|
2224
2476
|
}
|
|
2225
2477
|
function buildEnvUrls(projectInfo, apiPath) {
|
|
2226
2478
|
const urls = {};
|
|
@@ -2395,7 +2647,7 @@ async function runDocsSyncBindings(rawArgs) {
|
|
|
2395
2647
|
file_hashes: existing.file_hashes ? { ...existing.file_hashes } : {},
|
|
2396
2648
|
};
|
|
2397
2649
|
if (options.dir) {
|
|
2398
|
-
next.dir =
|
|
2650
|
+
next.dir = normalizeBindingDirForContext(homeDir, rootDir, process.cwd(), options.dir);
|
|
2399
2651
|
}
|
|
2400
2652
|
if (options.projectId !== undefined && Number.isFinite(options.projectId)) {
|
|
2401
2653
|
next.project_id = Number(options.projectId);
|
|
@@ -2418,7 +2670,17 @@ async function runDocsSyncBindings(rawArgs) {
|
|
|
2418
2670
|
}
|
|
2419
2671
|
config.bindings[options.name] = next;
|
|
2420
2672
|
saveDocsSyncConfig(homeDir, config);
|
|
2673
|
+
const resolvedDir = resolveBindingDirForContext(homeDir, rootDir, process.cwd(), next.dir);
|
|
2421
2674
|
console.log(`${action === "add" ? "binding added" : "binding updated"}: ${options.name}`);
|
|
2675
|
+
console.log(`stored_dir=${next.dir}`);
|
|
2676
|
+
console.log(`resolved_dir=${resolvedDir}`);
|
|
2677
|
+
const gitRoot = findGitRoot(process.cwd());
|
|
2678
|
+
if (gitRoot) {
|
|
2679
|
+
console.log(`git_root=${gitRoot}`);
|
|
2680
|
+
}
|
|
2681
|
+
else if (isGlobalDocsSyncHome(homeDir)) {
|
|
2682
|
+
console.warn("warning: no git root detected, relative --dir was resolved from current working directory");
|
|
2683
|
+
}
|
|
2422
2684
|
return 0;
|
|
2423
2685
|
}
|
|
2424
2686
|
async function runDocsSync(rawArgs) {
|
|
@@ -2626,7 +2888,7 @@ async function runDocsSync(rawArgs) {
|
|
|
2626
2888
|
result = await sendOnce();
|
|
2627
2889
|
}
|
|
2628
2890
|
if (!result.response.ok) {
|
|
2629
|
-
throw new
|
|
2891
|
+
throw new HttpStatusError(endpoint, result.response.status, result.response.statusText, result.text);
|
|
2630
2892
|
}
|
|
2631
2893
|
if (!result.json) {
|
|
2632
2894
|
throw new Error(`invalid JSON response from ${endpoint}`);
|
|
@@ -2642,7 +2904,7 @@ async function runDocsSync(rawArgs) {
|
|
|
2642
2904
|
if (!binding) {
|
|
2643
2905
|
throw new Error(`binding not found: ${name}`);
|
|
2644
2906
|
}
|
|
2645
|
-
const dirPath =
|
|
2907
|
+
const dirPath = resolveBindingDirForContext(docsSyncHome, rootDir, process.cwd(), binding.dir);
|
|
2646
2908
|
const existing = dirToBindings.get(dirPath) || [];
|
|
2647
2909
|
existing.push(name);
|
|
2648
2910
|
dirToBindings.set(dirPath, existing);
|
|
@@ -2667,12 +2929,16 @@ async function runDocsSync(rawArgs) {
|
|
|
2667
2929
|
if (!binding) {
|
|
2668
2930
|
throw new Error(`binding not found: ${name}`);
|
|
2669
2931
|
}
|
|
2670
|
-
const dirPath =
|
|
2932
|
+
const dirPath = resolveBindingDirForContext(docsSyncHome, rootDir, process.cwd(), binding.dir);
|
|
2671
2933
|
if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
|
|
2672
2934
|
throw new Error(`dir not found for binding ${name}: ${dirPath}`);
|
|
2673
2935
|
}
|
|
2674
2936
|
const result = await syncDocsDir(dirPath, binding, options, request);
|
|
2675
|
-
|
|
2937
|
+
if (options.dryRun) {
|
|
2938
|
+
console.log(`dry-run preview binding=${name}`);
|
|
2939
|
+
result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
|
|
2940
|
+
}
|
|
2941
|
+
console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} binding=${name} dir=${dirPath}`);
|
|
2676
2942
|
bindingResults[name] = { binding, files: result.files };
|
|
2677
2943
|
}
|
|
2678
2944
|
if (!options.dryRun) {
|
|
@@ -2688,10 +2954,14 @@ async function runDocsSync(rawArgs) {
|
|
|
2688
2954
|
}
|
|
2689
2955
|
const { mapping, mappingPath } = loadMapping(dirPath);
|
|
2690
2956
|
const result = await syncDocsDir(dirPath, mapping, options, request);
|
|
2957
|
+
if (options.dryRun) {
|
|
2958
|
+
console.log(`dry-run preview dir=${dirPath}`);
|
|
2959
|
+
result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
|
|
2960
|
+
}
|
|
2691
2961
|
if (!options.dryRun) {
|
|
2692
2962
|
saveMapping(mapping, mappingPath);
|
|
2693
2963
|
}
|
|
2694
|
-
console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} dir=${dirPath}`);
|
|
2964
|
+
console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} dir=${dirPath}`);
|
|
2695
2965
|
}
|
|
2696
2966
|
}
|
|
2697
2967
|
return 0;
|
|
@@ -2704,12 +2974,21 @@ async function runDocsSync(rawArgs) {
|
|
|
2704
2974
|
async function main() {
|
|
2705
2975
|
const rawArgs = process.argv.slice(2);
|
|
2706
2976
|
const parsedForUpdate = parseArgs(rawArgs);
|
|
2977
|
+
const subcommand = rawArgs[0] || "";
|
|
2707
2978
|
const skipUpdateCheck = parsedForUpdate.version ||
|
|
2708
2979
|
parsedForUpdate.help ||
|
|
2709
2980
|
parsedForUpdate.noUpdate ||
|
|
2710
2981
|
rawArgs.includes("-h") ||
|
|
2711
2982
|
rawArgs.includes("--help");
|
|
2712
2983
|
await checkForUpdates({ noUpdate: parsedForUpdate.noUpdate, skip: skipUpdateCheck });
|
|
2984
|
+
const skipSkillWarning = skipUpdateCheck ||
|
|
2985
|
+
subcommand === "install-skill" ||
|
|
2986
|
+
subcommand === "self-update" ||
|
|
2987
|
+
process.env.YAPI_NO_SKILL_UPDATE_CHECK === "1";
|
|
2988
|
+
warnIfInstalledSkillsOutdated({ skip: skipSkillWarning });
|
|
2989
|
+
if (subcommand === "self-update") {
|
|
2990
|
+
return runSelfUpdate(rawArgs.slice(1));
|
|
2991
|
+
}
|
|
2713
2992
|
if (rawArgs[0] === "install-skill") {
|
|
2714
2993
|
await (0, install_1.runInstallSkill)(rawArgs.slice(1));
|
|
2715
2994
|
return 0;
|
|
@@ -2862,7 +3141,7 @@ async function main() {
|
|
|
2862
3141
|
}
|
|
2863
3142
|
const queryItems = [];
|
|
2864
3143
|
for (const query of options.query || []) {
|
|
2865
|
-
queryItems.push(
|
|
3144
|
+
queryItems.push(...parseQueryArg(query));
|
|
2866
3145
|
}
|
|
2867
3146
|
const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
|
|
2868
3147
|
let dataRaw = null;
|