@leeguoo/yapi-mcp 0.3.0 → 0.3.2
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 +48 -4
- package/dist/cli.js +24 -9
- package/dist/cli.js.map +1 -1
- package/dist/skill/install.d.ts +1 -0
- package/dist/skill/install.js +728 -0
- package/dist/skill/install.js.map +1 -0
- package/dist/yapi-cli.d.ts +2 -0
- package/dist/yapi-cli.js +1532 -0
- package/dist/yapi-cli.js.map +1 -0
- package/package.json +4 -3
package/dist/yapi-cli.js
ADDED
|
@@ -0,0 +1,1532 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const readline_1 = __importDefault(require("readline"));
|
|
12
|
+
const install_1 = require("./skill/install");
|
|
13
|
+
const auth_1 = require("./services/yapi/auth");
|
|
14
|
+
function parseKeyValue(raw) {
|
|
15
|
+
if (!raw || !raw.includes("="))
|
|
16
|
+
throw new Error("expected key=value");
|
|
17
|
+
const idx = raw.indexOf("=");
|
|
18
|
+
return [raw.slice(0, idx), raw.slice(idx + 1)];
|
|
19
|
+
}
|
|
20
|
+
function parseHeader(raw) {
|
|
21
|
+
if (!raw || !raw.includes(":"))
|
|
22
|
+
throw new Error("expected Header:Value");
|
|
23
|
+
const idx = raw.indexOf(":");
|
|
24
|
+
return [raw.slice(0, idx).trim(), raw.slice(idx + 1).trim()];
|
|
25
|
+
}
|
|
26
|
+
function parseJsonMaybe(text) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function getPayloadMessage(payload) {
|
|
35
|
+
if (!payload || typeof payload !== "object")
|
|
36
|
+
return "";
|
|
37
|
+
const record = payload;
|
|
38
|
+
const message = record.errmsg ?? record.message ?? record.msg;
|
|
39
|
+
return typeof message === "string" ? message : "";
|
|
40
|
+
}
|
|
41
|
+
function getPayloadErrcode(payload) {
|
|
42
|
+
if (!payload || typeof payload !== "object")
|
|
43
|
+
return null;
|
|
44
|
+
const record = payload;
|
|
45
|
+
const code = Number(record.errcode ?? record.code ?? record.errCode);
|
|
46
|
+
return Number.isFinite(code) ? code : null;
|
|
47
|
+
}
|
|
48
|
+
function looksLikeAuthError(status, payload) {
|
|
49
|
+
if (status === 401 || status === 403)
|
|
50
|
+
return true;
|
|
51
|
+
const code = getPayloadErrcode(payload);
|
|
52
|
+
if (code !== null && [40011, 40012, 40013, 401, 403].includes(code))
|
|
53
|
+
return true;
|
|
54
|
+
const message = getPayloadMessage(payload);
|
|
55
|
+
if (!message)
|
|
56
|
+
return false;
|
|
57
|
+
return /登录|login|权限|unauthori|forbidden|not\s*login|no\s*permission/i.test(message);
|
|
58
|
+
}
|
|
59
|
+
function joinUrl(baseUrl, endpoint) {
|
|
60
|
+
if (!baseUrl)
|
|
61
|
+
return endpoint;
|
|
62
|
+
if (baseUrl.endsWith("/") && endpoint.startsWith("/"))
|
|
63
|
+
return baseUrl.slice(0, -1) + endpoint;
|
|
64
|
+
if (!baseUrl.endsWith("/") && !endpoint.startsWith("/"))
|
|
65
|
+
return baseUrl + "/" + endpoint;
|
|
66
|
+
return baseUrl + endpoint;
|
|
67
|
+
}
|
|
68
|
+
function globalConfigPath() {
|
|
69
|
+
const yapiHome = process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi");
|
|
70
|
+
return path_1.default.join(yapiHome, "config.toml");
|
|
71
|
+
}
|
|
72
|
+
function parseSimpleToml(text) {
|
|
73
|
+
const data = {};
|
|
74
|
+
const lines = String(text || "").split(/\r?\n/);
|
|
75
|
+
for (const rawLine of lines) {
|
|
76
|
+
const line = rawLine.split("#", 1)[0].split(";", 1)[0].trim();
|
|
77
|
+
if (!line || line.startsWith("["))
|
|
78
|
+
continue;
|
|
79
|
+
const idx = line.indexOf("=");
|
|
80
|
+
if (idx === -1)
|
|
81
|
+
continue;
|
|
82
|
+
const key = line.slice(0, idx).trim();
|
|
83
|
+
let value = line.slice(idx + 1).trim();
|
|
84
|
+
if (!key)
|
|
85
|
+
continue;
|
|
86
|
+
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
87
|
+
value = value.slice(1, -1);
|
|
88
|
+
}
|
|
89
|
+
data[key] = value;
|
|
90
|
+
}
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
function resolveToken(tokenValue, projectId) {
|
|
94
|
+
if (!tokenValue)
|
|
95
|
+
return "";
|
|
96
|
+
if (tokenValue.includes(",") || tokenValue.includes(":")) {
|
|
97
|
+
let defaultToken = "";
|
|
98
|
+
const mapping = {};
|
|
99
|
+
tokenValue.split(",").forEach((rawPair) => {
|
|
100
|
+
const pair = rawPair.trim();
|
|
101
|
+
if (!pair)
|
|
102
|
+
return;
|
|
103
|
+
const idx = pair.indexOf(":");
|
|
104
|
+
if (idx === -1) {
|
|
105
|
+
defaultToken = pair;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const pid = pair.slice(0, idx).trim();
|
|
109
|
+
const token = pair.slice(idx + 1).trim();
|
|
110
|
+
if (pid && token)
|
|
111
|
+
mapping[pid] = token;
|
|
112
|
+
});
|
|
113
|
+
if (projectId && mapping[projectId])
|
|
114
|
+
return mapping[projectId];
|
|
115
|
+
if (defaultToken)
|
|
116
|
+
return defaultToken;
|
|
117
|
+
const keys = Object.keys(mapping);
|
|
118
|
+
if (keys.length)
|
|
119
|
+
return mapping[keys[0]];
|
|
120
|
+
}
|
|
121
|
+
return tokenValue;
|
|
122
|
+
}
|
|
123
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
126
|
+
try {
|
|
127
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function buildUrl(baseUrl, endpoint, queryItems, token, tokenParam) {
|
|
134
|
+
const url = baseUrl ? joinUrl(baseUrl, endpoint) : endpoint;
|
|
135
|
+
const parsed = new URL(url);
|
|
136
|
+
for (const [key, value] of queryItems) {
|
|
137
|
+
if (key)
|
|
138
|
+
parsed.searchParams.append(key, value ?? "");
|
|
139
|
+
}
|
|
140
|
+
if (token && !parsed.searchParams.has(tokenParam)) {
|
|
141
|
+
parsed.searchParams.append(tokenParam, token);
|
|
142
|
+
}
|
|
143
|
+
return parsed.toString();
|
|
144
|
+
}
|
|
145
|
+
function parseArgs(argv) {
|
|
146
|
+
const options = {
|
|
147
|
+
query: [],
|
|
148
|
+
header: [],
|
|
149
|
+
method: "GET",
|
|
150
|
+
tokenParam: "token",
|
|
151
|
+
timeout: 30000,
|
|
152
|
+
};
|
|
153
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
154
|
+
const arg = argv[i];
|
|
155
|
+
if (!arg)
|
|
156
|
+
continue;
|
|
157
|
+
if (arg === "-h" || arg === "--help") {
|
|
158
|
+
options.help = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (arg === "-V" || arg === "--version") {
|
|
162
|
+
options.version = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (arg === "--config") {
|
|
166
|
+
options.config = argv[++i];
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (arg.startsWith("--config=")) {
|
|
170
|
+
options.config = arg.slice(9);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (arg === "--base-url") {
|
|
174
|
+
options.baseUrl = argv[++i];
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (arg.startsWith("--base-url=")) {
|
|
178
|
+
options.baseUrl = arg.slice(11);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (arg === "--token") {
|
|
182
|
+
options.token = argv[++i];
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (arg.startsWith("--token=")) {
|
|
186
|
+
options.token = arg.slice(8);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (arg === "--project-id") {
|
|
190
|
+
options.projectId = argv[++i];
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (arg.startsWith("--project-id=")) {
|
|
194
|
+
options.projectId = arg.slice(13);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (arg === "--auth-mode") {
|
|
198
|
+
options.authMode = argv[++i];
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (arg.startsWith("--auth-mode=")) {
|
|
202
|
+
options.authMode = arg.slice(12);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (arg === "--email") {
|
|
206
|
+
options.email = argv[++i];
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (arg.startsWith("--email=")) {
|
|
210
|
+
options.email = arg.slice(8);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (arg === "--password") {
|
|
214
|
+
options.password = argv[++i];
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (arg.startsWith("--password=")) {
|
|
218
|
+
options.password = arg.slice(11);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (arg === "--cookie") {
|
|
222
|
+
options.cookie = argv[++i];
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (arg.startsWith("--cookie=")) {
|
|
226
|
+
options.cookie = arg.slice(9);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (arg === "--token-param") {
|
|
230
|
+
options.tokenParam = argv[++i];
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (arg.startsWith("--token-param=")) {
|
|
234
|
+
options.tokenParam = arg.slice(14);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (arg === "--method") {
|
|
238
|
+
options.method = argv[++i];
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (arg.startsWith("--method=")) {
|
|
242
|
+
options.method = arg.slice(9);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (arg === "--path") {
|
|
246
|
+
options.path = argv[++i];
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (arg.startsWith("--path=")) {
|
|
250
|
+
options.path = arg.slice(7);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (arg === "--url") {
|
|
254
|
+
options.url = argv[++i];
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (arg.startsWith("--url=")) {
|
|
258
|
+
options.url = arg.slice(6);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (arg === "--query") {
|
|
262
|
+
options.query?.push(argv[++i]);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (arg.startsWith("--query=")) {
|
|
266
|
+
options.query?.push(arg.slice(8));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (arg === "--header") {
|
|
270
|
+
options.header?.push(argv[++i]);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (arg.startsWith("--header=")) {
|
|
274
|
+
options.header?.push(arg.slice(9));
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (arg === "--data") {
|
|
278
|
+
options.data = argv[++i];
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (arg.startsWith("--data=")) {
|
|
282
|
+
options.data = arg.slice(7);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (arg === "--data-file") {
|
|
286
|
+
options.dataFile = argv[++i];
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (arg.startsWith("--data-file=")) {
|
|
290
|
+
options.dataFile = arg.slice(12);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (arg === "--timeout") {
|
|
294
|
+
options.timeout = Number(argv[++i]);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (arg.startsWith("--timeout=")) {
|
|
298
|
+
options.timeout = Number(arg.slice(10));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (arg === "--no-pretty") {
|
|
302
|
+
options.noPretty = true;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return options;
|
|
307
|
+
}
|
|
308
|
+
function parseDocsSyncArgs(argv) {
|
|
309
|
+
const options = {
|
|
310
|
+
dirs: [],
|
|
311
|
+
bindings: [],
|
|
312
|
+
tokenParam: "token",
|
|
313
|
+
timeout: 30000,
|
|
314
|
+
};
|
|
315
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
316
|
+
const arg = argv[i];
|
|
317
|
+
if (!arg)
|
|
318
|
+
continue;
|
|
319
|
+
if (arg === "-h" || arg === "--help") {
|
|
320
|
+
options.help = true;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (arg === "--config") {
|
|
324
|
+
options.config = argv[++i];
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (arg.startsWith("--config=")) {
|
|
328
|
+
options.config = arg.slice(9);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (arg === "--base-url") {
|
|
332
|
+
options.baseUrl = argv[++i];
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (arg.startsWith("--base-url=")) {
|
|
336
|
+
options.baseUrl = arg.slice(11);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (arg === "--token") {
|
|
340
|
+
options.token = argv[++i];
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (arg.startsWith("--token=")) {
|
|
344
|
+
options.token = arg.slice(8);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (arg === "--project-id") {
|
|
348
|
+
options.projectId = argv[++i];
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (arg.startsWith("--project-id=")) {
|
|
352
|
+
options.projectId = arg.slice(13);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (arg === "--auth-mode") {
|
|
356
|
+
options.authMode = argv[++i];
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (arg.startsWith("--auth-mode=")) {
|
|
360
|
+
options.authMode = arg.slice(12);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (arg === "--email") {
|
|
364
|
+
options.email = argv[++i];
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (arg.startsWith("--email=")) {
|
|
368
|
+
options.email = arg.slice(8);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (arg === "--password") {
|
|
372
|
+
options.password = argv[++i];
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (arg.startsWith("--password=")) {
|
|
376
|
+
options.password = arg.slice(11);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (arg === "--cookie") {
|
|
380
|
+
options.cookie = argv[++i];
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (arg.startsWith("--cookie=")) {
|
|
384
|
+
options.cookie = arg.slice(9);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (arg === "--token-param") {
|
|
388
|
+
options.tokenParam = argv[++i];
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (arg.startsWith("--token-param=")) {
|
|
392
|
+
options.tokenParam = arg.slice(14);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (arg === "--timeout") {
|
|
396
|
+
options.timeout = Number(argv[++i]);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (arg.startsWith("--timeout=")) {
|
|
400
|
+
options.timeout = Number(arg.slice(10));
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (arg === "--dir") {
|
|
404
|
+
options.dirs.push(argv[++i]);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (arg.startsWith("--dir=")) {
|
|
408
|
+
options.dirs.push(arg.slice(6));
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (arg === "--binding") {
|
|
412
|
+
options.bindings.push(argv[++i]);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (arg.startsWith("--binding=")) {
|
|
416
|
+
options.bindings.push(arg.slice(10));
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (arg === "--dry-run") {
|
|
420
|
+
options.dryRun = true;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (arg === "--no-mermaid") {
|
|
424
|
+
options.noMermaid = true;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (arg.startsWith("-"))
|
|
428
|
+
continue;
|
|
429
|
+
options.dirs.push(arg);
|
|
430
|
+
}
|
|
431
|
+
return options;
|
|
432
|
+
}
|
|
433
|
+
function parseDocsSyncBindArgs(argv) {
|
|
434
|
+
const options = {
|
|
435
|
+
sourceFiles: [],
|
|
436
|
+
};
|
|
437
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
438
|
+
const arg = argv[i];
|
|
439
|
+
if (!arg)
|
|
440
|
+
continue;
|
|
441
|
+
if (arg === "-h" || arg === "--help") {
|
|
442
|
+
options.help = true;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (arg === "--name" || arg === "--binding") {
|
|
446
|
+
options.name = argv[++i];
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (arg.startsWith("--name=")) {
|
|
450
|
+
options.name = arg.slice(7);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (arg.startsWith("--binding=")) {
|
|
454
|
+
options.name = arg.slice(10);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (arg === "--dir") {
|
|
458
|
+
options.dir = argv[++i];
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (arg.startsWith("--dir=")) {
|
|
462
|
+
options.dir = arg.slice(6);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (arg === "--project-id") {
|
|
466
|
+
options.projectId = Number(argv[++i]);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (arg.startsWith("--project-id=")) {
|
|
470
|
+
options.projectId = Number(arg.slice(13));
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (arg === "--catid" || arg === "--cat-id") {
|
|
474
|
+
options.catId = Number(argv[++i]);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (arg.startsWith("--catid=")) {
|
|
478
|
+
options.catId = Number(arg.slice(8));
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (arg.startsWith("--cat-id=")) {
|
|
482
|
+
options.catId = Number(arg.slice(9));
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (arg === "--template-id") {
|
|
486
|
+
options.templateId = Number(argv[++i]);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (arg.startsWith("--template-id=")) {
|
|
490
|
+
options.templateId = Number(arg.slice(14));
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (arg === "--source-file") {
|
|
494
|
+
options.sourceFiles?.push(argv[++i]);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (arg.startsWith("--source-file=")) {
|
|
498
|
+
options.sourceFiles?.push(arg.slice(14));
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (arg === "--clear-source-files") {
|
|
502
|
+
options.clearSourceFiles = true;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return options;
|
|
507
|
+
}
|
|
508
|
+
function usage() {
|
|
509
|
+
return [
|
|
510
|
+
"Usage:",
|
|
511
|
+
" yapi --path /api/interface/get --query id=123",
|
|
512
|
+
" yapi docs-sync [options] [dir...]",
|
|
513
|
+
" yapi docs-sync bind <action> [options]",
|
|
514
|
+
" yapi login [options]",
|
|
515
|
+
" yapi install-skill [options]",
|
|
516
|
+
"Options:",
|
|
517
|
+
" --config <path> config file path (default: ~/.yapi/config.toml)",
|
|
518
|
+
" --base-url <url> YApi base URL",
|
|
519
|
+
" --token <token> project token (supports projectId:token)",
|
|
520
|
+
" --project-id <id> select token for project",
|
|
521
|
+
" --auth-mode <mode> token or global",
|
|
522
|
+
" --email <email> login email for global mode",
|
|
523
|
+
" --password <pwd> login password for global mode",
|
|
524
|
+
" --path <path> API path (e.g., /api/interface/get)",
|
|
525
|
+
" --url <url> full URL (overrides base-url/path)",
|
|
526
|
+
" --query key=value query param (repeatable)",
|
|
527
|
+
" --header Header:Value request header (repeatable)",
|
|
528
|
+
" --method <method> HTTP method",
|
|
529
|
+
" --data <payload> request body (JSON or text)",
|
|
530
|
+
" --data-file <file> request body file",
|
|
531
|
+
" --timeout <ms> request timeout in ms",
|
|
532
|
+
" --no-pretty print raw response",
|
|
533
|
+
"Docs-sync options:",
|
|
534
|
+
" --dir <path> docs directory (repeatable; default: docs/release-notes)",
|
|
535
|
+
" --binding <name> use binding from .yapi/docs-sync.json (repeatable)",
|
|
536
|
+
" --dry-run compute but do not update YApi or mapping files",
|
|
537
|
+
" --no-mermaid do not render mermaid code blocks",
|
|
538
|
+
"Docs-sync bind actions:",
|
|
539
|
+
" list, get, add, update, remove",
|
|
540
|
+
" -V, --version print version",
|
|
541
|
+
" -h, --help show help",
|
|
542
|
+
].join("\n");
|
|
543
|
+
}
|
|
544
|
+
function docsSyncUsage() {
|
|
545
|
+
return [
|
|
546
|
+
"Usage:",
|
|
547
|
+
" yapi docs-sync [options] [dir...]",
|
|
548
|
+
" yapi docs-sync bind <action> [options]",
|
|
549
|
+
"Options:",
|
|
550
|
+
" --config <path> config file path (default: ~/.yapi/config.toml)",
|
|
551
|
+
" --base-url <url> YApi base URL",
|
|
552
|
+
" --token <token> project token (supports projectId:token)",
|
|
553
|
+
" --project-id <id> select token for project",
|
|
554
|
+
" --auth-mode <mode> token or global",
|
|
555
|
+
" --email <email> login email for global mode",
|
|
556
|
+
" --password <pwd> login password for global mode",
|
|
557
|
+
" --cookie <cookie> cookie for global mode",
|
|
558
|
+
" --token-param <name> token query param name (default: token)",
|
|
559
|
+
" --timeout <ms> request timeout in ms",
|
|
560
|
+
" --dir <path> docs directory (repeatable; default: docs/release-notes)",
|
|
561
|
+
" --binding <name> use binding from .yapi/docs-sync.json (repeatable)",
|
|
562
|
+
" --dry-run compute but do not update YApi or mapping files",
|
|
563
|
+
" --no-mermaid do not render mermaid code blocks",
|
|
564
|
+
" -h, --help show help",
|
|
565
|
+
].join("\n");
|
|
566
|
+
}
|
|
567
|
+
function docsSyncBindUsage() {
|
|
568
|
+
return [
|
|
569
|
+
"Usage:",
|
|
570
|
+
" yapi docs-sync bind list",
|
|
571
|
+
" yapi docs-sync bind get --name <binding>",
|
|
572
|
+
" yapi docs-sync bind add --name <binding> --dir <path> --project-id <id> --catid <id> [options]",
|
|
573
|
+
" yapi docs-sync bind update --name <binding> [options]",
|
|
574
|
+
" yapi docs-sync bind remove --name <binding>",
|
|
575
|
+
"Options:",
|
|
576
|
+
" --name <binding> binding name (alias: --binding)",
|
|
577
|
+
" --dir <path> docs directory path",
|
|
578
|
+
" --project-id <id> YApi project id",
|
|
579
|
+
" --catid <id> YApi category id",
|
|
580
|
+
" --template-id <id> template interface id",
|
|
581
|
+
" --source-file <path> sync specific file(s) (repeatable)",
|
|
582
|
+
" --clear-source-files clear source_files list",
|
|
583
|
+
" -h, --help show help",
|
|
584
|
+
].join("\n");
|
|
585
|
+
}
|
|
586
|
+
function loginUsage() {
|
|
587
|
+
return [
|
|
588
|
+
"Usage:",
|
|
589
|
+
" yapi login [options]",
|
|
590
|
+
"Options:",
|
|
591
|
+
" --config <path> config file path (default: ~/.yapi/config.toml)",
|
|
592
|
+
" --base-url <url> YApi base URL",
|
|
593
|
+
" --email <email> login email for global mode",
|
|
594
|
+
" --password <pwd> login password for global mode",
|
|
595
|
+
" --timeout <ms> request timeout in ms",
|
|
596
|
+
" -h, --help show help",
|
|
597
|
+
].join("\n");
|
|
598
|
+
}
|
|
599
|
+
function escapeTomlValue(value) {
|
|
600
|
+
return String(value || "").replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
601
|
+
}
|
|
602
|
+
function formatToml(config) {
|
|
603
|
+
const orderedKeys = ["base_url", "auth_mode", "email", "password", "token", "project_id"];
|
|
604
|
+
const lines = ["# YApi CLI config"];
|
|
605
|
+
for (const key of orderedKeys) {
|
|
606
|
+
const value = config[key] || "";
|
|
607
|
+
lines.push(`${key} = "${escapeTomlValue(value)}"`);
|
|
608
|
+
}
|
|
609
|
+
return `${lines.join("\n")}\n`;
|
|
610
|
+
}
|
|
611
|
+
function writeConfig(filePath, config) {
|
|
612
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
613
|
+
fs_1.default.writeFileSync(filePath, formatToml(config), "utf8");
|
|
614
|
+
}
|
|
615
|
+
function promptText(question) {
|
|
616
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
617
|
+
return new Promise((resolve) => {
|
|
618
|
+
rl.question(question, (answer) => {
|
|
619
|
+
rl.close();
|
|
620
|
+
resolve(answer);
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function promptHidden(question) {
|
|
625
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
626
|
+
const originalWrite = rl._writeToOutput;
|
|
627
|
+
rl.stdoutMuted = true;
|
|
628
|
+
rl._writeToOutput = function writeToOutput(value) {
|
|
629
|
+
if (rl.stdoutMuted)
|
|
630
|
+
return;
|
|
631
|
+
if (typeof originalWrite === "function") {
|
|
632
|
+
originalWrite.call(this, value);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
rl.output.write(value);
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
return new Promise((resolve) => {
|
|
639
|
+
rl.question(question, (answer) => {
|
|
640
|
+
rl.stdoutMuted = false;
|
|
641
|
+
rl.close();
|
|
642
|
+
resolve(answer);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
async function promptRequired(question, hidden) {
|
|
647
|
+
while (true) {
|
|
648
|
+
const answer = hidden ? await promptHidden(question) : await promptText(question);
|
|
649
|
+
const trimmed = String(answer || "").trim();
|
|
650
|
+
if (trimmed)
|
|
651
|
+
return trimmed;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function initConfigIfMissing(options) {
|
|
655
|
+
const hasBaseUrl = Boolean(options.baseUrl);
|
|
656
|
+
const hasEmail = Boolean(options.email);
|
|
657
|
+
const hasPassword = Boolean(options.password);
|
|
658
|
+
if (!hasBaseUrl || !hasEmail || !hasPassword) {
|
|
659
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
const baseUrl = hasBaseUrl ? options.baseUrl : await promptRequired("YApi base URL: ", false);
|
|
663
|
+
const email = hasEmail ? options.email : await promptRequired("YApi email: ", false);
|
|
664
|
+
const password = hasPassword ? options.password : await promptRequired("YApi password: ", true);
|
|
665
|
+
const config = {
|
|
666
|
+
base_url: baseUrl || "",
|
|
667
|
+
auth_mode: "global",
|
|
668
|
+
email: email || "",
|
|
669
|
+
password: password || "",
|
|
670
|
+
token: options.token || "",
|
|
671
|
+
project_id: options.projectId || "",
|
|
672
|
+
};
|
|
673
|
+
const configPath = globalConfigPath();
|
|
674
|
+
writeConfig(configPath, config);
|
|
675
|
+
return { configPath, config };
|
|
676
|
+
}
|
|
677
|
+
function readVersion() {
|
|
678
|
+
try {
|
|
679
|
+
const pkgPath = path_1.default.resolve(__dirname, "../package.json");
|
|
680
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, "utf8"));
|
|
681
|
+
return pkg.version || "unknown";
|
|
682
|
+
}
|
|
683
|
+
catch {
|
|
684
|
+
return "unknown";
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function runLogin(rawArgs) {
|
|
688
|
+
const options = parseArgs(rawArgs);
|
|
689
|
+
if (options.help) {
|
|
690
|
+
console.log(loginUsage());
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
693
|
+
if (options.version) {
|
|
694
|
+
console.log(readVersion());
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
697
|
+
const configPath = options.config || globalConfigPath();
|
|
698
|
+
const config = fs_1.default.existsSync(configPath)
|
|
699
|
+
? parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"))
|
|
700
|
+
: {};
|
|
701
|
+
let baseUrl = options.baseUrl || config.base_url || "";
|
|
702
|
+
let email = options.email || config.email || "";
|
|
703
|
+
let password = options.password || config.password || "";
|
|
704
|
+
let updated = false;
|
|
705
|
+
if (!baseUrl) {
|
|
706
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
707
|
+
console.error("missing --base-url or config base_url");
|
|
708
|
+
return 2;
|
|
709
|
+
}
|
|
710
|
+
baseUrl = await promptRequired("YApi base URL: ", false);
|
|
711
|
+
updated = true;
|
|
712
|
+
}
|
|
713
|
+
if (!email) {
|
|
714
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
715
|
+
console.error("missing --email or config email");
|
|
716
|
+
return 2;
|
|
717
|
+
}
|
|
718
|
+
email = await promptRequired("YApi email: ", false);
|
|
719
|
+
updated = true;
|
|
720
|
+
}
|
|
721
|
+
if (!password) {
|
|
722
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
723
|
+
console.error("missing --password or config password");
|
|
724
|
+
return 2;
|
|
725
|
+
}
|
|
726
|
+
password = await promptRequired("YApi password: ", true);
|
|
727
|
+
updated = true;
|
|
728
|
+
}
|
|
729
|
+
const shouldWriteConfig = updated || !fs_1.default.existsSync(configPath) || config.auth_mode !== "global";
|
|
730
|
+
if (shouldWriteConfig) {
|
|
731
|
+
const mergedConfig = {
|
|
732
|
+
base_url: baseUrl,
|
|
733
|
+
auth_mode: "global",
|
|
734
|
+
email,
|
|
735
|
+
password,
|
|
736
|
+
token: options.token || config.token || "",
|
|
737
|
+
project_id: options.projectId || config.project_id || "",
|
|
738
|
+
};
|
|
739
|
+
writeConfig(configPath, mergedConfig);
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
const authService = new auth_1.YApiAuthService(baseUrl, email, password, "warn", { timeoutMs: options.timeout || 30000 });
|
|
743
|
+
await authService.getCookieHeaderWithLogin({ forceLogin: true });
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
747
|
+
return 2;
|
|
748
|
+
}
|
|
749
|
+
console.log("login success (cookie cached in ~/.yapi-mcp/auth-*.json)");
|
|
750
|
+
return 0;
|
|
751
|
+
}
|
|
752
|
+
function findDocsSyncHome(startDir) {
|
|
753
|
+
let current = path_1.default.resolve(startDir);
|
|
754
|
+
while (true) {
|
|
755
|
+
const candidate = path_1.default.join(current, ".yapi");
|
|
756
|
+
if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isDirectory()) {
|
|
757
|
+
return candidate;
|
|
758
|
+
}
|
|
759
|
+
const parent = path_1.default.dirname(current);
|
|
760
|
+
if (parent === current)
|
|
761
|
+
return null;
|
|
762
|
+
current = parent;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function resolveDocsSyncHome(startDir, ensure) {
|
|
766
|
+
const found = findDocsSyncHome(startDir);
|
|
767
|
+
if (found)
|
|
768
|
+
return found;
|
|
769
|
+
if (!ensure)
|
|
770
|
+
return null;
|
|
771
|
+
const home = path_1.default.join(path_1.default.resolve(startDir), ".yapi");
|
|
772
|
+
fs_1.default.mkdirSync(home, { recursive: true });
|
|
773
|
+
ensureDocsSyncReadme(home);
|
|
774
|
+
return home;
|
|
775
|
+
}
|
|
776
|
+
function docsSyncConfigPath(homeDir) {
|
|
777
|
+
return path_1.default.join(homeDir, "docs-sync.json");
|
|
778
|
+
}
|
|
779
|
+
const DOCS_SYNC_README = [
|
|
780
|
+
"# YApi Docs Sync",
|
|
781
|
+
"",
|
|
782
|
+
"This folder is generated by the yapi docs-sync tool.",
|
|
783
|
+
"",
|
|
784
|
+
"Project: https://github.com/leeguooooo/cross-request-master",
|
|
785
|
+
"",
|
|
786
|
+
"Files:",
|
|
787
|
+
"- docs-sync.json: bindings and file-to-doc ID mapping.",
|
|
788
|
+
].join("\n");
|
|
789
|
+
function ensureDocsSyncReadme(homeDir) {
|
|
790
|
+
const readmePath = path_1.default.join(homeDir, "README.md");
|
|
791
|
+
if (fs_1.default.existsSync(readmePath))
|
|
792
|
+
return;
|
|
793
|
+
fs_1.default.writeFileSync(readmePath, `${DOCS_SYNC_README}\n`, "utf8");
|
|
794
|
+
}
|
|
795
|
+
function loadDocsSyncConfig(homeDir) {
|
|
796
|
+
const configPath = docsSyncConfigPath(homeDir);
|
|
797
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
798
|
+
return { version: 1, bindings: {} };
|
|
799
|
+
}
|
|
800
|
+
const raw = fs_1.default.readFileSync(configPath, "utf8");
|
|
801
|
+
const parsed = JSON.parse(raw);
|
|
802
|
+
const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
|
|
803
|
+
return {
|
|
804
|
+
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
805
|
+
bindings: bindings,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function saveDocsSyncConfig(homeDir, config) {
|
|
809
|
+
const configPath = docsSyncConfigPath(homeDir);
|
|
810
|
+
fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
811
|
+
}
|
|
812
|
+
function resolveBindingDir(rootDir, bindingDir) {
|
|
813
|
+
if (!bindingDir)
|
|
814
|
+
return rootDir;
|
|
815
|
+
return path_1.default.isAbsolute(bindingDir) ? bindingDir : path_1.default.resolve(rootDir, bindingDir);
|
|
816
|
+
}
|
|
817
|
+
function normalizeBindingDir(rootDir, bindingDir) {
|
|
818
|
+
const resolved = resolveBindingDir(rootDir, bindingDir);
|
|
819
|
+
const relative = path_1.default.relative(rootDir, resolved);
|
|
820
|
+
if (!relative || relative === ".")
|
|
821
|
+
return ".";
|
|
822
|
+
if (relative.startsWith("..") || path_1.default.isAbsolute(relative))
|
|
823
|
+
return resolved;
|
|
824
|
+
return relative;
|
|
825
|
+
}
|
|
826
|
+
function ensurePandoc() {
|
|
827
|
+
try {
|
|
828
|
+
(0, child_process_1.execFileSync)("pandoc", ["--version"], { stdio: "ignore" });
|
|
829
|
+
}
|
|
830
|
+
catch (error) {
|
|
831
|
+
throw new Error("pandoc not found. Install pandoc first.");
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function ensureMmdc() {
|
|
835
|
+
try {
|
|
836
|
+
(0, child_process_1.execFileSync)("mmdc", ["--version"], { stdio: "ignore" });
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
throw new Error("mmdc (mermaid-cli) not found. Install it to render Mermaid diagrams.");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function stripSvgProlog(svg) {
|
|
843
|
+
return svg
|
|
844
|
+
.replace(/^\s*<\?xml[^>]*>\s*/i, "")
|
|
845
|
+
.replace(/^\s*<!DOCTYPE[^>]*>\s*/i, "")
|
|
846
|
+
.trim();
|
|
847
|
+
}
|
|
848
|
+
function renderMermaidToSvg(source) {
|
|
849
|
+
ensureMmdc();
|
|
850
|
+
const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "yapi-docs-sync-"));
|
|
851
|
+
const inputPath = path_1.default.join(tmpDir, "diagram.mmd");
|
|
852
|
+
const outputPath = path_1.default.join(tmpDir, "diagram.svg");
|
|
853
|
+
try {
|
|
854
|
+
fs_1.default.writeFileSync(inputPath, source, "utf8");
|
|
855
|
+
(0, child_process_1.execFileSync)("mmdc", ["-i", inputPath, "-o", outputPath, "-b", "transparent"], { stdio: "pipe" });
|
|
856
|
+
const svg = fs_1.default.readFileSync(outputPath, "utf8");
|
|
857
|
+
return stripSvgProlog(svg);
|
|
858
|
+
}
|
|
859
|
+
finally {
|
|
860
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function preprocessMarkdown(markdown, options) {
|
|
864
|
+
if (options.noMermaid)
|
|
865
|
+
return markdown;
|
|
866
|
+
const pattern = /```mermaid\s*\r?\n([\s\S]*?)\r?\n```/g;
|
|
867
|
+
return markdown.replace(pattern, (_match, content) => {
|
|
868
|
+
const svg = renderMermaidToSvg(String(content || "").trim());
|
|
869
|
+
return `<div class="mermaid-diagram">\n${svg}\n</div>`;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
function markdownToHtml(markdown) {
|
|
873
|
+
return (0, child_process_1.execFileSync)("pandoc", ["-f", "gfm+hard_line_breaks", "-t", "html"], {
|
|
874
|
+
input: markdown,
|
|
875
|
+
encoding: "utf8",
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
function loadMapping(dirPath) {
|
|
879
|
+
const mappingPath = path_1.default.join(dirPath, ".yapi.json");
|
|
880
|
+
if (!fs_1.default.existsSync(mappingPath)) {
|
|
881
|
+
return { mapping: {}, mappingPath };
|
|
882
|
+
}
|
|
883
|
+
const raw = fs_1.default.readFileSync(mappingPath, "utf8");
|
|
884
|
+
const mapping = JSON.parse(raw);
|
|
885
|
+
return { mapping, mappingPath };
|
|
886
|
+
}
|
|
887
|
+
function saveMapping(mapping, mappingPath) {
|
|
888
|
+
fs_1.default.writeFileSync(mappingPath, `${JSON.stringify(mapping, null, 2)}\n`, "utf8");
|
|
889
|
+
}
|
|
890
|
+
function resolveSourceFiles(dirPath, mapping) {
|
|
891
|
+
const sources = Array.isArray(mapping.source_files) ? mapping.source_files : [];
|
|
892
|
+
if (!sources.length) {
|
|
893
|
+
return fs_1.default
|
|
894
|
+
.readdirSync(dirPath)
|
|
895
|
+
.filter((name) => name.endsWith(".md") && name !== "README.md")
|
|
896
|
+
.map((name) => path_1.default.join(dirPath, name))
|
|
897
|
+
.sort((a, b) => a.localeCompare(b));
|
|
898
|
+
}
|
|
899
|
+
return sources.map((source) => {
|
|
900
|
+
const resolved = path_1.default.isAbsolute(source) ? source : path_1.default.resolve(dirPath, source);
|
|
901
|
+
if (!fs_1.default.existsSync(resolved) || !fs_1.default.statSync(resolved).isFile()) {
|
|
902
|
+
throw new Error(`source file not found: ${resolved}`);
|
|
903
|
+
}
|
|
904
|
+
return resolved;
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
async function listExistingInterfaces(catId, request) {
|
|
908
|
+
const resp = await request("/api/interface/list_cat", "GET", { catid: catId, page: 1, limit: 10000 });
|
|
909
|
+
if (resp?.errcode !== 0) {
|
|
910
|
+
throw new Error(`list_cat failed: ${resp?.errmsg || "unknown error"}`);
|
|
911
|
+
}
|
|
912
|
+
const items = resp?.data?.list || [];
|
|
913
|
+
const byPath = {};
|
|
914
|
+
const byTitle = {};
|
|
915
|
+
for (const item of items) {
|
|
916
|
+
const apiPath = item?.path;
|
|
917
|
+
const title = item?.title;
|
|
918
|
+
const itemId = item?._id;
|
|
919
|
+
if (apiPath && itemId)
|
|
920
|
+
byPath[apiPath] = Number(itemId);
|
|
921
|
+
if (title && itemId)
|
|
922
|
+
byTitle[title] = Number(itemId);
|
|
923
|
+
}
|
|
924
|
+
return { byPath, byTitle };
|
|
925
|
+
}
|
|
926
|
+
function buildAddPayload(template, title, apiPath, catId, projectId) {
|
|
927
|
+
return {
|
|
928
|
+
title,
|
|
929
|
+
path: apiPath,
|
|
930
|
+
method: template.method || "GET",
|
|
931
|
+
catid: catId,
|
|
932
|
+
project_id: projectId,
|
|
933
|
+
status: template.status || "undone",
|
|
934
|
+
type: template.type || "static",
|
|
935
|
+
api_opened: template.api_opened || false,
|
|
936
|
+
req_query: template.req_query || [],
|
|
937
|
+
req_headers: template.req_headers || [],
|
|
938
|
+
req_params: template.req_params || [],
|
|
939
|
+
req_body_type: template.req_body_type || "json",
|
|
940
|
+
req_body_form: template.req_body_form || [],
|
|
941
|
+
req_body_is_json_schema: template.req_body_is_json_schema ?? true,
|
|
942
|
+
res_body_type: template.res_body_type || "json",
|
|
943
|
+
res_body: template.res_body || "{\"type\":\"object\",\"title\":\"title\",\"properties\":{}}",
|
|
944
|
+
res_body_is_json_schema: template.res_body_is_json_schema ?? true,
|
|
945
|
+
tag: template.tag || [],
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
async function addInterface(title, apiPath, mapping, request) {
|
|
949
|
+
const projectId = Number(mapping.project_id || 0);
|
|
950
|
+
const catId = Number(mapping.catid || 0);
|
|
951
|
+
if (!projectId || !catId) {
|
|
952
|
+
throw new Error("project_id and catid are required to create new docs");
|
|
953
|
+
}
|
|
954
|
+
let template = {};
|
|
955
|
+
if (mapping.template_id) {
|
|
956
|
+
const resp = await request("/api/interface/get", "GET", { id: mapping.template_id });
|
|
957
|
+
if (resp?.errcode !== 0) {
|
|
958
|
+
throw new Error(`interface get failed: ${resp?.errmsg || "unknown error"}`);
|
|
959
|
+
}
|
|
960
|
+
template = resp?.data || {};
|
|
961
|
+
}
|
|
962
|
+
const payload = buildAddPayload(template, title, apiPath, catId, projectId);
|
|
963
|
+
const resp = await request("/api/interface/add", "POST", {}, payload);
|
|
964
|
+
if (resp?.errcode !== 0) {
|
|
965
|
+
throw new Error(`interface add failed: ${resp?.errmsg || "unknown error"}`);
|
|
966
|
+
}
|
|
967
|
+
const newId = resp?.data?._id;
|
|
968
|
+
if (!newId) {
|
|
969
|
+
throw new Error("interface add succeeded but missing id");
|
|
970
|
+
}
|
|
971
|
+
return Number(newId);
|
|
972
|
+
}
|
|
973
|
+
async function updateInterface(docId, markdown, html, request) {
|
|
974
|
+
const resp = await request("/api/interface/up", "POST", {}, { id: docId, markdown, desc: html });
|
|
975
|
+
if (resp?.errcode !== 0) {
|
|
976
|
+
throw new Error(`interface up failed: ${resp?.errmsg || "unknown error"}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async function syncDocsDir(dirPath, mapping, options, request) {
|
|
980
|
+
mapping.files = mapping.files || {};
|
|
981
|
+
const envProjectId = process.env.YAPI_PROJECT_ID;
|
|
982
|
+
const envCatId = process.env.YAPI_CATID;
|
|
983
|
+
const envTemplateId = process.env.YAPI_TEMPLATE_ID;
|
|
984
|
+
if (!mapping.project_id && envProjectId)
|
|
985
|
+
mapping.project_id = Number(envProjectId);
|
|
986
|
+
if (!mapping.catid && envCatId)
|
|
987
|
+
mapping.catid = Number(envCatId);
|
|
988
|
+
if (!mapping.template_id && envTemplateId)
|
|
989
|
+
mapping.template_id = Number(envTemplateId);
|
|
990
|
+
if (!mapping.project_id || !mapping.catid) {
|
|
991
|
+
throw new Error("project_id/catid missing; set in binding/.yapi.json or env");
|
|
992
|
+
}
|
|
993
|
+
const { byPath, byTitle } = await listExistingInterfaces(Number(mapping.catid), request);
|
|
994
|
+
let updated = 0;
|
|
995
|
+
let created = 0;
|
|
996
|
+
const files = resolveSourceFiles(dirPath, mapping);
|
|
997
|
+
for (const mdPath of files) {
|
|
998
|
+
const stem = path_1.default.parse(mdPath).name;
|
|
999
|
+
const relName = path_1.default.basename(mdPath);
|
|
1000
|
+
const apiPath = `/${stem}`;
|
|
1001
|
+
let docId = mapping.files[relName];
|
|
1002
|
+
if (!docId) {
|
|
1003
|
+
docId = byPath[apiPath] || byTitle[stem];
|
|
1004
|
+
if (docId)
|
|
1005
|
+
mapping.files[relName] = docId;
|
|
1006
|
+
}
|
|
1007
|
+
if (!docId) {
|
|
1008
|
+
created += 1;
|
|
1009
|
+
if (!options.dryRun) {
|
|
1010
|
+
docId = await addInterface(stem, apiPath, mapping, request);
|
|
1011
|
+
mapping.files[relName] = docId;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const markdown = fs_1.default.readFileSync(mdPath, "utf8");
|
|
1015
|
+
const html = markdownToHtml(preprocessMarkdown(markdown, options));
|
|
1016
|
+
if (!options.dryRun && docId) {
|
|
1017
|
+
await updateInterface(docId, markdown, html, request);
|
|
1018
|
+
}
|
|
1019
|
+
updated += 1;
|
|
1020
|
+
}
|
|
1021
|
+
return { updated, created };
|
|
1022
|
+
}
|
|
1023
|
+
async function runDocsSyncBindings(rawArgs) {
|
|
1024
|
+
const action = (rawArgs[0] || "list").toLowerCase();
|
|
1025
|
+
const args = rawArgs[0] ? rawArgs.slice(1) : rawArgs;
|
|
1026
|
+
const options = parseDocsSyncBindArgs(args);
|
|
1027
|
+
if (options.help) {
|
|
1028
|
+
console.log(docsSyncBindUsage());
|
|
1029
|
+
return 0;
|
|
1030
|
+
}
|
|
1031
|
+
const writeActions = new Set(["add", "update", "remove", "rm", "delete", "del"]);
|
|
1032
|
+
const readActions = new Set(["list", "get"]);
|
|
1033
|
+
if (!writeActions.has(action) && !readActions.has(action)) {
|
|
1034
|
+
console.error(`unknown docs-sync bind action: ${action}`);
|
|
1035
|
+
console.error(docsSyncBindUsage());
|
|
1036
|
+
return 2;
|
|
1037
|
+
}
|
|
1038
|
+
const homeDir = resolveDocsSyncHome(process.cwd(), writeActions.has(action));
|
|
1039
|
+
if (!homeDir) {
|
|
1040
|
+
if (action === "list") {
|
|
1041
|
+
console.log("no docs-sync bindings (missing .yapi/docs-sync.json)");
|
|
1042
|
+
return 0;
|
|
1043
|
+
}
|
|
1044
|
+
console.error("missing .yapi/docs-sync.json (run in project root or create one with docs-sync bind add)");
|
|
1045
|
+
return 2;
|
|
1046
|
+
}
|
|
1047
|
+
const rootDir = path_1.default.dirname(homeDir);
|
|
1048
|
+
const config = loadDocsSyncConfig(homeDir);
|
|
1049
|
+
if (action === "list") {
|
|
1050
|
+
const entries = Object.entries(config.bindings).sort(([a], [b]) => a.localeCompare(b));
|
|
1051
|
+
if (!entries.length) {
|
|
1052
|
+
console.log("no docs-sync bindings");
|
|
1053
|
+
return 0;
|
|
1054
|
+
}
|
|
1055
|
+
for (const [name, binding] of entries) {
|
|
1056
|
+
const filesCount = binding.files ? Object.keys(binding.files).length : 0;
|
|
1057
|
+
console.log(`${name} dir=${binding.dir} project_id=${binding.project_id ?? ""} catid=${binding.catid ?? ""}` +
|
|
1058
|
+
(binding.template_id ? ` template_id=${binding.template_id}` : "") +
|
|
1059
|
+
` files=${filesCount}`);
|
|
1060
|
+
}
|
|
1061
|
+
return 0;
|
|
1062
|
+
}
|
|
1063
|
+
if (!options.name) {
|
|
1064
|
+
console.error("missing --name for docs-sync bind action");
|
|
1065
|
+
console.error(docsSyncBindUsage());
|
|
1066
|
+
return 2;
|
|
1067
|
+
}
|
|
1068
|
+
if (action === "get") {
|
|
1069
|
+
const binding = config.bindings[options.name];
|
|
1070
|
+
if (!binding) {
|
|
1071
|
+
console.error(`binding not found: ${options.name}`);
|
|
1072
|
+
return 2;
|
|
1073
|
+
}
|
|
1074
|
+
console.log(JSON.stringify(binding, null, 2));
|
|
1075
|
+
return 0;
|
|
1076
|
+
}
|
|
1077
|
+
if (action === "remove" || action === "rm" || action === "delete" || action === "del") {
|
|
1078
|
+
if (!config.bindings[options.name]) {
|
|
1079
|
+
console.error(`binding not found: ${options.name}`);
|
|
1080
|
+
return 2;
|
|
1081
|
+
}
|
|
1082
|
+
delete config.bindings[options.name];
|
|
1083
|
+
saveDocsSyncConfig(homeDir, config);
|
|
1084
|
+
console.log(`binding removed: ${options.name}`);
|
|
1085
|
+
return 0;
|
|
1086
|
+
}
|
|
1087
|
+
const hasExisting = Boolean(config.bindings[options.name]);
|
|
1088
|
+
if (action === "add" && hasExisting) {
|
|
1089
|
+
console.error(`binding already exists: ${options.name}`);
|
|
1090
|
+
return 2;
|
|
1091
|
+
}
|
|
1092
|
+
if (action === "update" && !hasExisting) {
|
|
1093
|
+
console.error(`binding not found: ${options.name}`);
|
|
1094
|
+
return 2;
|
|
1095
|
+
}
|
|
1096
|
+
if (action === "add") {
|
|
1097
|
+
if (!options.dir || !Number.isFinite(options.projectId ?? NaN) || !Number.isFinite(options.catId ?? NaN)) {
|
|
1098
|
+
console.error("add requires --dir, --project-id, and --catid");
|
|
1099
|
+
console.error(docsSyncBindUsage());
|
|
1100
|
+
return 2;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const existing = config.bindings[options.name] || {};
|
|
1104
|
+
const next = {
|
|
1105
|
+
dir: existing.dir || "",
|
|
1106
|
+
project_id: existing.project_id,
|
|
1107
|
+
catid: existing.catid,
|
|
1108
|
+
template_id: existing.template_id,
|
|
1109
|
+
source_files: existing.source_files ? [...existing.source_files] : undefined,
|
|
1110
|
+
files: existing.files ? { ...existing.files } : {},
|
|
1111
|
+
};
|
|
1112
|
+
if (options.dir) {
|
|
1113
|
+
next.dir = normalizeBindingDir(rootDir, options.dir);
|
|
1114
|
+
}
|
|
1115
|
+
if (options.projectId !== undefined && Number.isFinite(options.projectId)) {
|
|
1116
|
+
next.project_id = Number(options.projectId);
|
|
1117
|
+
}
|
|
1118
|
+
if (options.catId !== undefined && Number.isFinite(options.catId)) {
|
|
1119
|
+
next.catid = Number(options.catId);
|
|
1120
|
+
}
|
|
1121
|
+
if (options.templateId !== undefined && Number.isFinite(options.templateId)) {
|
|
1122
|
+
next.template_id = Number(options.templateId);
|
|
1123
|
+
}
|
|
1124
|
+
if (options.clearSourceFiles) {
|
|
1125
|
+
next.source_files = [];
|
|
1126
|
+
}
|
|
1127
|
+
else if (options.sourceFiles && options.sourceFiles.length) {
|
|
1128
|
+
next.source_files = options.sourceFiles;
|
|
1129
|
+
}
|
|
1130
|
+
if (!next.dir || !next.project_id || !next.catid) {
|
|
1131
|
+
console.error("binding requires dir/project_id/catid");
|
|
1132
|
+
return 2;
|
|
1133
|
+
}
|
|
1134
|
+
config.bindings[options.name] = next;
|
|
1135
|
+
saveDocsSyncConfig(homeDir, config);
|
|
1136
|
+
console.log(`${action === "add" ? "binding added" : "binding updated"}: ${options.name}`);
|
|
1137
|
+
return 0;
|
|
1138
|
+
}
|
|
1139
|
+
async function runDocsSync(rawArgs) {
|
|
1140
|
+
const firstArg = rawArgs[0];
|
|
1141
|
+
if (firstArg === "bind" || firstArg === "bindings") {
|
|
1142
|
+
return await runDocsSyncBindings(rawArgs.slice(1));
|
|
1143
|
+
}
|
|
1144
|
+
const options = parseDocsSyncArgs(rawArgs);
|
|
1145
|
+
if (options.help) {
|
|
1146
|
+
console.log(docsSyncUsage());
|
|
1147
|
+
return 0;
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
ensurePandoc();
|
|
1151
|
+
if (options.bindings.length && options.dirs.length) {
|
|
1152
|
+
console.error("use --binding or --dir, not both");
|
|
1153
|
+
return 2;
|
|
1154
|
+
}
|
|
1155
|
+
const docsSyncHome = resolveDocsSyncHome(process.cwd(), false);
|
|
1156
|
+
const docsSyncConfig = docsSyncHome ? loadDocsSyncConfig(docsSyncHome) : null;
|
|
1157
|
+
let bindingNames = options.bindings;
|
|
1158
|
+
let useBindings = bindingNames.length > 0;
|
|
1159
|
+
if (!useBindings && !options.dirs.length && docsSyncConfig && Object.keys(docsSyncConfig.bindings).length) {
|
|
1160
|
+
useBindings = true;
|
|
1161
|
+
bindingNames = Object.keys(docsSyncConfig.bindings);
|
|
1162
|
+
}
|
|
1163
|
+
if (useBindings && (!docsSyncHome || !docsSyncConfig)) {
|
|
1164
|
+
console.error("missing .yapi/docs-sync.json (run docs-sync bind add or use --dir)");
|
|
1165
|
+
return 2;
|
|
1166
|
+
}
|
|
1167
|
+
if (useBindings && !bindingNames.length) {
|
|
1168
|
+
console.error("no docs-sync bindings found (run docs-sync bind add or use --dir)");
|
|
1169
|
+
return 2;
|
|
1170
|
+
}
|
|
1171
|
+
const dirs = useBindings ? [] : options.dirs.length ? options.dirs : ["docs/release-notes"];
|
|
1172
|
+
let config = {};
|
|
1173
|
+
let configPath = options.config || "";
|
|
1174
|
+
if (options.config) {
|
|
1175
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
1176
|
+
config = parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"));
|
|
1177
|
+
}
|
|
1178
|
+
else {
|
|
1179
|
+
const init = await initConfigIfMissing(options);
|
|
1180
|
+
if (init) {
|
|
1181
|
+
config = init.config;
|
|
1182
|
+
configPath = init.configPath;
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
console.error(`missing config file: ${configPath}`);
|
|
1186
|
+
return 2;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
const globalPath = globalConfigPath();
|
|
1192
|
+
if (fs_1.default.existsSync(globalPath)) {
|
|
1193
|
+
configPath = globalPath;
|
|
1194
|
+
config = parseSimpleToml(fs_1.default.readFileSync(globalPath, "utf8"));
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
const init = await initConfigIfMissing(options);
|
|
1198
|
+
if (init) {
|
|
1199
|
+
config = init.config;
|
|
1200
|
+
configPath = init.configPath;
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
console.error("missing config: create ~/.yapi/config.toml or pass --config");
|
|
1204
|
+
return 2;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
const baseUrl = options.baseUrl || config.base_url || "";
|
|
1209
|
+
if (!baseUrl) {
|
|
1210
|
+
console.error("missing --base-url or config base_url");
|
|
1211
|
+
return 2;
|
|
1212
|
+
}
|
|
1213
|
+
const projectId = options.projectId || config.project_id || "";
|
|
1214
|
+
const rawToken = options.token || config.token || "";
|
|
1215
|
+
const token = resolveToken(rawToken, projectId);
|
|
1216
|
+
let authMode = (options.authMode || config.auth_mode || "").trim().toLowerCase();
|
|
1217
|
+
if (!authMode) {
|
|
1218
|
+
authMode = token
|
|
1219
|
+
? "token"
|
|
1220
|
+
: (options.email || options.password || config.email || config.password)
|
|
1221
|
+
? "global"
|
|
1222
|
+
: "token";
|
|
1223
|
+
}
|
|
1224
|
+
if (authMode !== "token" && authMode !== "global") {
|
|
1225
|
+
console.error("invalid --auth-mode (use token or global)");
|
|
1226
|
+
return 2;
|
|
1227
|
+
}
|
|
1228
|
+
const headers = {};
|
|
1229
|
+
const email = options.email || config.email || "";
|
|
1230
|
+
const password = options.password || config.password || "";
|
|
1231
|
+
const authService = authMode === "global"
|
|
1232
|
+
? new auth_1.YApiAuthService(baseUrl, email || "", password || "", "warn", { timeoutMs: options.timeout || 30000 })
|
|
1233
|
+
: null;
|
|
1234
|
+
const canRelogin = authMode === "global" && Boolean(authService) && Boolean(email) && Boolean(password) && !options.cookie;
|
|
1235
|
+
if (options.cookie) {
|
|
1236
|
+
headers.Cookie = options.cookie;
|
|
1237
|
+
}
|
|
1238
|
+
else if (authMode === "global") {
|
|
1239
|
+
const cachedCookie = authService?.getCachedCookieHeader();
|
|
1240
|
+
if (cachedCookie) {
|
|
1241
|
+
headers.Cookie = cachedCookie;
|
|
1242
|
+
}
|
|
1243
|
+
else if (email && password && authService) {
|
|
1244
|
+
try {
|
|
1245
|
+
headers.Cookie = await authService.getCookieHeaderWithLogin();
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1249
|
+
return 2;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
console.error("missing email/password for global auth");
|
|
1254
|
+
return 2;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
const request = async (endpoint, method, query = {}, data) => {
|
|
1258
|
+
const queryItems = [];
|
|
1259
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1260
|
+
if (value !== undefined && value !== null) {
|
|
1261
|
+
queryItems.push([key, String(value)]);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
|
|
1265
|
+
let body;
|
|
1266
|
+
if (data !== undefined) {
|
|
1267
|
+
body = JSON.stringify(data);
|
|
1268
|
+
}
|
|
1269
|
+
const sendOnce = async () => {
|
|
1270
|
+
const requestHeaders = { ...headers };
|
|
1271
|
+
if (body !== undefined) {
|
|
1272
|
+
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
1273
|
+
}
|
|
1274
|
+
const response = await fetchWithTimeout(url, {
|
|
1275
|
+
method,
|
|
1276
|
+
headers: requestHeaders,
|
|
1277
|
+
body,
|
|
1278
|
+
}, options.timeout || 30000);
|
|
1279
|
+
const text = await response.text();
|
|
1280
|
+
return { response, text, json: parseJsonMaybe(text) };
|
|
1281
|
+
};
|
|
1282
|
+
let result = await sendOnce();
|
|
1283
|
+
if (canRelogin && looksLikeAuthError(result.response.status, result.json)) {
|
|
1284
|
+
try {
|
|
1285
|
+
headers.Cookie = await authService.getCookieHeaderWithLogin({ forceLogin: true });
|
|
1286
|
+
}
|
|
1287
|
+
catch (error) {
|
|
1288
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
1289
|
+
}
|
|
1290
|
+
result = await sendOnce();
|
|
1291
|
+
}
|
|
1292
|
+
if (!result.response.ok) {
|
|
1293
|
+
throw new Error(`request failed: ${result.response.status} ${result.response.statusText} ${result.text}`);
|
|
1294
|
+
}
|
|
1295
|
+
if (!result.json) {
|
|
1296
|
+
throw new Error(`invalid JSON response from ${endpoint}`);
|
|
1297
|
+
}
|
|
1298
|
+
return result.json;
|
|
1299
|
+
};
|
|
1300
|
+
if (useBindings) {
|
|
1301
|
+
const rootDir = path_1.default.dirname(docsSyncHome);
|
|
1302
|
+
const configForBindings = docsSyncConfig;
|
|
1303
|
+
for (const name of bindingNames) {
|
|
1304
|
+
const binding = configForBindings.bindings[name];
|
|
1305
|
+
if (!binding) {
|
|
1306
|
+
throw new Error(`binding not found: ${name}`);
|
|
1307
|
+
}
|
|
1308
|
+
const dirPath = resolveBindingDir(rootDir, binding.dir);
|
|
1309
|
+
if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
|
|
1310
|
+
throw new Error(`dir not found for binding ${name}: ${dirPath}`);
|
|
1311
|
+
}
|
|
1312
|
+
const result = await syncDocsDir(dirPath, binding, options, request);
|
|
1313
|
+
console.log(`synced=${result.updated} created=${result.created} binding=${name} dir=${dirPath}`);
|
|
1314
|
+
}
|
|
1315
|
+
if (!options.dryRun) {
|
|
1316
|
+
saveDocsSyncConfig(docsSyncHome, configForBindings);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
else {
|
|
1320
|
+
for (const dir of dirs) {
|
|
1321
|
+
const dirPath = path_1.default.resolve(dir);
|
|
1322
|
+
if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
|
|
1323
|
+
throw new Error(`dir not found: ${dirPath}`);
|
|
1324
|
+
}
|
|
1325
|
+
const { mapping, mappingPath } = loadMapping(dirPath);
|
|
1326
|
+
const result = await syncDocsDir(dirPath, mapping, options, request);
|
|
1327
|
+
if (!options.dryRun) {
|
|
1328
|
+
saveMapping(mapping, mappingPath);
|
|
1329
|
+
}
|
|
1330
|
+
console.log(`synced=${result.updated} created=${result.created} dir=${dirPath}`);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return 0;
|
|
1334
|
+
}
|
|
1335
|
+
catch (error) {
|
|
1336
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1337
|
+
return 2;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
async function main() {
|
|
1341
|
+
const rawArgs = process.argv.slice(2);
|
|
1342
|
+
if (rawArgs[0] === "install-skill") {
|
|
1343
|
+
await (0, install_1.runInstallSkill)(rawArgs.slice(1));
|
|
1344
|
+
return 0;
|
|
1345
|
+
}
|
|
1346
|
+
if (rawArgs[0] === "login") {
|
|
1347
|
+
return await runLogin(rawArgs.slice(1));
|
|
1348
|
+
}
|
|
1349
|
+
if (rawArgs[0] === "docs-sync") {
|
|
1350
|
+
return await runDocsSync(rawArgs.slice(1));
|
|
1351
|
+
}
|
|
1352
|
+
const options = parseArgs(rawArgs);
|
|
1353
|
+
if (options.version) {
|
|
1354
|
+
console.log(readVersion());
|
|
1355
|
+
return 0;
|
|
1356
|
+
}
|
|
1357
|
+
if (options.help) {
|
|
1358
|
+
console.log(usage());
|
|
1359
|
+
return 0;
|
|
1360
|
+
}
|
|
1361
|
+
if (options.url && options.path) {
|
|
1362
|
+
console.error("use --url or --path, not both");
|
|
1363
|
+
return 2;
|
|
1364
|
+
}
|
|
1365
|
+
if (!options.url && !options.path) {
|
|
1366
|
+
console.error("missing --path or --url");
|
|
1367
|
+
console.error(usage());
|
|
1368
|
+
return 2;
|
|
1369
|
+
}
|
|
1370
|
+
let config = {};
|
|
1371
|
+
let configPath = options.config || "";
|
|
1372
|
+
if (options.config) {
|
|
1373
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
1374
|
+
config = parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"));
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
const init = await initConfigIfMissing(options);
|
|
1378
|
+
if (init) {
|
|
1379
|
+
config = init.config;
|
|
1380
|
+
configPath = init.configPath;
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
console.error(`missing config file: ${configPath}`);
|
|
1384
|
+
return 2;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
const globalPath = globalConfigPath();
|
|
1390
|
+
if (fs_1.default.existsSync(globalPath)) {
|
|
1391
|
+
configPath = globalPath;
|
|
1392
|
+
config = parseSimpleToml(fs_1.default.readFileSync(globalPath, "utf8"));
|
|
1393
|
+
}
|
|
1394
|
+
else {
|
|
1395
|
+
const init = await initConfigIfMissing(options);
|
|
1396
|
+
if (init) {
|
|
1397
|
+
config = init.config;
|
|
1398
|
+
configPath = init.configPath;
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
console.error("missing config: create ~/.yapi/config.toml or pass --config");
|
|
1402
|
+
return 2;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
const authBaseUrl = options.baseUrl || config.base_url || "";
|
|
1407
|
+
const baseUrl = options.url ? null : authBaseUrl;
|
|
1408
|
+
const endpoint = options.url || options.path || "";
|
|
1409
|
+
if (!options.url && !baseUrl) {
|
|
1410
|
+
console.error("missing --base-url or config base_url");
|
|
1411
|
+
return 2;
|
|
1412
|
+
}
|
|
1413
|
+
const projectId = options.projectId || config.project_id || "";
|
|
1414
|
+
const rawToken = options.token || config.token || "";
|
|
1415
|
+
const token = resolveToken(rawToken, projectId);
|
|
1416
|
+
let authMode = (options.authMode || config.auth_mode || "").trim().toLowerCase();
|
|
1417
|
+
if (!authMode) {
|
|
1418
|
+
authMode = token ? "token" : (options.email || options.password || config.email || config.password) ? "global" : "token";
|
|
1419
|
+
}
|
|
1420
|
+
if (authMode !== "token" && authMode !== "global") {
|
|
1421
|
+
console.error("invalid --auth-mode (use token or global)");
|
|
1422
|
+
return 2;
|
|
1423
|
+
}
|
|
1424
|
+
if (authMode === "global" && !authBaseUrl) {
|
|
1425
|
+
console.error("missing --base-url or config base_url for global auth");
|
|
1426
|
+
return 2;
|
|
1427
|
+
}
|
|
1428
|
+
const headers = {};
|
|
1429
|
+
for (const header of options.header || []) {
|
|
1430
|
+
const [key, value] = parseHeader(header);
|
|
1431
|
+
headers[key] = value;
|
|
1432
|
+
}
|
|
1433
|
+
const email = options.email || config.email || "";
|
|
1434
|
+
const password = options.password || config.password || "";
|
|
1435
|
+
const authService = authMode === "global"
|
|
1436
|
+
? new auth_1.YApiAuthService(authBaseUrl, email || "", password || "", "warn", { timeoutMs: options.timeout || 30000 })
|
|
1437
|
+
: null;
|
|
1438
|
+
const canRelogin = authMode === "global" && Boolean(authService) && Boolean(email) && Boolean(password) && !options.cookie;
|
|
1439
|
+
if (options.cookie) {
|
|
1440
|
+
headers.Cookie = options.cookie;
|
|
1441
|
+
}
|
|
1442
|
+
else if (authMode === "global") {
|
|
1443
|
+
const cachedCookie = authService?.getCachedCookieHeader();
|
|
1444
|
+
if (cachedCookie) {
|
|
1445
|
+
headers.Cookie = cachedCookie;
|
|
1446
|
+
}
|
|
1447
|
+
else if (email && password && authService) {
|
|
1448
|
+
try {
|
|
1449
|
+
headers.Cookie = await authService.getCookieHeaderWithLogin();
|
|
1450
|
+
}
|
|
1451
|
+
catch (error) {
|
|
1452
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1453
|
+
return 2;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
console.error("missing email/password for global auth");
|
|
1458
|
+
return 2;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
const queryItems = [];
|
|
1462
|
+
for (const query of options.query || []) {
|
|
1463
|
+
queryItems.push(parseKeyValue(query));
|
|
1464
|
+
}
|
|
1465
|
+
const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
|
|
1466
|
+
let dataRaw = null;
|
|
1467
|
+
if (options.dataFile) {
|
|
1468
|
+
dataRaw = fs_1.default.readFileSync(options.dataFile, "utf8");
|
|
1469
|
+
}
|
|
1470
|
+
else if (options.data !== undefined) {
|
|
1471
|
+
dataRaw = options.data;
|
|
1472
|
+
}
|
|
1473
|
+
let body;
|
|
1474
|
+
const method = (options.method || "GET").toUpperCase();
|
|
1475
|
+
if (dataRaw !== null && method !== "GET" && method !== "HEAD") {
|
|
1476
|
+
try {
|
|
1477
|
+
const parsed = JSON.parse(dataRaw);
|
|
1478
|
+
body = JSON.stringify(parsed);
|
|
1479
|
+
if (!headers["Content-Type"])
|
|
1480
|
+
headers["Content-Type"] = "application/json";
|
|
1481
|
+
}
|
|
1482
|
+
catch {
|
|
1483
|
+
body = String(dataRaw);
|
|
1484
|
+
if (!headers["Content-Type"])
|
|
1485
|
+
headers["Content-Type"] = "text/plain";
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const sendOnce = async () => {
|
|
1489
|
+
const response = await fetchWithTimeout(url, {
|
|
1490
|
+
method,
|
|
1491
|
+
headers,
|
|
1492
|
+
body,
|
|
1493
|
+
}, options.timeout || 30000);
|
|
1494
|
+
const text = await response.text();
|
|
1495
|
+
return { response, text, json: parseJsonMaybe(text) };
|
|
1496
|
+
};
|
|
1497
|
+
let result;
|
|
1498
|
+
try {
|
|
1499
|
+
result = await sendOnce();
|
|
1500
|
+
}
|
|
1501
|
+
catch (error) {
|
|
1502
|
+
console.error("request failed: " + (error instanceof Error ? error.message : String(error)));
|
|
1503
|
+
return 2;
|
|
1504
|
+
}
|
|
1505
|
+
if (canRelogin && looksLikeAuthError(result.response.status, result.json)) {
|
|
1506
|
+
try {
|
|
1507
|
+
headers.Cookie = await authService.getCookieHeaderWithLogin({ forceLogin: true });
|
|
1508
|
+
result = await sendOnce();
|
|
1509
|
+
}
|
|
1510
|
+
catch (error) {
|
|
1511
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1512
|
+
return 2;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
const { text } = result;
|
|
1516
|
+
if (options.noPretty) {
|
|
1517
|
+
console.log(text);
|
|
1518
|
+
return 0;
|
|
1519
|
+
}
|
|
1520
|
+
try {
|
|
1521
|
+
const payload = result.json ?? JSON.parse(text);
|
|
1522
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
console.log(text);
|
|
1526
|
+
}
|
|
1527
|
+
return 0;
|
|
1528
|
+
}
|
|
1529
|
+
main().then((code) => {
|
|
1530
|
+
process.exitCode = code;
|
|
1531
|
+
});
|
|
1532
|
+
//# sourceMappingURL=yapi-cli.js.map
|