bukesiyi-cli 0.0.0-stage → 0.1.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.
- package/README.md +217 -2
- package/bin/buke.js +11 -0
- package/dist/main.js +1269 -0
- package/package.json +32 -4
package/dist/main.js
ADDED
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
// src/main.ts
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
|
|
7
|
+
// src/env-proxy.ts
|
|
8
|
+
var DEFAULT_PORTS = { "http:": 80, "https:": 443 };
|
|
9
|
+
function nonEmpty(value) {
|
|
10
|
+
return value !== void 0 && value.trim() !== "";
|
|
11
|
+
}
|
|
12
|
+
function enabled(env2) {
|
|
13
|
+
return nonEmpty(env2.NODE_USE_ENV_PROXY) || /(^|\s)--use-env-proxy(=|\s|$)/.test(env2.NODE_OPTIONS ?? "");
|
|
14
|
+
}
|
|
15
|
+
function proxyVariable(protocol, env2) {
|
|
16
|
+
const forHttp = ["http_proxy", "HTTP_PROXY"];
|
|
17
|
+
const candidates = protocol === "https:" ? ["https_proxy", "HTTPS_PROXY", ...forHttp] : forHttp;
|
|
18
|
+
return candidates.find((name2) => nonEmpty(env2[name2])) ?? null;
|
|
19
|
+
}
|
|
20
|
+
function excluded(url, env2) {
|
|
21
|
+
const value = env2.no_proxy ?? env2.NO_PROXY ?? "";
|
|
22
|
+
if (value === "*") return true;
|
|
23
|
+
const hostname = url.host.replace(/:\d*$/, "").toLowerCase();
|
|
24
|
+
const port = Number.parseInt(url.port, 10) || DEFAULT_PORTS[url.protocol] || 0;
|
|
25
|
+
return value.split(/[,\s]/).some((entry) => {
|
|
26
|
+
const withPort = /^(.+):(\d+)$/.exec(entry);
|
|
27
|
+
const entryPort = withPort?.[2] === void 0 ? 0 : Number.parseInt(withPort[2], 10);
|
|
28
|
+
return (withPort?.[1] ?? entry).toLowerCase() === hostname && (entryPort === 0 || entryPort === port);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function envProxyFor(origin, env2) {
|
|
32
|
+
if (!enabled(env2)) return null;
|
|
33
|
+
const url = new URL(origin);
|
|
34
|
+
const variable = proxyVariable(url.protocol, env2);
|
|
35
|
+
return variable !== null && !excluded(url, env2) ? variable : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/exit-codes.ts
|
|
39
|
+
var EXIT = {
|
|
40
|
+
OK: 0,
|
|
41
|
+
USAGE: 2,
|
|
42
|
+
AUTH: 3,
|
|
43
|
+
FUNDS: 4,
|
|
44
|
+
SERVER: 5,
|
|
45
|
+
QUOTE_CHANGED: 6,
|
|
46
|
+
RETRY_LATER: 7,
|
|
47
|
+
WAIT_TIMEOUT: 8,
|
|
48
|
+
TASK_FAILED: 9,
|
|
49
|
+
NOT_FOUND: 10,
|
|
50
|
+
INTERRUPTED: 130
|
|
51
|
+
};
|
|
52
|
+
function exitCodeForHttpStatus(status) {
|
|
53
|
+
if (status === 401 || status === 403) return EXIT.AUTH;
|
|
54
|
+
if (status === 404) return EXIT.NOT_FOUND;
|
|
55
|
+
if (status === 429) return EXIT.RETRY_LATER;
|
|
56
|
+
if (status >= 400 && status < 500) return EXIT.USAGE;
|
|
57
|
+
return EXIT.SERVER;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/errors.ts
|
|
61
|
+
var KEY_LIKE = /bk_[A-Za-z0-9_-]{20,}/g;
|
|
62
|
+
var PUBLIC_PREFIX = /^bk_(?:live|test)_[A-Za-z0-9]{16}$/;
|
|
63
|
+
var SECRET_TAIL = /^bk_[A-Za-z0-9]+_[A-Za-z0-9]+_(.{16,})$/;
|
|
64
|
+
var MIN_SECRET_LENGTH = 8;
|
|
65
|
+
var CONTROL_CHARS = new RegExp("\\p{Cc}+", "gu");
|
|
66
|
+
var MAX_REASON_LENGTH = 200;
|
|
67
|
+
function redactKeys(text, keep = []) {
|
|
68
|
+
return text.replace(
|
|
69
|
+
KEY_LIKE,
|
|
70
|
+
(found) => PUBLIC_PREFIX.test(found) && keep.includes(found) ? found : "bk_[redacted]"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
function redactSecret(text, secret) {
|
|
74
|
+
if (secret.length < MIN_SECRET_LENGTH) return text;
|
|
75
|
+
const withoutWhole = text.split(secret).join("bk_[redacted]");
|
|
76
|
+
const tail = SECRET_TAIL.exec(secret)?.[1];
|
|
77
|
+
return tail ? withoutWhole.split(tail).join("[redacted]") : withoutWhole;
|
|
78
|
+
}
|
|
79
|
+
function singleLine(text) {
|
|
80
|
+
return text.replace(CONTROL_CHARS, " ").trim();
|
|
81
|
+
}
|
|
82
|
+
var CliError = class _CliError extends Error {
|
|
83
|
+
code;
|
|
84
|
+
exitCode;
|
|
85
|
+
retryable;
|
|
86
|
+
traceId;
|
|
87
|
+
retryAfterSeconds;
|
|
88
|
+
httpStatus;
|
|
89
|
+
hints;
|
|
90
|
+
constructor(init) {
|
|
91
|
+
super(redactKeys(init.message));
|
|
92
|
+
this.name = "CliError";
|
|
93
|
+
this.code = init.code;
|
|
94
|
+
this.exitCode = init.exitCode;
|
|
95
|
+
this.retryable = init.retryable ?? false;
|
|
96
|
+
this.traceId = init.traceId ?? null;
|
|
97
|
+
if (init.retryAfterSeconds !== void 0) this.retryAfterSeconds = init.retryAfterSeconds;
|
|
98
|
+
if (init.httpStatus !== void 0) this.httpStatus = init.httpStatus;
|
|
99
|
+
this.hints = init.hints ?? [];
|
|
100
|
+
}
|
|
101
|
+
withHint(hint) {
|
|
102
|
+
return new _CliError({
|
|
103
|
+
code: this.code,
|
|
104
|
+
message: this.message,
|
|
105
|
+
exitCode: this.exitCode,
|
|
106
|
+
retryable: this.retryable,
|
|
107
|
+
traceId: this.traceId,
|
|
108
|
+
retryAfterSeconds: this.retryAfterSeconds,
|
|
109
|
+
httpStatus: this.httpStatus,
|
|
110
|
+
hints: [...this.hints, hint]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function usageError(message) {
|
|
115
|
+
return new CliError({ code: "CLI_USAGE", exitCode: EXIT.USAGE, message: `${message}\u3002\u8FD0\u884C buke --help \u770B\u7528\u6CD5` });
|
|
116
|
+
}
|
|
117
|
+
function interruptedError(message = "\u5DF2\u4E2D\u65AD") {
|
|
118
|
+
return new CliError({ code: "CLI_INTERRUPTED", exitCode: EXIT.INTERRUPTED, message });
|
|
119
|
+
}
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null;
|
|
122
|
+
}
|
|
123
|
+
function nonEmptyText(value) {
|
|
124
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
125
|
+
}
|
|
126
|
+
function briefReason(error, secrets = []) {
|
|
127
|
+
const cause = isRecord(error) ? error.cause : void 0;
|
|
128
|
+
const picked = (isRecord(cause) ? nonEmptyText(cause.code) ?? nonEmptyText(cause.message) : void 0) ?? (isRecord(error) ? nonEmptyText(error.message) : void 0) ?? nonEmptyText(error) ?? "\u539F\u56E0\u4E0D\u660E";
|
|
129
|
+
return secrets.reduce(redactSecret, redactKeys(singleLine(picked))).slice(0, MAX_REASON_LENGTH);
|
|
130
|
+
}
|
|
131
|
+
function toCliError(error) {
|
|
132
|
+
if (error instanceof CliError) return error;
|
|
133
|
+
return new CliError({
|
|
134
|
+
code: "CLI_INTERNAL",
|
|
135
|
+
exitCode: EXIT.SERVER,
|
|
136
|
+
message: `\u51FA\u4E86\u4E00\u4E2A\u6CA1\u6599\u5230\u7684\u9519\uFF1A${briefReason(error)}`
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/base-url.ts
|
|
141
|
+
var DEFAULT_BASE_URL = "https://buke41.com";
|
|
142
|
+
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
143
|
+
function invalid(detail) {
|
|
144
|
+
return new CliError({
|
|
145
|
+
code: "CLI_BASE_URL_INVALID",
|
|
146
|
+
exitCode: EXIT.USAGE,
|
|
147
|
+
message: `\u670D\u52A1\u5730\u5740\u4E0D\u5BF9\uFF1A${detail}`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function normalizeBaseUrl(input) {
|
|
151
|
+
let url;
|
|
152
|
+
try {
|
|
153
|
+
url = new URL(input);
|
|
154
|
+
} catch {
|
|
155
|
+
throw invalid("\u8981\u5199\u6210\u5B8C\u6574\u7684\u7F51\u5740\uFF0C\u4F8B\u5982 https://buke41.com");
|
|
156
|
+
}
|
|
157
|
+
if (url.username !== "" || url.password !== "" || url.pathname.replace(/^\/$/, "") !== "") {
|
|
158
|
+
throw invalid("\u53EA\u5199\u5230\u57DF\u540D\u4E3A\u6B62\uFF0C\u4F8B\u5982 https://buke41.com");
|
|
159
|
+
}
|
|
160
|
+
if (url.search !== "" || url.hash !== "" || url.href.includes("?") || url.href.includes("#")) {
|
|
161
|
+
throw invalid("\u53EA\u5199\u5230\u57DF\u540D\u4E3A\u6B62\uFF0C\u4F8B\u5982 https://buke41.com");
|
|
162
|
+
}
|
|
163
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && LOCAL_HOSTS.has(url.hostname))) {
|
|
164
|
+
throw new CliError({
|
|
165
|
+
code: "CLI_BASE_URL_INSECURE",
|
|
166
|
+
exitCode: EXIT.USAGE,
|
|
167
|
+
message: "\u4E3A\u4E86\u4E0D\u8BA9\u5BC6\u94A5\u660E\u6587\u8FC7\u7F51\uFF0C\u53EA\u63A5\u53D7 https \u5730\u5740\uFF08\u672C\u673A\u8C03\u8BD5\u7684 localhost \u9664\u5916\uFF09"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return url.origin;
|
|
171
|
+
}
|
|
172
|
+
function assertReachedDirectly(origin, env2) {
|
|
173
|
+
if (env2 === void 0 || !origin.startsWith("http://")) return;
|
|
174
|
+
const variable = envProxyFor(origin, env2);
|
|
175
|
+
if (variable === null) return;
|
|
176
|
+
throw new CliError({
|
|
177
|
+
code: "CLI_BASE_URL_INSECURE",
|
|
178
|
+
exitCode: EXIT.USAGE,
|
|
179
|
+
message: `\u73AF\u5883\u91CC\u5F00\u7740\u4EE3\u7406\uFF08${variable}\uFF09\uFF0C\u53D1\u5F80 ${origin} \u7684\u8BF7\u6C42\u53EF\u80FD\u4F1A\u8FDE\u540C\u5BC6\u94A5\u660E\u6587\u4EA4\u7ED9\u4EE3\u7406\uFF0C\u6240\u4EE5\u6CA1\u6709\u53D1\u9001\u3002\u672C\u673A\u8C03\u8BD5\u8BF7\u628A\u8FD9\u4E2A\u5730\u5740\u6392\u9664\u5728\u4EE3\u7406\u4E4B\u5916\uFF0C\u4F8B\u5982\u8BBE\u7F6E NO_PROXY='localhost,127.0.0.1,[::1]'`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function nonEmpty2(value) {
|
|
183
|
+
return value !== void 0 && value.trim() !== "" ? value : void 0;
|
|
184
|
+
}
|
|
185
|
+
function explicitBaseUrl(flag, env2) {
|
|
186
|
+
if (flag !== void 0) return normalizeBaseUrl(flag);
|
|
187
|
+
const fromEnv = nonEmpty2(env2.BUKE_BASE_URL);
|
|
188
|
+
return fromEnv === void 0 ? null : normalizeBaseUrl(fromEnv);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/key-input.ts
|
|
192
|
+
var DEFAULT_STDIN_TIMEOUT_MS = 3e4;
|
|
193
|
+
var KEY_IN_ARGV = /(^|[^A-Za-z0-9])bk_[A-Za-z0-9_-]{4,}/;
|
|
194
|
+
var KEY_FORMAT = /^bk_[A-Za-z0-9_-]{8,200}$/;
|
|
195
|
+
var CANONICAL_KEY = /^bk_(live|test)_/;
|
|
196
|
+
var PUBLIC_KEY_ID = /^[A-Za-z0-9]{16}$/;
|
|
197
|
+
var MAX_INPUT_BYTES = 8 * 1024;
|
|
198
|
+
var CTRL_C = "";
|
|
199
|
+
var CTRL_D = "";
|
|
200
|
+
var BACKSPACE = "\b";
|
|
201
|
+
var DELETE = "\x7F";
|
|
202
|
+
var ESCAPE = "\x1B";
|
|
203
|
+
var CANCELLED = "\u5DF2\u53D6\u6D88\uFF0C\u6CA1\u6709\u4FDD\u5B58\u4EFB\u4F55\u51ED\u636E";
|
|
204
|
+
function argvContainsKey(argv) {
|
|
205
|
+
return argv.some((item) => KEY_IN_ARGV.test(item));
|
|
206
|
+
}
|
|
207
|
+
function keyFromEnv(env2) {
|
|
208
|
+
const value = env2.BUKE_API_KEY?.trim();
|
|
209
|
+
return value ? value : null;
|
|
210
|
+
}
|
|
211
|
+
function malformedKey() {
|
|
212
|
+
return new CliError({
|
|
213
|
+
code: "CLI_KEY_MALFORMED",
|
|
214
|
+
exitCode: EXIT.USAGE,
|
|
215
|
+
message: "\u8FD9\u4E0D\u50CF\u4E00\u628A API \u5BC6\u94A5\uFF1A\u5E94\u4EE5 bk_ \u5F00\u5934\uFF0C\u4E2D\u95F4\u6CA1\u6709\u7A7A\u683C\u6216\u6362\u884C"
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function missingKey(message) {
|
|
219
|
+
return new CliError({ code: "CLI_KEY_MISSING", exitCode: EXIT.USAGE, message });
|
|
220
|
+
}
|
|
221
|
+
var NO_KEY_READ = "\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF BUKE_API_KEY\uFF0C\u6216\u8005\u901A\u8FC7\u7BA1\u9053\u628A\u5BC6\u94A5\u4F20\u7ED9 buke login";
|
|
222
|
+
function assertKeyFormat(key) {
|
|
223
|
+
if (!KEY_FORMAT.test(key)) throw malformedKey();
|
|
224
|
+
}
|
|
225
|
+
function displayPrefixOf(apiKey, keyId) {
|
|
226
|
+
const environment = CANONICAL_KEY.exec(apiKey)?.[1];
|
|
227
|
+
return environment && PUBLIC_KEY_ID.test(keyId) ? `bk_${environment}_${keyId}` : keyId;
|
|
228
|
+
}
|
|
229
|
+
function formatSeconds(milliseconds) {
|
|
230
|
+
return String(Math.round(milliseconds / 10) / 100);
|
|
231
|
+
}
|
|
232
|
+
function release(stdin) {
|
|
233
|
+
stdin.pause();
|
|
234
|
+
const handle = stdin;
|
|
235
|
+
if (typeof handle.unref === "function") handle.unref();
|
|
236
|
+
}
|
|
237
|
+
function toBuffer(chunk) {
|
|
238
|
+
if (typeof chunk === "string") return Buffer.from(chunk, "utf8");
|
|
239
|
+
return Buffer.from(chunk);
|
|
240
|
+
}
|
|
241
|
+
function readToEnd(stdin, timeoutMs, signal) {
|
|
242
|
+
return new Promise((resolve2, reject) => {
|
|
243
|
+
const state = stdin;
|
|
244
|
+
if (state.readableEnded === true || state.destroyed === true) {
|
|
245
|
+
resolve2("");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const chunks = [];
|
|
249
|
+
let size = 0;
|
|
250
|
+
let settled = false;
|
|
251
|
+
const settle = (deliver) => {
|
|
252
|
+
if (settled) return;
|
|
253
|
+
settled = true;
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
stdin.removeListener("data", onData);
|
|
256
|
+
stdin.removeListener("end", onEnd);
|
|
257
|
+
stdin.removeListener("close", onEnd);
|
|
258
|
+
stdin.removeListener("error", onError);
|
|
259
|
+
signal?.removeEventListener("abort", onAbort);
|
|
260
|
+
release(stdin);
|
|
261
|
+
deliver();
|
|
262
|
+
};
|
|
263
|
+
const onAbort = () => settle(() => reject(interruptedError(CANCELLED)));
|
|
264
|
+
const onData = (chunk) => {
|
|
265
|
+
const piece = toBuffer(chunk);
|
|
266
|
+
size += piece.length;
|
|
267
|
+
if (size > MAX_INPUT_BYTES) {
|
|
268
|
+
settle(() => reject(malformedKey()));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
chunks.push(piece);
|
|
272
|
+
};
|
|
273
|
+
const onEnd = () => settle(() => resolve2(Buffer.concat(chunks).toString("utf8").trim()));
|
|
274
|
+
const onError = (error) => settle(() => reject(missingKey(`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u6807\u51C6\u8F93\u5165\u51FA\u9519\uFF08${briefReason(error)}\uFF09`)));
|
|
275
|
+
const timer = setTimeout(() => {
|
|
276
|
+
settle(
|
|
277
|
+
() => reject(
|
|
278
|
+
missingKey(
|
|
279
|
+
`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u7B49\u4E86 ${formatSeconds(timeoutMs)} \u79D2\uFF0C\u6807\u51C6\u8F93\u5165\u4E00\u76F4\u6CA1\u6709\u7ED3\u675F\u3002\u8BF7\u7528 BUKE_API_KEY\uFF0C\u6216\u8005\u8FD9\u6837\u4F20\uFF1Aprintf '%s' "$KEY" | buke login`
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
}, timeoutMs);
|
|
284
|
+
stdin.on("data", onData);
|
|
285
|
+
stdin.on("end", onEnd);
|
|
286
|
+
stdin.on("close", onEnd);
|
|
287
|
+
stdin.on("error", onError);
|
|
288
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
289
|
+
stdin.resume();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function readHiddenLine(stdin, signal) {
|
|
293
|
+
return new Promise((resolve2, reject) => {
|
|
294
|
+
let typed = "";
|
|
295
|
+
let escape = "none";
|
|
296
|
+
let settled = false;
|
|
297
|
+
const settle = (deliver) => {
|
|
298
|
+
if (settled) return;
|
|
299
|
+
settled = true;
|
|
300
|
+
stdin.removeListener("data", onData);
|
|
301
|
+
stdin.removeListener("end", onEnd);
|
|
302
|
+
stdin.removeListener("close", onEnd);
|
|
303
|
+
stdin.removeListener("error", onError);
|
|
304
|
+
signal?.removeEventListener("abort", onAbort);
|
|
305
|
+
deliver();
|
|
306
|
+
};
|
|
307
|
+
const onData = (chunk) => {
|
|
308
|
+
for (const char of toBuffer(chunk).toString("utf8")) {
|
|
309
|
+
if (char === "\r" || char === "\n" || char === CTRL_C || char === CTRL_D) {
|
|
310
|
+
escape = "none";
|
|
311
|
+
} else if (escape === "started" || escape === "carried") {
|
|
312
|
+
const alone = escape === "carried";
|
|
313
|
+
if (char === "[" || char === "O" && !alone) {
|
|
314
|
+
escape = "sequence";
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
escape = "none";
|
|
318
|
+
if (!alone) continue;
|
|
319
|
+
} else if (escape === "sequence") {
|
|
320
|
+
if (char >= "@" && char <= "~") escape = "none";
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (char === "\r" || char === "\n") {
|
|
324
|
+
settle(() => resolve2(typed));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (char === CTRL_C) {
|
|
328
|
+
settle(() => reject(interruptedError(CANCELLED)));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (char === CTRL_D) {
|
|
332
|
+
settle(() => typed === "" ? reject(missingKey(NO_KEY_READ)) : resolve2(typed));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (char === DELETE || char === BACKSPACE) {
|
|
336
|
+
typed = typed.slice(0, -1);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (char === ESCAPE) {
|
|
340
|
+
escape = "started";
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (char < " ") continue;
|
|
344
|
+
typed += char;
|
|
345
|
+
if (typed.length > MAX_INPUT_BYTES) {
|
|
346
|
+
settle(() => reject(malformedKey()));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (escape === "started") escape = "carried";
|
|
351
|
+
};
|
|
352
|
+
const onEnd = () => settle(() => resolve2(typed));
|
|
353
|
+
const onError = (error) => settle(() => reject(missingKey(`\u6CA1\u6709\u8BFB\u5230\u5BC6\u94A5\uFF1A\u6807\u51C6\u8F93\u5165\u51FA\u9519\uFF08${briefReason(error)}\uFF09`)));
|
|
354
|
+
const onAbort = () => settle(() => reject(interruptedError(CANCELLED)));
|
|
355
|
+
stdin.on("data", onData);
|
|
356
|
+
stdin.on("end", onEnd);
|
|
357
|
+
stdin.on("close", onEnd);
|
|
358
|
+
stdin.on("error", onError);
|
|
359
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
360
|
+
stdin.resume();
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
async function promptHidden(io, setRawMode) {
|
|
364
|
+
io.stderr.write("\u8BF7\u7C98\u8D34 API \u5BC6\u94A5\uFF08\u8F93\u5165\u4E0D\u4F1A\u663E\u793A\uFF09\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF1A");
|
|
365
|
+
try {
|
|
366
|
+
setRawMode.call(io.stdin, true);
|
|
367
|
+
return (await readHiddenLine(io.stdin, io.signal)).trim();
|
|
368
|
+
} finally {
|
|
369
|
+
try {
|
|
370
|
+
setRawMode.call(io.stdin, false);
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
release(io.stdin);
|
|
374
|
+
io.stderr.write("\n");
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function willPromptForKey(io) {
|
|
378
|
+
return keyFromEnv(io.env) === null && io.stdin.isTTY === true && typeof io.stdin.setRawMode === "function";
|
|
379
|
+
}
|
|
380
|
+
async function readNewKey(io) {
|
|
381
|
+
const fromEnv = keyFromEnv(io.env);
|
|
382
|
+
if (fromEnv !== null) {
|
|
383
|
+
assertKeyFormat(fromEnv);
|
|
384
|
+
return { key: fromEnv, source: "env" };
|
|
385
|
+
}
|
|
386
|
+
if (io.signal?.aborted) throw interruptedError(CANCELLED);
|
|
387
|
+
const setRawMode = io.stdin.setRawMode;
|
|
388
|
+
const interactive = io.stdin.isTTY === true && typeof setRawMode === "function";
|
|
389
|
+
const key = interactive ? await promptHidden(io, setRawMode) : await readToEnd(io.stdin, io.stdinTimeoutMs ?? DEFAULT_STDIN_TIMEOUT_MS, io.signal);
|
|
390
|
+
if (key === "") throw missingKey(NO_KEY_READ);
|
|
391
|
+
assertKeyFormat(key);
|
|
392
|
+
return { key, source: interactive ? "terminal" : "stdin" };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// package.json
|
|
396
|
+
var name = "bukesiyi-cli";
|
|
397
|
+
var version = "0.1.0";
|
|
398
|
+
|
|
399
|
+
// src/version.ts
|
|
400
|
+
var PACKAGE_NAME = name;
|
|
401
|
+
var VERSION = version;
|
|
402
|
+
|
|
403
|
+
// src/client.ts
|
|
404
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
|
|
405
|
+
var ME_PATH = "/api/open/v1/me";
|
|
406
|
+
var MAX_BODY_BYTES = 1024 * 1024;
|
|
407
|
+
var NOT_FOUND_HINT = "\u8FD9\u4E2A\u5730\u5740\u4E0A\u6CA1\u6709\u5F00\u653E\u63A5\u53E3\uFF1A\u53EF\u80FD\u8FD8\u6CA1\u6709\u5BF9\u5916\u5F00\u901A\uFF0C\u6216\u8005 --base-url \u5199\u9519\u4E86";
|
|
408
|
+
function userAgent() {
|
|
409
|
+
return `${PACKAGE_NAME}/${VERSION} node/${process.versions.node}`;
|
|
410
|
+
}
|
|
411
|
+
function isRecord2(value) {
|
|
412
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
413
|
+
}
|
|
414
|
+
function retryableStatus(status) {
|
|
415
|
+
return status === 429 || status >= 500;
|
|
416
|
+
}
|
|
417
|
+
function unreadableResponse(status) {
|
|
418
|
+
const retryable = retryableStatus(status);
|
|
419
|
+
const advice = retryable ? "\u53EF\u80FD\u662F\u7F51\u5173\u6216\u4E2D\u95F4\u5C42\u51FA\u7684\u9519\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : "\u53EF\u80FD\u662F\u7F51\u5173\u6216\u4E2D\u95F4\u5C42\u51FA\u7684\u9519\uFF0C\u4E5F\u53EF\u80FD\u662F\u670D\u52A1\u5730\u5740\u6307\u5230\u4E86\u522B\u5904\uFF1B\u91CD\u8BD5\u591A\u534A\u6CA1\u6709\u7528\uFF0C\u8BF7\u5148\u6838\u5BF9\u670D\u52A1\u5730\u5740";
|
|
420
|
+
return new CliError({
|
|
421
|
+
code: "CLI_BAD_RESPONSE",
|
|
422
|
+
exitCode: EXIT.SERVER,
|
|
423
|
+
retryable,
|
|
424
|
+
message: `\u670D\u52A1\u5668\u56DE\u4E86\u4E00\u4E2A\u770B\u4E0D\u61C2\u7684\u56DE\u5E94\uFF08HTTP ${status}\uFF09\uFF0C${advice}`,
|
|
425
|
+
hints: status === 404 ? [NOT_FOUND_HINT] : []
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function redirectTarget(response, requestUrl) {
|
|
429
|
+
const location = response.headers?.get("location");
|
|
430
|
+
if (!location) return null;
|
|
431
|
+
try {
|
|
432
|
+
const origin = new URL(location, requestUrl).origin;
|
|
433
|
+
return origin === "null" ? null : origin;
|
|
434
|
+
} catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function discardBody(response) {
|
|
439
|
+
try {
|
|
440
|
+
void response.body?.cancel().catch(() => void 0);
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async function readBody(response, abandoned) {
|
|
445
|
+
const body = response.body;
|
|
446
|
+
if (body === null) return "";
|
|
447
|
+
if (body === void 0) {
|
|
448
|
+
const text = await response.text();
|
|
449
|
+
return Buffer.byteLength(text, "utf8") > MAX_BODY_BYTES ? null : text;
|
|
450
|
+
}
|
|
451
|
+
const reader = body.getReader();
|
|
452
|
+
const release2 = () => void reader.cancel().catch(() => void 0);
|
|
453
|
+
if (abandoned.aborted) release2();
|
|
454
|
+
abandoned.addEventListener("abort", release2, { once: true });
|
|
455
|
+
const chunks = [];
|
|
456
|
+
let size = 0;
|
|
457
|
+
try {
|
|
458
|
+
for (; ; ) {
|
|
459
|
+
const { done, value } = await reader.read();
|
|
460
|
+
if (done) break;
|
|
461
|
+
size += value.byteLength;
|
|
462
|
+
if (size > MAX_BODY_BYTES) {
|
|
463
|
+
await reader.cancel().catch(() => void 0);
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
chunks.push(value);
|
|
467
|
+
}
|
|
468
|
+
} finally {
|
|
469
|
+
abandoned.removeEventListener("abort", release2);
|
|
470
|
+
}
|
|
471
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
472
|
+
}
|
|
473
|
+
function isTimeout(error) {
|
|
474
|
+
const name2 = typeof error === "object" && error !== null ? error.name : void 0;
|
|
475
|
+
return name2 === "TimeoutError" || name2 === "AbortError";
|
|
476
|
+
}
|
|
477
|
+
function proxyHints(origin, deps) {
|
|
478
|
+
const variable = deps.env ? envProxyFor(origin, deps.env) : null;
|
|
479
|
+
return variable === null ? [] : [`\u73AF\u5883\u91CC\u5F00\u7740\u4EE3\u7406\uFF08${variable}\uFF09\uFF0C\u8FD9\u6B21\u8BF7\u6C42\u53EF\u80FD\u662F\u7ECF\u5B83\u53D1\u51FA\u7684\uFF1A\u51FA\u95EE\u9898\u7684\u4E5F\u53EF\u80FD\u662F\u4EE3\u7406`];
|
|
480
|
+
}
|
|
481
|
+
async function exchange(url, apiKey, deps, origin) {
|
|
482
|
+
if (deps.signal?.aborted) throw interruptedError();
|
|
483
|
+
const timeoutMs = deps.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
484
|
+
const controller = new AbortController();
|
|
485
|
+
let timedOut = false;
|
|
486
|
+
let timer;
|
|
487
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
488
|
+
timer = setTimeout(() => {
|
|
489
|
+
timedOut = true;
|
|
490
|
+
const reason = new DOMException("request timed out", "TimeoutError");
|
|
491
|
+
controller.abort(reason);
|
|
492
|
+
reject(reason);
|
|
493
|
+
}, timeoutMs);
|
|
494
|
+
});
|
|
495
|
+
let stop = () => void 0;
|
|
496
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
497
|
+
stop = () => {
|
|
498
|
+
controller.abort();
|
|
499
|
+
reject(interruptedError());
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
deps.signal?.addEventListener("abort", stop, { once: true });
|
|
503
|
+
const send = deps.fetch;
|
|
504
|
+
const work = (async () => {
|
|
505
|
+
const response = await send(url, {
|
|
506
|
+
method: "GET",
|
|
507
|
+
headers: {
|
|
508
|
+
Authorization: `Bearer ${apiKey}`,
|
|
509
|
+
Accept: "application/json",
|
|
510
|
+
"User-Agent": userAgent()
|
|
511
|
+
},
|
|
512
|
+
// 跟着跳转走,密钥就会被带到一个使用者没有点头的地方。
|
|
513
|
+
redirect: "manual",
|
|
514
|
+
signal: controller.signal
|
|
515
|
+
});
|
|
516
|
+
if (response.status >= 300 && response.status < 400) {
|
|
517
|
+
discardBody(response);
|
|
518
|
+
return { status: response.status, redirectedTo: redirectTarget(response, url) };
|
|
519
|
+
}
|
|
520
|
+
return { status: response.status, text: await readBody(response, controller.signal) };
|
|
521
|
+
})();
|
|
522
|
+
work.catch(() => void 0);
|
|
523
|
+
try {
|
|
524
|
+
return await Promise.race([work, deadline, interrupted]);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (error instanceof CliError) throw error;
|
|
527
|
+
if (timedOut || isTimeout(error)) {
|
|
528
|
+
throw new CliError({
|
|
529
|
+
code: "CLI_TIMEOUT",
|
|
530
|
+
exitCode: EXIT.SERVER,
|
|
531
|
+
retryable: true,
|
|
532
|
+
message: `\u8FDE\u63A5 ${origin} \u8D85\u65F6\uFF08${formatSeconds(timeoutMs)} \u79D2\uFF09`,
|
|
533
|
+
hints: proxyHints(origin, deps)
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
throw new CliError({
|
|
537
|
+
code: "CLI_NETWORK",
|
|
538
|
+
exitCode: EXIT.SERVER,
|
|
539
|
+
retryable: true,
|
|
540
|
+
// 底层的错有时会把请求头原样抄进原因里;手里这把密钥在这一层就抹掉,不指望调用方。
|
|
541
|
+
message: `\u8FDE\u4E0D\u4E0A ${origin}\uFF1A${briefReason(error, [apiKey])}`,
|
|
542
|
+
hints: proxyHints(origin, deps)
|
|
543
|
+
});
|
|
544
|
+
} finally {
|
|
545
|
+
clearTimeout(timer);
|
|
546
|
+
deps.signal?.removeEventListener("abort", stop);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function parseJson(text) {
|
|
550
|
+
if (text === null) return null;
|
|
551
|
+
try {
|
|
552
|
+
return { value: JSON.parse(text) };
|
|
553
|
+
} catch {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function serverError(status, body, apiKey) {
|
|
558
|
+
const error = isRecord2(body) ? body.error : void 0;
|
|
559
|
+
if (!isRecord2(error) || typeof error.code !== "string" || typeof error.message !== "string") return null;
|
|
560
|
+
const retryAfter = error.retryAfterSeconds;
|
|
561
|
+
return new CliError({
|
|
562
|
+
code: error.code,
|
|
563
|
+
// 对面把收到的密钥抄回话里时,在这一层就抹掉。
|
|
564
|
+
message: error.message.trim() === "" ? `\u670D\u52A1\u5668\u62D2\u7EDD\u4E86\u8FD9\u6B21\u8BF7\u6C42\uFF08HTTP ${status}\uFF09` : redactSecret(error.message, apiKey),
|
|
565
|
+
exitCode: exitCodeForHttpStatus(status),
|
|
566
|
+
retryable: typeof error.retryable === "boolean" ? error.retryable : retryableStatus(status),
|
|
567
|
+
traceId: typeof error.traceId === "string" ? error.traceId : null,
|
|
568
|
+
retryAfterSeconds: typeof retryAfter === "number" && Number.isFinite(retryAfter) ? retryAfter : void 0,
|
|
569
|
+
httpStatus: status,
|
|
570
|
+
hints: status === 404 ? [NOT_FOUND_HINT] : []
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
async function getJson(baseUrl, apiKey, path, deps) {
|
|
574
|
+
const origin = normalizeBaseUrl(baseUrl);
|
|
575
|
+
assertKeyFormat(apiKey);
|
|
576
|
+
assertReachedDirectly(origin, deps.env);
|
|
577
|
+
const result = await exchange(`${origin}${path}`, apiKey, deps, origin);
|
|
578
|
+
if ("redirectedTo" in result) {
|
|
579
|
+
const where = result.redirectedTo ? `\uFF08${result.redirectedTo}\uFF09` : "";
|
|
580
|
+
throw new CliError({
|
|
581
|
+
code: "CLI_REDIRECT",
|
|
582
|
+
exitCode: EXIT.SERVER,
|
|
583
|
+
retryable: false,
|
|
584
|
+
message: `\u670D\u52A1\u5668\u628A\u8BF7\u6C42\u8F6C\u5230\u4E86\u522B\u5904${where}\uFF0C\u4E3A\u4E86\u4E0D\u628A\u5BC6\u94A5\u5E26\u8FC7\u53BB\u6CA1\u6709\u8DDF\u968F\u3002\u8BF7\u6838\u5BF9 --base-url`
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
const parsed = parseJson(result.text);
|
|
588
|
+
if (result.status >= 200 && result.status < 300) {
|
|
589
|
+
if (!parsed) throw unreadableResponse(result.status);
|
|
590
|
+
return { status: result.status, body: parsed.value };
|
|
591
|
+
}
|
|
592
|
+
throw (parsed && serverError(result.status, parsed.value, apiKey)) ?? unreadableResponse(result.status);
|
|
593
|
+
}
|
|
594
|
+
function parseMe(body) {
|
|
595
|
+
if (!isRecord2(body)) return null;
|
|
596
|
+
const { key, account, workspace } = body;
|
|
597
|
+
if (!isRecord2(key) || !isRecord2(account) || !isRecord2(workspace)) return null;
|
|
598
|
+
const { id, name: name2, scopes, expiresAt } = key;
|
|
599
|
+
if (typeof id !== "string" || typeof name2 !== "string") return null;
|
|
600
|
+
if (!Array.isArray(scopes) || !scopes.every((scope) => typeof scope === "string")) return null;
|
|
601
|
+
if (expiresAt !== null && typeof expiresAt !== "string") return null;
|
|
602
|
+
if (typeof account.id !== "string") return null;
|
|
603
|
+
if (typeof workspace.id !== "string" || typeof workspace.role !== "string") return null;
|
|
604
|
+
return {
|
|
605
|
+
key: { id, name: name2, scopes: [...scopes], expiresAt },
|
|
606
|
+
account: { id: account.id },
|
|
607
|
+
workspace: { id: workspace.id, role: workspace.role }
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async function getMe(baseUrl, apiKey, deps) {
|
|
611
|
+
const reply = await getJson(baseUrl, apiKey, ME_PATH, deps);
|
|
612
|
+
const me = parseMe(reply.body);
|
|
613
|
+
if (!me) throw unreadableResponse(reply.status);
|
|
614
|
+
return me;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/credentials.ts
|
|
618
|
+
import { randomBytes } from "node:crypto";
|
|
619
|
+
import { constants } from "node:fs";
|
|
620
|
+
import { chmod, lstat, mkdir, open, readdir, rename, stat, unlink } from "node:fs/promises";
|
|
621
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
622
|
+
var FILE_NAME = "credentials.json";
|
|
623
|
+
var OWNER_ONLY_FILE = 384;
|
|
624
|
+
var OWNER_ONLY_DIR = 448;
|
|
625
|
+
var GROUP_OR_OTHERS = 63;
|
|
626
|
+
var WRITABLE_BY_OTHERS = 18;
|
|
627
|
+
var MAX_FILE_BYTES = 64 * 1024;
|
|
628
|
+
var TEMP_NAME = /^\.credentials\.json\.\d+\.[0-9a-f]{12}\.tmp$/;
|
|
629
|
+
var STALE_TEMP_MS = 6e4;
|
|
630
|
+
var NO_FOLLOW = constants.O_NOFOLLOW ?? 0;
|
|
631
|
+
var NON_BLOCKING = constants.O_NONBLOCK ?? 0;
|
|
632
|
+
var SILENT = () => void 0;
|
|
633
|
+
function nonEmpty3(value) {
|
|
634
|
+
return value !== void 0 && value.trim() !== "" ? value : void 0;
|
|
635
|
+
}
|
|
636
|
+
function absoluteOnly(value) {
|
|
637
|
+
return value !== void 0 && isAbsolute(value) ? value : void 0;
|
|
638
|
+
}
|
|
639
|
+
function configDirError(message) {
|
|
640
|
+
return new CliError({ code: "CLI_CONFIG_DIR_INVALID", exitCode: EXIT.USAGE, message });
|
|
641
|
+
}
|
|
642
|
+
function expandHome(value, io) {
|
|
643
|
+
if (value === "~") return io.homedir();
|
|
644
|
+
if (value.startsWith("~/") || io.platform === "win32" && value.startsWith("~\\")) {
|
|
645
|
+
return join(io.homedir(), value.slice(2));
|
|
646
|
+
}
|
|
647
|
+
return value;
|
|
648
|
+
}
|
|
649
|
+
function locate(io) {
|
|
650
|
+
const chosen = nonEmpty3(io.env.BUKE_CONFIG_DIR);
|
|
651
|
+
if (chosen !== void 0) {
|
|
652
|
+
const dir = expandHome(chosen, io);
|
|
653
|
+
if (!isAbsolute(dir)) {
|
|
654
|
+
throw configDirError(
|
|
655
|
+
"BUKE_CONFIG_DIR \u8981\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF08\u53EF\u4EE5\u7528 ~/ \u5F00\u5934\u8868\u793A\u5BB6\u76EE\u5F55\uFF09\u3002\u76F8\u5BF9\u8DEF\u5F84\u4F1A\u8BA9\u51ED\u636E\u843D\u5728\u5F53\u524D\u76EE\u5F55\u91CC\uFF0C\u6240\u4EE5\u6CA1\u6709\u4F7F\u7528"
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return { dir, ours: false };
|
|
659
|
+
}
|
|
660
|
+
const base = absoluteOnly(nonEmpty3(io.env.XDG_CONFIG_HOME)) ?? (io.platform === "win32" ? absoluteOnly(nonEmpty3(io.env.APPDATA)) : void 0);
|
|
661
|
+
if (base !== void 0) return { dir: join(base, "buke"), ours: true };
|
|
662
|
+
const home = io.homedir();
|
|
663
|
+
if (!isAbsolute(home)) {
|
|
664
|
+
throw configDirError("\u627E\u4E0D\u5230\u5BB6\u76EE\u5F55\uFF0C\u6CA1\u6CD5\u51B3\u5B9A\u51ED\u636E\u4FDD\u5B58\u5728\u54EA\u91CC\u3002\u8BF7\u7528 BUKE_CONFIG_DIR \u6307\u5B9A\u4E00\u4E2A\u7EDD\u5BF9\u8DEF\u5F84");
|
|
665
|
+
}
|
|
666
|
+
return { dir: join(home, ".config", "buke"), ours: true };
|
|
667
|
+
}
|
|
668
|
+
function credentialsPath(io) {
|
|
669
|
+
return join(locate(io).dir, FILE_NAME);
|
|
670
|
+
}
|
|
671
|
+
function errorCode(error) {
|
|
672
|
+
const code2 = typeof error === "object" && error !== null ? error.code : void 0;
|
|
673
|
+
return typeof code2 === "string" ? code2 : void 0;
|
|
674
|
+
}
|
|
675
|
+
function systemReason(error) {
|
|
676
|
+
return briefReason(error).split(", ")[0] ?? "\u539F\u56E0\u4E0D\u660E";
|
|
677
|
+
}
|
|
678
|
+
function isMissing(error) {
|
|
679
|
+
const code2 = errorCode(error);
|
|
680
|
+
return code2 === "ENOENT" || code2 === "ENOTDIR";
|
|
681
|
+
}
|
|
682
|
+
async function ensureDir(dir) {
|
|
683
|
+
await mkdir(dirname(dir), { recursive: true });
|
|
684
|
+
try {
|
|
685
|
+
await mkdir(dir, { mode: OWNER_ONLY_DIR });
|
|
686
|
+
return true;
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (errorCode(error) !== "EEXIST" || !(await stat(dir)).isDirectory()) throw error;
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
async function guardDir(io, { dir, ours }, tighten, warn) {
|
|
693
|
+
if (io.platform === "win32") return;
|
|
694
|
+
let mode;
|
|
695
|
+
try {
|
|
696
|
+
mode = (await stat(dir)).mode;
|
|
697
|
+
} catch {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if ((mode & GROUP_OR_OTHERS) === 0) return;
|
|
701
|
+
const exposed = (mode & WRITABLE_BY_OTHERS) !== 0;
|
|
702
|
+
if (ours && tighten) {
|
|
703
|
+
try {
|
|
704
|
+
await chmod(dir, OWNER_ONLY_DIR);
|
|
705
|
+
if (exposed) warn(`\u4FDD\u5B58\u51ED\u636E\u7684\u76EE\u5F55\u539F\u5148\u522B\u4EBA\u4E5F\u80FD\u5199\uFF0C\u5DF2\u6536\u7D27\u4E3A\u53EA\u6709\u672C\u4EBA\u53EF\u8FDB\u5165\uFF1A${dir}`);
|
|
706
|
+
return;
|
|
707
|
+
} catch {
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (exposed) {
|
|
711
|
+
warn(`\u4FDD\u5B58\u51ED\u636E\u7684\u76EE\u5F55\u522B\u4EBA\u4E5F\u80FD\u5199\uFF1A${dir}\u3002\u5EFA\u8BAE chmod 700 ${dir}\uFF0C\u6216\u8005\u7528 BUKE_CONFIG_DIR \u6362\u4E00\u4E2A\u53EA\u6709\u672C\u4EBA\u80FD\u5199\u7684\u76EE\u5F55`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function leftoverTemps(dir) {
|
|
715
|
+
try {
|
|
716
|
+
return (await readdir(dir)).filter((name2) => TEMP_NAME.test(name2)).map((name2) => join(dir, name2));
|
|
717
|
+
} catch {
|
|
718
|
+
return [];
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function sweepStaleTemps(dir) {
|
|
722
|
+
const cutoff = Date.now() - STALE_TEMP_MS;
|
|
723
|
+
for (const path of await leftoverTemps(dir)) {
|
|
724
|
+
try {
|
|
725
|
+
if ((await lstat(path)).mtimeMs < cutoff) await unlink(path);
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async function writeCredentials(io, credentials, warn = SILENT) {
|
|
731
|
+
const location = locate(io);
|
|
732
|
+
const { dir } = location;
|
|
733
|
+
const path = join(dir, FILE_NAME);
|
|
734
|
+
const temp = join(dir, `.${FILE_NAME}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
735
|
+
const content = {
|
|
736
|
+
version: credentials.version,
|
|
737
|
+
baseUrl: credentials.baseUrl,
|
|
738
|
+
apiKey: credentials.apiKey,
|
|
739
|
+
keyId: credentials.keyId,
|
|
740
|
+
savedAt: credentials.savedAt
|
|
741
|
+
};
|
|
742
|
+
let tempExists = false;
|
|
743
|
+
try {
|
|
744
|
+
const created = await ensureDir(dir);
|
|
745
|
+
if (!created) {
|
|
746
|
+
await guardDir(io, location, true, warn);
|
|
747
|
+
await sweepStaleTemps(dir);
|
|
748
|
+
}
|
|
749
|
+
const handle = await open(temp, "wx", OWNER_ONLY_FILE);
|
|
750
|
+
tempExists = true;
|
|
751
|
+
try {
|
|
752
|
+
await handle.writeFile(`${JSON.stringify(content, null, 2)}
|
|
753
|
+
`, "utf8");
|
|
754
|
+
} finally {
|
|
755
|
+
await handle.close();
|
|
756
|
+
}
|
|
757
|
+
await rename(temp, path);
|
|
758
|
+
tempExists = false;
|
|
759
|
+
await chmod(path, OWNER_ONLY_FILE);
|
|
760
|
+
return path;
|
|
761
|
+
} catch (error) {
|
|
762
|
+
if (tempExists) await unlink(temp).catch(() => void 0);
|
|
763
|
+
throw new CliError({
|
|
764
|
+
code: "CLI_CREDENTIALS_WRITE_FAILED",
|
|
765
|
+
exitCode: EXIT.SERVER,
|
|
766
|
+
message: `\u51ED\u636E\u6CA1\u80FD\u4FDD\u5B58\u5230 ${path}\uFF1A${systemReason(error)}`
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function unreadable(path) {
|
|
771
|
+
return new CliError({
|
|
772
|
+
code: "CLI_CREDENTIALS_UNREADABLE",
|
|
773
|
+
exitCode: EXIT.SERVER,
|
|
774
|
+
message: `\u51ED\u636E\u6587\u4EF6\u8BFB\u4E0D\u61C2\uFF1A${path}\u3002\u5220\u6389\u5B83\u540E\u91CD\u65B0 buke login`
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
function cannotRead(path, reason) {
|
|
778
|
+
return new CliError({
|
|
779
|
+
code: "CLI_CREDENTIALS_UNREADABLE",
|
|
780
|
+
exitCode: EXIT.SERVER,
|
|
781
|
+
message: `\u51ED\u636E\u6587\u4EF6\u8BFB\u4E0D\u4E86\uFF1A${path}\uFF08${reason}\uFF09\u3002\u5220\u6389\u5B83\u540E\u91CD\u65B0 buke login`
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async function readOwnFile(io, path, warn) {
|
|
785
|
+
const notRegular = "\u4E0D\u662F\u4E00\u4E2A\u666E\u901A\u6587\u4EF6";
|
|
786
|
+
if (!(await lstat(path)).isFile()) throw cannotRead(path, notRegular);
|
|
787
|
+
const handle = await open(path, constants.O_RDONLY | NO_FOLLOW | NON_BLOCKING);
|
|
788
|
+
try {
|
|
789
|
+
const info = await handle.stat();
|
|
790
|
+
if (!info.isFile()) throw cannotRead(path, notRegular);
|
|
791
|
+
if (info.size > MAX_FILE_BYTES) throw unreadable(path);
|
|
792
|
+
if (io.platform !== "win32" && (info.mode & GROUP_OR_OTHERS) !== 0) {
|
|
793
|
+
try {
|
|
794
|
+
await handle.chmod(OWNER_ONLY_FILE);
|
|
795
|
+
warn(`\u51ED\u636E\u6587\u4EF6\u7684\u6743\u9650\u8FC7\u5BBD\uFF0C\u5DF2\u6536\u7D27\u4E3A\u53EA\u6709\u672C\u4EBA\u53EF\u8BFB\u5199\uFF1A${path}`);
|
|
796
|
+
} catch {
|
|
797
|
+
warn(`\u51ED\u636E\u6587\u4EF6\u7684\u6743\u9650\u8FC7\u5BBD\uFF0C\u6CA1\u80FD\u81EA\u52A8\u6536\u7D27\uFF0C\u8BF7\u624B\u52A8 chmod 600 ${path}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const { buffer, bytesRead } = await handle.read(Buffer.alloc(info.size), 0, info.size, 0);
|
|
801
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
802
|
+
} finally {
|
|
803
|
+
await handle.close();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function parseStored(text) {
|
|
807
|
+
let parsed;
|
|
808
|
+
try {
|
|
809
|
+
parsed = JSON.parse(text);
|
|
810
|
+
} catch {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
814
|
+
const { version: version2, baseUrl, apiKey, keyId, savedAt } = parsed;
|
|
815
|
+
if (version2 !== 1) return null;
|
|
816
|
+
if (typeof baseUrl !== "string" || typeof apiKey !== "string") return null;
|
|
817
|
+
if (typeof keyId !== "string" || typeof savedAt !== "string") return null;
|
|
818
|
+
try {
|
|
819
|
+
assertKeyFormat(apiKey);
|
|
820
|
+
return { version: 1, baseUrl: normalizeBaseUrl(baseUrl), apiKey, keyId, savedAt };
|
|
821
|
+
} catch {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
async function readCredentials(io, warn = SILENT) {
|
|
826
|
+
const location = locate(io);
|
|
827
|
+
const path = join(location.dir, FILE_NAME);
|
|
828
|
+
let text;
|
|
829
|
+
try {
|
|
830
|
+
text = await readOwnFile(io, path, warn);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
if (error instanceof CliError) throw error;
|
|
833
|
+
if (isMissing(error)) return null;
|
|
834
|
+
throw cannotRead(path, systemReason(error));
|
|
835
|
+
}
|
|
836
|
+
await guardDir(io, location, false, warn);
|
|
837
|
+
const stored = parseStored(text);
|
|
838
|
+
if (!stored) throw unreadable(path);
|
|
839
|
+
return stored;
|
|
840
|
+
}
|
|
841
|
+
async function deleteCredentials(io, warn = SILENT) {
|
|
842
|
+
const { dir } = locate(io);
|
|
843
|
+
const path = join(dir, FILE_NAME);
|
|
844
|
+
let removed = false;
|
|
845
|
+
try {
|
|
846
|
+
await unlink(path);
|
|
847
|
+
removed = true;
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (!isMissing(error)) {
|
|
850
|
+
throw new CliError({
|
|
851
|
+
code: "CLI_CREDENTIALS_WRITE_FAILED",
|
|
852
|
+
exitCode: EXIT.SERVER,
|
|
853
|
+
message: `\u51ED\u636E\u6587\u4EF6\u6CA1\u80FD\u5220\u9664\uFF1A${path}\uFF08${systemReason(error)}\uFF09`
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
for (const leftover of await leftoverTemps(dir)) {
|
|
858
|
+
try {
|
|
859
|
+
await unlink(leftover);
|
|
860
|
+
removed = true;
|
|
861
|
+
} catch (error) {
|
|
862
|
+
if (!isMissing(error)) warn(`\u8FD8\u6709\u4E00\u4E2A\u6CA1\u5199\u5B8C\u7684\u4E34\u65F6\u6587\u4EF6\u6CA1\u80FD\u5220\u9664\uFF0C\u91CC\u9762\u53EF\u80FD\u6709\u5BC6\u94A5\uFF0C\u8BF7\u624B\u52A8\u5220\u9664\uFF1A${leftover}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return removed;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// src/output.ts
|
|
869
|
+
function errorDocument(error) {
|
|
870
|
+
const document = {
|
|
871
|
+
error: { code: error.code, message: error.message, retryable: error.retryable, traceId: error.traceId }
|
|
872
|
+
};
|
|
873
|
+
if (error.retryAfterSeconds !== void 0) document.error.retryAfterSeconds = error.retryAfterSeconds;
|
|
874
|
+
if (error.httpStatus !== void 0) document.error.httpStatus = error.httpStatus;
|
|
875
|
+
return document;
|
|
876
|
+
}
|
|
877
|
+
function errorLines(error) {
|
|
878
|
+
const details = [`\u9519\u8BEF\u7801 ${singleLine(error.code)}`];
|
|
879
|
+
if (error.traceId) details.push(`\u8FFD\u8E2A\u7F16\u53F7 ${singleLine(error.traceId)}`);
|
|
880
|
+
if (error.retryAfterSeconds !== void 0) details.push(`${error.retryAfterSeconds} \u79D2\u540E\u53EF\u4EE5\u91CD\u8BD5`);
|
|
881
|
+
return [`\u9519\u8BEF\uFF1A${singleLine(error.message)}`, `\uFF08${details.join("\uFF1B")}\uFF09`, ...error.hints];
|
|
882
|
+
}
|
|
883
|
+
function createOutput(streams, json) {
|
|
884
|
+
const secrets = [];
|
|
885
|
+
const scrub = (text, keep) => secrets.reduce(redactSecret, redactKeys(text, keep));
|
|
886
|
+
const toStdout = (text, keep = []) => {
|
|
887
|
+
streams.stdout.write(scrub(text, keep));
|
|
888
|
+
};
|
|
889
|
+
const toStderr = (text) => {
|
|
890
|
+
streams.stderr.write(scrub(text, []));
|
|
891
|
+
};
|
|
892
|
+
let documentWritten = false;
|
|
893
|
+
return {
|
|
894
|
+
json,
|
|
895
|
+
protect(secret) {
|
|
896
|
+
secrets.push(secret);
|
|
897
|
+
},
|
|
898
|
+
result(lines, data, keep = []) {
|
|
899
|
+
if (!json) {
|
|
900
|
+
toStdout(`${lines.join("\n")}
|
|
901
|
+
`, keep);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
toStdout(`${JSON.stringify(data, null, 2)}
|
|
905
|
+
`, keep);
|
|
906
|
+
documentWritten = true;
|
|
907
|
+
},
|
|
908
|
+
notice(line) {
|
|
909
|
+
toStderr(`${line}
|
|
910
|
+
`);
|
|
911
|
+
},
|
|
912
|
+
fail(error) {
|
|
913
|
+
if (json && !documentWritten) {
|
|
914
|
+
toStdout(`${JSON.stringify(errorDocument(error), null, 2)}
|
|
915
|
+
`);
|
|
916
|
+
documentWritten = true;
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
toStderr(`${errorLines(error).join("\n")}
|
|
920
|
+
`);
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function identityDocument(baseUrl, me, displayPrefix) {
|
|
925
|
+
return {
|
|
926
|
+
baseUrl,
|
|
927
|
+
key: {
|
|
928
|
+
id: me.key.id,
|
|
929
|
+
name: me.key.name,
|
|
930
|
+
scopes: me.key.scopes,
|
|
931
|
+
expiresAt: me.key.expiresAt,
|
|
932
|
+
displayPrefix
|
|
933
|
+
},
|
|
934
|
+
account: { id: me.account.id },
|
|
935
|
+
workspace: { id: me.workspace.id, role: me.workspace.role }
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function identityLines(me, displayPrefix) {
|
|
939
|
+
const name2 = singleLine(me.key.name);
|
|
940
|
+
const prefix = singleLine(displayPrefix);
|
|
941
|
+
const scopes = me.key.scopes.map(singleLine).filter((scope) => scope !== "");
|
|
942
|
+
return [
|
|
943
|
+
name2 === "" ? `\u5BC6\u94A5\uFF1A${prefix}` : `\u5BC6\u94A5\uFF1A${name2}\uFF08${prefix}\uFF09`,
|
|
944
|
+
`\u5236\u7247\u7A7A\u95F4\uFF1A${singleLine(me.workspace.id)}\uFF08${singleLine(me.workspace.role)}\uFF09`,
|
|
945
|
+
`\u6743\u9650\uFF1A${scopes.length > 0 ? scopes.join("\u3001") : "\u65E0"}`,
|
|
946
|
+
`\u6709\u6548\u671F\uFF1A${me.key.expiresAt === null ? "\u957F\u671F\u6709\u6548" : `\u5230 ${singleLine(me.key.expiresAt)} \u4E3A\u6B62`}`
|
|
947
|
+
];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/commands/login.ts
|
|
951
|
+
async function login({ io, out, baseUrl: flag }) {
|
|
952
|
+
const explicit = explicitBaseUrl(flag, io.env);
|
|
953
|
+
const baseUrl = explicit ?? DEFAULT_BASE_URL;
|
|
954
|
+
credentialsPath(io);
|
|
955
|
+
assertReachedDirectly(baseUrl, io.env);
|
|
956
|
+
const fromEnv = flag === void 0 && explicit !== null;
|
|
957
|
+
const origin = flag !== void 0 ? "\uFF08\u5730\u5740\u6765\u81EA --base-url\uFF09" : fromEnv ? "\uFF08\u5730\u5740\u6765\u81EA\u73AF\u5883\u53D8\u91CF BUKE_BASE_URL\uFF09" : "";
|
|
958
|
+
if (willPromptForKey(io) || fromEnv && baseUrl !== DEFAULT_BASE_URL) {
|
|
959
|
+
out.notice(`\u5C06\u767B\u5F55 ${baseUrl}${origin}`);
|
|
960
|
+
}
|
|
961
|
+
const { key, source } = await readNewKey(io);
|
|
962
|
+
out.protect(key);
|
|
963
|
+
const me = await getMe(baseUrl, key, io);
|
|
964
|
+
const savedTo = await writeCredentials(
|
|
965
|
+
io,
|
|
966
|
+
{
|
|
967
|
+
version: 1,
|
|
968
|
+
baseUrl,
|
|
969
|
+
apiKey: key,
|
|
970
|
+
keyId: me.key.id,
|
|
971
|
+
savedAt: (io.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
972
|
+
},
|
|
973
|
+
(line) => out.notice(line)
|
|
974
|
+
);
|
|
975
|
+
const displayPrefix = displayPrefixOf(key, me.key.id);
|
|
976
|
+
const savedLine = io.platform === "win32" ? `\u51ED\u636E\u4FDD\u5B58\u5728 ${savedTo}` : `\u51ED\u636E\u4FDD\u5B58\u5728 ${savedTo}\uFF08\u53EA\u6709\u672C\u4EBA\u53EF\u8BFB\u5199\uFF09`;
|
|
977
|
+
out.result(
|
|
978
|
+
[`\u5DF2\u767B\u5F55 ${baseUrl}`, ...identityLines(me, displayPrefix), savedLine],
|
|
979
|
+
{ ...identityDocument(baseUrl, me, displayPrefix), credentialsPath: savedTo },
|
|
980
|
+
[displayPrefix]
|
|
981
|
+
);
|
|
982
|
+
if (source === "env") {
|
|
983
|
+
out.notice("\u8FD9\u628A\u5BC6\u94A5\u6765\u81EA\u73AF\u5883\u53D8\u91CF BUKE_API_KEY\uFF1B\u5B83\u5DF2\u4FDD\u5B58\u5230\u672C\u673A\uFF0C\u4E4B\u540E\u4E0D\u8BBE\u8FD9\u4E2A\u53D8\u91CF\u4E5F\u80FD\u7528");
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/commands/logout.ts
|
|
988
|
+
async function logout({ io, out }) {
|
|
989
|
+
const removed = await deleteCredentials(io, (line) => out.notice(line));
|
|
990
|
+
out.result(
|
|
991
|
+
[removed ? "\u5DF2\u9000\u51FA\u767B\u5F55\uFF0C\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5\u5DF2\u5220\u9664\u3002\u8FD9\u628A\u5BC6\u94A5\u672C\u8EAB\u4ECD\u7136\u6709\u6548\uFF0C\u8981\u4F5C\u5E9F\u8BF7\u540A\u9500\u5B83" : "\u672C\u673A\u6CA1\u6709\u4FDD\u5B58\u7684\u5BC6\u94A5"],
|
|
992
|
+
{ removed, credentialsPath: credentialsPath(io) }
|
|
993
|
+
);
|
|
994
|
+
if (keyFromEnv(io.env) !== null) {
|
|
995
|
+
out.notice("\u73AF\u5883\u53D8\u91CF BUKE_API_KEY \u8FD8\u8BBE\u7740\uFF0C\u4E4B\u540E\u7684\u547D\u4EE4\u4ECD\u4F1A\u7528\u5B83");
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/commands/whoami.ts
|
|
1000
|
+
async function resolve({ io, out, baseUrl: flag }) {
|
|
1001
|
+
const explicit = explicitBaseUrl(flag, io.env);
|
|
1002
|
+
const fromEnv = keyFromEnv(io.env);
|
|
1003
|
+
if (fromEnv !== null) {
|
|
1004
|
+
assertKeyFormat(fromEnv);
|
|
1005
|
+
out.protect(fromEnv);
|
|
1006
|
+
return { apiKey: fromEnv, baseUrl: explicit ?? DEFAULT_BASE_URL, source: "env" };
|
|
1007
|
+
}
|
|
1008
|
+
const stored = await readCredentials(io, (line) => out.notice(line));
|
|
1009
|
+
if (!stored) {
|
|
1010
|
+
throw new CliError({
|
|
1011
|
+
code: "CLI_NOT_LOGGED_IN",
|
|
1012
|
+
exitCode: EXIT.AUTH,
|
|
1013
|
+
message: "\u8FD8\u6CA1\u6709\u767B\u5F55\u3002\u5148\u8FD0\u884C buke login\uFF0C\u6216\u8005\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF BUKE_API_KEY"
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
out.protect(stored.apiKey);
|
|
1017
|
+
if (explicit !== null && explicit !== stored.baseUrl) {
|
|
1018
|
+
throw new CliError({
|
|
1019
|
+
code: "CLI_BASE_URL_MISMATCH",
|
|
1020
|
+
exitCode: EXIT.USAGE,
|
|
1021
|
+
message: `\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5\u662F\u7ED9 ${stored.baseUrl} \u7528\u7684\uFF0C\u4E0D\u4F1A\u53D1\u5230 ${explicit}\u3002\u8981\u6362\u5730\u5740\u8BF7\u91CD\u65B0\u8FD0\u884C buke login --base-url ${explicit}`
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
return { apiKey: stored.apiKey, baseUrl: stored.baseUrl, source: "file", credentialsPath: credentialsPath(io) };
|
|
1025
|
+
}
|
|
1026
|
+
async function whoami(context) {
|
|
1027
|
+
const { io, out } = context;
|
|
1028
|
+
const resolved = await resolve(context);
|
|
1029
|
+
const { apiKey, baseUrl } = resolved;
|
|
1030
|
+
let me;
|
|
1031
|
+
try {
|
|
1032
|
+
me = await getMe(baseUrl, apiKey, io);
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (error instanceof CliError && error.httpStatus === 401) {
|
|
1035
|
+
throw error.withHint(
|
|
1036
|
+
resolved.source === "env" ? "\u8FD9\u628A\u5BC6\u94A5\u6765\u81EA\u73AF\u5883\u53D8\u91CF BUKE_API_KEY\uFF1A\u6362\u4E86\u65B0\u5BC6\u94A5\u7684\u8BDD\uFF0C\u8981\u6539\u7684\u662F\u8FD9\u4E2A\u53D8\u91CF" : "\u5982\u679C\u6362\u4E86\u65B0\u5BC6\u94A5\uFF0C\u91CD\u65B0\u8FD0\u884C buke login"
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
throw error;
|
|
1040
|
+
}
|
|
1041
|
+
const displayPrefix = displayPrefixOf(apiKey, me.key.id);
|
|
1042
|
+
const identity = identityDocument(baseUrl, me, displayPrefix);
|
|
1043
|
+
out.result(
|
|
1044
|
+
[
|
|
1045
|
+
`\u5F53\u524D\u767B\u5F55 ${baseUrl}`,
|
|
1046
|
+
...identityLines(me, displayPrefix),
|
|
1047
|
+
resolved.source === "env" ? "\u5BC6\u94A5\u6765\u81EA\uFF1A\u73AF\u5883\u53D8\u91CF BUKE_API_KEY" : `\u5BC6\u94A5\u6765\u81EA\uFF1A${resolved.credentialsPath}`
|
|
1048
|
+
],
|
|
1049
|
+
resolved.source === "env" ? { ...identity, source: resolved.source } : { ...identity, source: resolved.source, credentialsPath: resolved.credentialsPath },
|
|
1050
|
+
[displayPrefix]
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/cli.ts
|
|
1055
|
+
var KEY_HOWTO = [
|
|
1056
|
+
"\u5BC6\u94A5\u600E\u4E48\u7ED9\uFF08\u4E0D\u8981\u5199\u5728\u547D\u4EE4\u884C\u91CC\uFF09\uFF0C\u6309\u987A\u5E8F\u53D6\u7B2C\u4E00\u79CD\uFF1A",
|
|
1057
|
+
" 1. \u73AF\u5883\u53D8\u91CF BUKE_API_KEY",
|
|
1058
|
+
` 2. \u7BA1\u9053\uFF1Aprintf '%s' "$KEY" | buke login`,
|
|
1059
|
+
" 3. \u5728\u7EC8\u7AEF\u91CC\u76F4\u63A5\u8FD0\u884C buke login\uFF0C\u6309\u63D0\u793A\u7C98\u8D34\uFF08\u8F93\u5165\u4E0D\u4F1A\u663E\u793A\uFF09"
|
|
1060
|
+
];
|
|
1061
|
+
var COMMANDS = {
|
|
1062
|
+
login: {
|
|
1063
|
+
run: login,
|
|
1064
|
+
acceptsBaseUrl: true,
|
|
1065
|
+
help: [
|
|
1066
|
+
"\u7528\u6CD5\uFF1Abuke login [--base-url <\u5730\u5740>] [--json]",
|
|
1067
|
+
"",
|
|
1068
|
+
"\u7528\u4E00\u628A API \u5BC6\u94A5\u767B\u5F55\uFF1A\u5148\u5411\u670D\u52A1\u7AEF\u786E\u8BA4\u8FD9\u628A\u5BC6\u94A5\u53EF\u7528\uFF0C\u518D\u628A\u5B83\u4FDD\u5B58\u5728\u672C\u673A\uFF0C\u4E4B\u540E\u7684\u547D\u4EE4\u4E0D\u7528\u518D\u7ED9\u3002",
|
|
1069
|
+
"\u767B\u5F55\u6CA1\u6210\u529F\u65F6\uFF0C\u672C\u673A\u539F\u6765\u4FDD\u5B58\u7684\u5BC6\u94A5\u4E0D\u53D7\u5F71\u54CD\u3002",
|
|
1070
|
+
"",
|
|
1071
|
+
...KEY_HOWTO,
|
|
1072
|
+
"",
|
|
1073
|
+
"\u9009\u9879\uFF1A",
|
|
1074
|
+
` --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF0C\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\uFF1B\u53EA\u5199\u5230\u57DF\u540D\u4E3A\u6B62`,
|
|
1075
|
+
" --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
|
|
1076
|
+
].join("\n")
|
|
1077
|
+
},
|
|
1078
|
+
whoami: {
|
|
1079
|
+
run: whoami,
|
|
1080
|
+
acceptsBaseUrl: true,
|
|
1081
|
+
help: [
|
|
1082
|
+
"\u7528\u6CD5\uFF1Abuke whoami [--base-url <\u5730\u5740>] [--json]",
|
|
1083
|
+
"",
|
|
1084
|
+
"\u5411\u670D\u52A1\u7AEF\u786E\u8BA4\u5F53\u524D\u7528\u7684\u662F\u54EA\u628A\u5BC6\u94A5\uFF1A\u5B83\u5C5E\u4E8E\u54EA\u4E2A\u5236\u7247\u7A7A\u95F4\u3001\u6709\u54EA\u4E9B\u6743\u9650\u3001\u4EC0\u4E48\u65F6\u5019\u5230\u671F\u3002",
|
|
1085
|
+
"\u8BBE\u4E86\u73AF\u5883\u53D8\u91CF BUKE_API_KEY \u65F6\u7528\u5B83\uFF0C\u5426\u5219\u7528 buke login \u4FDD\u5B58\u5728\u672C\u673A\u7684\u90A3\u4E00\u628A\u3002",
|
|
1086
|
+
"\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5\u53EA\u4F1A\u53D1\u5F80\u767B\u5F55\u65F6\u7684\u90A3\u4E2A\u5730\u5740\u3002",
|
|
1087
|
+
"",
|
|
1088
|
+
"\u9009\u9879\uFF1A",
|
|
1089
|
+
" --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF1B\u4E0D\u7ED9\u65F6\u7528\u767B\u5F55\u65F6\u7684\u5730\u5740",
|
|
1090
|
+
" --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
|
|
1091
|
+
].join("\n")
|
|
1092
|
+
},
|
|
1093
|
+
logout: {
|
|
1094
|
+
run: logout,
|
|
1095
|
+
acceptsBaseUrl: false,
|
|
1096
|
+
help: [
|
|
1097
|
+
"\u7528\u6CD5\uFF1Abuke logout [--json]",
|
|
1098
|
+
"",
|
|
1099
|
+
"\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5\u3002\u8FD9\u628A\u5BC6\u94A5\u672C\u8EAB\u4ECD\u7136\u6709\u6548\uFF0C\u8981\u4F5C\u5E9F\u8BF7\u540A\u9500\u5B83\u3002",
|
|
1100
|
+
"",
|
|
1101
|
+
"\u9009\u9879\uFF1A",
|
|
1102
|
+
" --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6"
|
|
1103
|
+
].join("\n")
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
var GENERAL_HELP = [
|
|
1107
|
+
"buke\uFF1A\u300A\u4E0D\u53EF\u601D\u8BAE\u300B\u5F00\u653E\u63A5\u53E3\u7684\u547D\u4EE4\u884C\u5BA2\u6237\u7AEF",
|
|
1108
|
+
"",
|
|
1109
|
+
"\u7528\u6CD5\uFF1A",
|
|
1110
|
+
" buke login [--base-url <\u5730\u5740>] [--json] \u7528\u4E00\u628A API \u5BC6\u94A5\u767B\u5F55\uFF0C\u5E76\u628A\u5B83\u4FDD\u5B58\u5728\u672C\u673A",
|
|
1111
|
+
" buke whoami [--base-url <\u5730\u5740>] [--json] \u67E5\u770B\u5F53\u524D\u7528\u7684\u662F\u54EA\u628A\u5BC6\u94A5\u3001\u5C5E\u4E8E\u54EA\u4E2A\u5236\u7247\u7A7A\u95F4",
|
|
1112
|
+
" buke logout [--json] \u9000\u51FA\u767B\u5F55\uFF0C\u5220\u9664\u672C\u673A\u4FDD\u5B58\u7684\u5BC6\u94A5",
|
|
1113
|
+
" buke --help | -h \u663E\u793A\u8FD9\u4EFD\u8BF4\u660E\uFF1Bbuke <\u547D\u4EE4> --help \u770B\u5355\u6761\u547D\u4EE4",
|
|
1114
|
+
" buke --version | -v \u663E\u793A\u7248\u672C\u53F7",
|
|
1115
|
+
"",
|
|
1116
|
+
"\u9009\u9879\uFF1A",
|
|
1117
|
+
` --base-url <\u5730\u5740> \u670D\u52A1\u5730\u5740\uFF0C\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\uFF1B\u53EA\u5199\u5230\u57DF\u540D\u4E3A\u6B62`,
|
|
1118
|
+
" --json \u53EA\u5728\u6807\u51C6\u8F93\u51FA\u6253\u5370\u4E00\u4E2A JSON \u6587\u6863\uFF0C\u65B9\u4FBF\u811A\u672C\u8BFB\u53D6",
|
|
1119
|
+
" -h, --help \u663E\u793A\u8BF4\u660E",
|
|
1120
|
+
" -v, --version \u663E\u793A\u7248\u672C\u53F7",
|
|
1121
|
+
"",
|
|
1122
|
+
...KEY_HOWTO,
|
|
1123
|
+
"",
|
|
1124
|
+
"\u73AF\u5883\u53D8\u91CF\uFF1A",
|
|
1125
|
+
" BUKE_API_KEY API \u5BC6\u94A5\uFF1B\u8BBE\u4E86\u5B83\uFF0Clogin \u548C whoami \u90FD\u76F4\u63A5\u7528\u5B83",
|
|
1126
|
+
" BUKE_BASE_URL \u670D\u52A1\u5730\u5740\uFF0C\u4F5C\u7528\u540C --base-url",
|
|
1127
|
+
" BUKE_CONFIG_DIR \u4FDD\u5B58\u51ED\u636E\u7684\u76EE\u5F55\uFF0C\u8981\u5199\u7EDD\u5BF9\u8DEF\u5F84\u3002\u4E0D\u8BBE\u65F6\u662F $XDG_CONFIG_HOME/buke\uFF1B",
|
|
1128
|
+
" \u8FD9\u4E2A\u4E5F\u6CA1\u8BBE\u5C31\u662F ~/.config/buke\uFF08Windows \u4E0A\u662F %APPDATA%\\buke\uFF09",
|
|
1129
|
+
"",
|
|
1130
|
+
"\u9000\u51FA\u7801\uFF1A",
|
|
1131
|
+
" 0 \u6210\u529F",
|
|
1132
|
+
" 2 \u53C2\u6570\u6216\u7528\u6CD5\u4E0D\u5BF9",
|
|
1133
|
+
" 3 \u6CA1\u6709\u767B\u5F55\uFF0C\u6216\u8005\u5BC6\u94A5\u65E0\u6548\u3001\u5DF2\u540A\u9500\u3001\u5DF2\u8FC7\u671F\u3001\u6CA1\u6709\u6743\u9650",
|
|
1134
|
+
" 4 \u4F59\u989D\u6216\u989D\u5EA6\u4E0D\u591F",
|
|
1135
|
+
" 5 \u670D\u52A1\u7AEF\u6216\u7F51\u7EDC\u51FA\u9519\uFF1B\u672C\u673A\u7684\u51ED\u636E\u6587\u4EF6\u8BFB\u4E0D\u4E86\u3001\u5199\u4E0D\u4E86\u4E5F\u662F\u8FD9\u4E2A\u7801",
|
|
1136
|
+
" 6 \u62A5\u4EF7\u53D8\u4E86",
|
|
1137
|
+
" 7 \u8BF7\u6C42\u592A\u9891\u7E41\uFF0C\u7A0D\u540E\u91CD\u8BD5",
|
|
1138
|
+
" 8 \u7B49\u5F85\u8D85\u65F6",
|
|
1139
|
+
" 9 \u4EFB\u52A1\u5931\u8D25",
|
|
1140
|
+
" 10 \u8981\u627E\u7684\u4E1C\u897F\u4E0D\u5B58\u5728",
|
|
1141
|
+
" 130 \u88AB Ctrl-C \u6253\u65AD",
|
|
1142
|
+
"",
|
|
1143
|
+
"\u5F53\u524D\u7248\u672C\u53EA\u6709\u767B\u5F55\u76F8\u5173\u7684\u547D\u4EE4\uFF1B\u4E0B\u5355\u3001\u67E5\u8FDB\u5EA6\u3001\u4E0B\u8F7D\u7B49\u547D\u4EE4\u968F\u5F00\u653E\u63A5\u53E3\u4E0A\u7EBF\u540E\u63D0\u4F9B\u3002"
|
|
1144
|
+
].join("\n");
|
|
1145
|
+
var MAX_ECHO_LENGTH = 40;
|
|
1146
|
+
function echo(item) {
|
|
1147
|
+
return item.length > MAX_ECHO_LENGTH ? `${item.slice(0, MAX_ECHO_LENGTH)}\u2026` : item;
|
|
1148
|
+
}
|
|
1149
|
+
function parse(argv) {
|
|
1150
|
+
try {
|
|
1151
|
+
const { values, positionals } = parseArgs({
|
|
1152
|
+
args: argv,
|
|
1153
|
+
options: {
|
|
1154
|
+
help: { type: "boolean", short: "h" },
|
|
1155
|
+
version: { type: "boolean", short: "v" },
|
|
1156
|
+
json: { type: "boolean" },
|
|
1157
|
+
"base-url": { type: "string" }
|
|
1158
|
+
},
|
|
1159
|
+
strict: true,
|
|
1160
|
+
allowPositionals: true
|
|
1161
|
+
});
|
|
1162
|
+
return {
|
|
1163
|
+
command: positionals[0],
|
|
1164
|
+
extraPositionals: Math.max(0, positionals.length - 1),
|
|
1165
|
+
help: values.help === true,
|
|
1166
|
+
version: values.version === true,
|
|
1167
|
+
baseUrl: values["base-url"]
|
|
1168
|
+
};
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
throw usageFromParseError(error);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function usageFromParseError(error) {
|
|
1174
|
+
const detail = error;
|
|
1175
|
+
const option = typeof detail.message === "string" ? /'(-{1,2}[A-Za-z0-9-]+)/.exec(detail.message)?.[1] : void 0;
|
|
1176
|
+
if (detail.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
1177
|
+
return usageError(option ? `\u4E0D\u8BA4\u8BC6\u7684\u9009\u9879 ${echo(option)}` : "\u6709\u4E0D\u8BA4\u8BC6\u7684\u9009\u9879");
|
|
1178
|
+
}
|
|
1179
|
+
if (detail.code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE") {
|
|
1180
|
+
if (option === "--base-url") return usageError(`--base-url \u540E\u9762\u8981\u8DDF\u4E00\u4E2A\u5730\u5740\uFF0C\u4F8B\u5982 --base-url ${DEFAULT_BASE_URL}`);
|
|
1181
|
+
return usageError(option ? `\u9009\u9879 ${echo(option)} \u540E\u9762\u4E0D\u5E26\u503C` : "\u6709\u9009\u9879\u7684\u5199\u6CD5\u4E0D\u5BF9");
|
|
1182
|
+
}
|
|
1183
|
+
return usageError("\u53C2\u6570\u7684\u5199\u6CD5\u4E0D\u5BF9");
|
|
1184
|
+
}
|
|
1185
|
+
async function dispatch(argv, io, out) {
|
|
1186
|
+
if (argvContainsKey(argv)) {
|
|
1187
|
+
throw new CliError({
|
|
1188
|
+
code: "CLI_KEY_IN_ARGV",
|
|
1189
|
+
exitCode: EXIT.USAGE,
|
|
1190
|
+
message: "\u5BC6\u94A5\u4E0D\u8981\u5199\u5728\u547D\u4EE4\u884C\u91CC\uFF08\u4F1A\u7559\u5728 shell \u5386\u53F2\u548C\u8FDB\u7A0B\u5217\u8868\u91CC\uFF09\u3002\u8BF7\u7528\u73AF\u5883\u53D8\u91CF BUKE_API_KEY\uFF0C\u6216\u8005\u901A\u8FC7\u7BA1\u9053\u4F20\u7ED9 buke login\u3002\u521A\u624D\u8FD9\u628A\u5982\u679C\u662F\u771F\u7684\uFF0C\u5EFA\u8BAE\u540A\u9500\u540E\u91CD\u5EFA\u3002"
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
const parsed = parse(argv);
|
|
1194
|
+
const command = parsed.command !== void 0 && Object.hasOwn(COMMANDS, parsed.command) ? COMMANDS[parsed.command] : void 0;
|
|
1195
|
+
if (parsed.command !== void 0 && !command) throw usageError(`\u4E0D\u8BA4\u8BC6\u7684\u547D\u4EE4 ${echo(parsed.command)}`);
|
|
1196
|
+
if (parsed.extraPositionals > 0) throw usageError(`${parsed.command} \u540E\u9762\u4E0D\u63A5\u522B\u7684\u53C2\u6570`);
|
|
1197
|
+
if (command && !command.acceptsBaseUrl && parsed.baseUrl !== void 0) {
|
|
1198
|
+
throw usageError(`${parsed.command} \u4E0D\u8BA4\u8BC6\u9009\u9879 --base-url`);
|
|
1199
|
+
}
|
|
1200
|
+
if (parsed.help || !command && !parsed.version) {
|
|
1201
|
+
const text = command ? command.help : GENERAL_HELP;
|
|
1202
|
+
out.result([text], { help: text });
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (parsed.version) {
|
|
1206
|
+
out.result([`${PACKAGE_NAME}/${VERSION}`], { name: PACKAGE_NAME, version: VERSION });
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (command) await command.run({ io, out, baseUrl: parsed.baseUrl });
|
|
1210
|
+
}
|
|
1211
|
+
async function run(argv, io) {
|
|
1212
|
+
let out;
|
|
1213
|
+
try {
|
|
1214
|
+
out = createOutput(io, argv.includes("--json"));
|
|
1215
|
+
await dispatch(argv, io, out);
|
|
1216
|
+
return EXIT.OK;
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
const failure = toCliError(error);
|
|
1219
|
+
try {
|
|
1220
|
+
out?.fail(failure);
|
|
1221
|
+
} catch {
|
|
1222
|
+
}
|
|
1223
|
+
return failure.exitCode;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/main.ts
|
|
1228
|
+
var SIGNAL_NUMBERS = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };
|
|
1229
|
+
var outputLost = false;
|
|
1230
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
1231
|
+
stream.on("error", (error) => {
|
|
1232
|
+
if (error.code === "EPIPE" || outputLost) return;
|
|
1233
|
+
outputLost = true;
|
|
1234
|
+
if (stream === process.stdout) process.stderr.write(`\u9519\u8BEF\uFF1A\u7ED3\u679C\u6CA1\u80FD\u5199\u5230\u6807\u51C6\u8F93\u51FA\uFF08${error.code ?? "\u539F\u56E0\u4E0D\u660E"}\uFF09
|
|
1235
|
+
`);
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
var interrupt = new AbortController();
|
|
1239
|
+
var received;
|
|
1240
|
+
for (const name2 of Object.keys(SIGNAL_NUMBERS)) {
|
|
1241
|
+
process.on(name2, () => {
|
|
1242
|
+
if (received !== void 0) process.exit(128 + SIGNAL_NUMBERS[name2]);
|
|
1243
|
+
received = name2;
|
|
1244
|
+
interrupt.abort();
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
var env = process.execArgv.includes("--use-env-proxy") ? { ...process.env, NODE_USE_ENV_PROXY: "1" } : process.env;
|
|
1248
|
+
var code = await run(process.argv.slice(2), {
|
|
1249
|
+
env,
|
|
1250
|
+
stdin: process.stdin,
|
|
1251
|
+
stdout: process.stdout,
|
|
1252
|
+
stderr: process.stderr,
|
|
1253
|
+
fetch: globalThis.fetch,
|
|
1254
|
+
homedir,
|
|
1255
|
+
platform: process.platform,
|
|
1256
|
+
signal: interrupt.signal
|
|
1257
|
+
});
|
|
1258
|
+
function finalCode() {
|
|
1259
|
+
if (received !== void 0 && code === EXIT.INTERRUPTED) return 128 + SIGNAL_NUMBERS[received];
|
|
1260
|
+
return code === EXIT.OK && outputLost ? EXIT.SERVER : code;
|
|
1261
|
+
}
|
|
1262
|
+
process.exitCode = finalCode();
|
|
1263
|
+
var unflushed = 2;
|
|
1264
|
+
var leave = () => {
|
|
1265
|
+
unflushed -= 1;
|
|
1266
|
+
if (unflushed === 0) process.exit(finalCode());
|
|
1267
|
+
};
|
|
1268
|
+
process.stdout.write("", leave);
|
|
1269
|
+
process.stderr.write("", leave);
|