rshow 0.0.2 → 0.0.4
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/dist/main.js +943 -523
- package/package.json +1 -1
- package/skills/rshow/SKILL.md +1 -1
- package/skills/rshow/reference/cli.md +4 -2
package/dist/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import path25 from "node:path";
|
|
5
|
+
import process23 from "node:process";
|
|
6
6
|
|
|
7
7
|
// src/binary.ts
|
|
8
8
|
import { mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -13,7 +13,7 @@ import process from "node:process";
|
|
|
13
13
|
// src/version.ts
|
|
14
14
|
import { readFileSync } from "node:fs";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
var VERSION = true ? "0.0.
|
|
16
|
+
var VERSION = true ? "0.0.4" : versionFromPackage();
|
|
17
17
|
|
|
18
18
|
// src/binary.ts
|
|
19
19
|
var ASSET_MANIFEST = "assets.json";
|
|
@@ -72,9 +72,9 @@ async function extractAssets(bundle, dir) {
|
|
|
72
72
|
function safeKey(key2) {
|
|
73
73
|
return key2 !== "" && !path.isAbsolute(key2) && !key2.split("/").includes("..");
|
|
74
74
|
}
|
|
75
|
-
function manifestKeys(
|
|
75
|
+
function manifestKeys(read4) {
|
|
76
76
|
try {
|
|
77
|
-
const doc = JSON.parse(new TextDecoder().decode(
|
|
77
|
+
const doc = JSON.parse(new TextDecoder().decode(read4(ASSET_MANIFEST)));
|
|
78
78
|
if (doc !== null && typeof doc === "object" && Array.isArray(doc.keys)) {
|
|
79
79
|
const keys = doc.keys.filter((key2) => typeof key2 === "string");
|
|
80
80
|
if (keys.length > 0) return keys.filter(safeKey);
|
|
@@ -85,9 +85,9 @@ function manifestKeys(read2) {
|
|
|
85
85
|
}
|
|
86
86
|
function bundleOf(mod) {
|
|
87
87
|
const raw = mod.getRawAsset;
|
|
88
|
-
const
|
|
89
|
-
const withManifest = manifestKeys(
|
|
90
|
-
return { keys: withManifest.length > 0 ? withManifest : (mod.getAssetKeys?.() ?? []).filter(safeKey), read:
|
|
88
|
+
const read4 = (key2) => new Uint8Array(raw(key2));
|
|
89
|
+
const withManifest = manifestKeys(read4);
|
|
90
|
+
return { keys: withManifest.length > 0 ? withManifest : (mod.getAssetKeys?.() ?? []).filter(safeKey), read: read4 };
|
|
91
91
|
}
|
|
92
92
|
async function unpack(env) {
|
|
93
93
|
if (!await isBinary()) return void 0;
|
|
@@ -148,10 +148,145 @@ var CheckError = class extends Error {
|
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
-
// src/
|
|
151
|
+
// src/network.ts
|
|
152
|
+
import { readFile } from "node:fs/promises";
|
|
153
|
+
import process3 from "node:process";
|
|
154
|
+
import tls from "node:tls";
|
|
155
|
+
|
|
156
|
+
// src/warn.ts
|
|
157
|
+
import process2 from "node:process";
|
|
158
|
+
function warn(message) {
|
|
159
|
+
process2.stderr.write(`rshow: warning: ${message}
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/network.ts
|
|
164
|
+
var PROXY_VARIABLES = ["https_proxy", "HTTPS_PROXY", "http_proxy", "HTTP_PROXY"];
|
|
165
|
+
var TRUST_HINT = "your network may inspect HTTPS: set RSHOW_EXTRA_CA_FILE to its root certificate, or check that the system store has it";
|
|
166
|
+
var PROXY_HINT = "if a proxy stands between you and that host, set https_proxy and run with NODE_USE_ENV_PROXY=1";
|
|
167
|
+
var DNS_HINT = "check the url in rshow config and your DNS";
|
|
168
|
+
var TRUSTED = { reason: "certificate not trusted", hint: TRUST_HINT };
|
|
169
|
+
var REASONS = {
|
|
170
|
+
UNABLE_TO_GET_ISSUER_CERT_LOCALLY: TRUSTED,
|
|
171
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: TRUSTED,
|
|
172
|
+
SELF_SIGNED_CERT_IN_CHAIN: TRUSTED,
|
|
173
|
+
DEPTH_ZERO_SELF_SIGNED_CERT: TRUSTED,
|
|
174
|
+
CERT_HAS_EXPIRED: TRUSTED,
|
|
175
|
+
ERR_TLS_CERT_ALTNAME_INVALID: TRUSTED,
|
|
176
|
+
ENOTFOUND: { reason: "host not found", hint: DNS_HINT },
|
|
177
|
+
EAI_AGAIN: { reason: "host not found", hint: DNS_HINT },
|
|
178
|
+
ECONNREFUSED: { reason: "connection refused" },
|
|
179
|
+
ETIMEDOUT: { reason: "connection timed out", proxyHint: true },
|
|
180
|
+
UND_ERR_CONNECT_TIMEOUT: { reason: "connection timed out", proxyHint: true },
|
|
181
|
+
ECONNRESET: { reason: "connection reset", proxyHint: true }
|
|
182
|
+
};
|
|
152
183
|
function messageOf(err) {
|
|
153
184
|
return err instanceof Error ? err.message : String(err);
|
|
154
185
|
}
|
|
186
|
+
function pemCertificatesOf(text) {
|
|
187
|
+
return text.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g) ?? [];
|
|
188
|
+
}
|
|
189
|
+
async function extraCertificates(env) {
|
|
190
|
+
const file = (env.RSHOW_EXTRA_CA_FILE ?? "").trim();
|
|
191
|
+
if (file === "") return [];
|
|
192
|
+
let text;
|
|
193
|
+
try {
|
|
194
|
+
text = await readFile(file, "utf8");
|
|
195
|
+
} catch (err) {
|
|
196
|
+
warn(`cannot read RSHOW_EXTRA_CA_FILE ${file}: ${messageOf(err)}`);
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const certs = pemCertificatesOf(text);
|
|
200
|
+
if (certs.length === 0) warn(`RSHOW_EXTRA_CA_FILE ${file} holds no PEM certificate`);
|
|
201
|
+
return certs;
|
|
202
|
+
}
|
|
203
|
+
async function installCaCertificates(env = process3.env) {
|
|
204
|
+
const extra = await extraCertificates(env);
|
|
205
|
+
const api = tls;
|
|
206
|
+
if (typeof api.getCACertificates !== "function" || typeof api.setDefaultCACertificates !== "function") return;
|
|
207
|
+
try {
|
|
208
|
+
const certs = [...api.getCACertificates("default")];
|
|
209
|
+
if (env.RSHOW_SYSTEM_CA !== "0") certs.push(...api.getCACertificates("system"));
|
|
210
|
+
certs.push(...extra);
|
|
211
|
+
api.setDefaultCACertificates([...new Set(certs)]);
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function proxyVariableOf(env) {
|
|
216
|
+
return PROXY_VARIABLES.find((name) => (env[name] ?? "").trim() !== "");
|
|
217
|
+
}
|
|
218
|
+
function envProxyEnabled(env) {
|
|
219
|
+
const flag = (env.NODE_USE_ENV_PROXY ?? "").trim();
|
|
220
|
+
if (flag !== "" && flag !== "0") return true;
|
|
221
|
+
return process3.execArgv.includes("--use-env-proxy");
|
|
222
|
+
}
|
|
223
|
+
var proxyNoticed = false;
|
|
224
|
+
function noticeProxy(env) {
|
|
225
|
+
if (proxyNoticed) return;
|
|
226
|
+
proxyNoticed = true;
|
|
227
|
+
const name = proxyVariableOf(env);
|
|
228
|
+
if (name === void 0 || envProxyEnabled(env)) return;
|
|
229
|
+
warn(`${name} is set; run with NODE_USE_ENV_PROXY=1 (Node 24+) so rshow uses it`);
|
|
230
|
+
}
|
|
231
|
+
var installed;
|
|
232
|
+
function readyNetwork(env = process3.env) {
|
|
233
|
+
installed ??= installCaCertificates(env);
|
|
234
|
+
noticeProxy(env);
|
|
235
|
+
return installed;
|
|
236
|
+
}
|
|
237
|
+
function codesOf(err, depth = 0) {
|
|
238
|
+
if (depth > 5 || err === null || typeof err !== "object") return [];
|
|
239
|
+
const out = [];
|
|
240
|
+
const code = err.code;
|
|
241
|
+
if (typeof code === "string" && code !== "") out.push(code);
|
|
242
|
+
const inner = err.errors;
|
|
243
|
+
if (Array.isArray(inner)) for (const one of inner) out.push(...codesOf(one, depth + 1));
|
|
244
|
+
out.push(...codesOf(err.cause, depth + 1));
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
function deepestMessageOf(err, depth = 0) {
|
|
248
|
+
if (depth > 5 || err === null || typeof err !== "object") return "";
|
|
249
|
+
const cause = deepestMessageOf(err.cause, depth + 1);
|
|
250
|
+
if (cause !== "") return cause;
|
|
251
|
+
const message = messageOf(err);
|
|
252
|
+
return message === "" ? "" : message;
|
|
253
|
+
}
|
|
254
|
+
function hostOf(url) {
|
|
255
|
+
try {
|
|
256
|
+
return new URL(url).host;
|
|
257
|
+
} catch {
|
|
258
|
+
return url;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function faultOf(err, env = process3.env) {
|
|
262
|
+
const codes = codesOf(err).reverse();
|
|
263
|
+
for (const code of codes) {
|
|
264
|
+
const known = REASONS[code];
|
|
265
|
+
if (known === void 0) continue;
|
|
266
|
+
const hint = known.proxyHint === true ? proxyVariableOf(env) === void 0 ? PROXY_HINT : void 0 : known.hint;
|
|
267
|
+
return hint === void 0 ? { reason: known.reason } : { reason: known.reason, hint };
|
|
268
|
+
}
|
|
269
|
+
return { reason: codes[0] ?? deepestMessageOf(err) };
|
|
270
|
+
}
|
|
271
|
+
function networkDetail(err, url, env = process3.env) {
|
|
272
|
+
return `network error: ${hostOf(url)}: ${faultOf(err, env).reason}`;
|
|
273
|
+
}
|
|
274
|
+
function networkText(err, url, env = process3.env) {
|
|
275
|
+
const fault = faultOf(err, env);
|
|
276
|
+
const line = `network error: ${hostOf(url)}: ${fault.reason}`;
|
|
277
|
+
return fault.hint === void 0 ? line : `${line}
|
|
278
|
+
hint: ${fault.hint}`;
|
|
279
|
+
}
|
|
280
|
+
async function sendRequest(fetchImpl, url, init, env = process3.env) {
|
|
281
|
+
await readyNetwork(env);
|
|
282
|
+
try {
|
|
283
|
+
return await fetchImpl(url, init);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
throw new ApiError(networkText(err, url, env));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/client.ts
|
|
155
290
|
function errorText(body, status2) {
|
|
156
291
|
if (body !== null && typeof body === "object" && typeof body.error === "string") {
|
|
157
292
|
return body.error;
|
|
@@ -189,12 +324,7 @@ var Client = class {
|
|
|
189
324
|
headers["content-type"] = opts.contentType ?? "application/octet-stream";
|
|
190
325
|
body = opts.bytes;
|
|
191
326
|
}
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
res = await this.fetchImpl(this.baseUrl + pathname, { method, headers, body });
|
|
195
|
-
} catch (err) {
|
|
196
|
-
throw new ApiError(`network error: ${messageOf(err)}`);
|
|
197
|
-
}
|
|
327
|
+
const res = await sendRequest(this.fetchImpl, this.baseUrl + pathname, { method, headers, body });
|
|
198
328
|
const text = await res.text();
|
|
199
329
|
const parsed = parseBody(text);
|
|
200
330
|
if (opts.allow404 === true && res.status === 404) return { status: 404, body: void 0 };
|
|
@@ -280,73 +410,9 @@ var Client = class {
|
|
|
280
410
|
};
|
|
281
411
|
|
|
282
412
|
// src/config.ts
|
|
283
|
-
import { readFile } from "node:fs/promises";
|
|
284
|
-
import { homedir as
|
|
285
|
-
import
|
|
286
|
-
|
|
287
|
-
// src/keychain.ts
|
|
288
|
-
import { spawn } from "node:child_process";
|
|
289
|
-
import process2 from "node:process";
|
|
290
|
-
var SERVICE = "rshow";
|
|
291
|
-
var MISSING = 44;
|
|
292
|
-
var NO_MACOS = "the keychain needs macOS; set RSHOW_TOKEN instead";
|
|
293
|
-
function bin() {
|
|
294
|
-
return process2.env.RSHOW_SECURITY_BIN ?? "security";
|
|
295
|
-
}
|
|
296
|
-
function usable() {
|
|
297
|
-
return process2.platform === "darwin" || process2.env.RSHOW_SECURITY_BIN !== void 0;
|
|
298
|
-
}
|
|
299
|
-
function run(args, input) {
|
|
300
|
-
return new Promise((resolve2, reject) => {
|
|
301
|
-
const child = spawn(bin(), args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
302
|
-
let stdout = "";
|
|
303
|
-
let stderr = "";
|
|
304
|
-
child.stdout.setEncoding("utf8");
|
|
305
|
-
child.stdout.on("data", (chunk2) => {
|
|
306
|
-
stdout += chunk2;
|
|
307
|
-
});
|
|
308
|
-
child.stderr.setEncoding("utf8");
|
|
309
|
-
child.stderr.on("data", (chunk2) => {
|
|
310
|
-
stderr += chunk2;
|
|
311
|
-
});
|
|
312
|
-
child.stdin.on("error", () => {
|
|
313
|
-
});
|
|
314
|
-
child.on("error", (err) => {
|
|
315
|
-
if (err.code === "ENOENT") resolve2({ code: -1, stdout, stderr, absent: true });
|
|
316
|
-
else reject(new ApiError(`keychain: ${err.message}`));
|
|
317
|
-
});
|
|
318
|
-
child.on("close", (code) => resolve2({ code: code ?? -1, stdout, stderr, absent: false }));
|
|
319
|
-
child.stdin.end(input ?? "");
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
function failure(doing, result, secret) {
|
|
323
|
-
const text = secret === void 0 ? result.stderr : result.stderr.split(secret).join("[redacted]");
|
|
324
|
-
const detail = text.trim();
|
|
325
|
-
return new ApiError(`${doing}: ${detail === "" ? `exit ${result.code}` : detail}`);
|
|
326
|
-
}
|
|
327
|
-
async function save(url, token) {
|
|
328
|
-
if (!usable()) throw new UsageError(NO_MACOS);
|
|
329
|
-
const line = `add-generic-password -U -s ${SERVICE} -a ${url} -w ${token}
|
|
330
|
-
`;
|
|
331
|
-
const result = await run(["-i"], line);
|
|
332
|
-
if (result.code !== 0) throw failure("cannot save the token in the keychain", result, token);
|
|
333
|
-
if (await read(url) !== token) throw failure("cannot save the token in the keychain", result, token);
|
|
334
|
-
}
|
|
335
|
-
async function read(url) {
|
|
336
|
-
if (!usable()) return void 0;
|
|
337
|
-
const result = await run(["find-generic-password", "-s", SERVICE, "-a", url, "-w"]);
|
|
338
|
-
if (result.absent || result.code === MISSING) return void 0;
|
|
339
|
-
if (result.code !== 0) throw failure("cannot read the token from the keychain", result);
|
|
340
|
-
const token = result.stdout.trim();
|
|
341
|
-
return token === "" ? void 0 : token;
|
|
342
|
-
}
|
|
343
|
-
async function remove(url) {
|
|
344
|
-
if (!usable()) throw new UsageError(NO_MACOS);
|
|
345
|
-
const result = await run(["delete-generic-password", "-s", SERVICE, "-a", url]);
|
|
346
|
-
if (result.absent || result.code === MISSING) return false;
|
|
347
|
-
if (result.code !== 0) throw failure("cannot remove the token from the keychain", result);
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
413
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
414
|
+
import { homedir as homedir3 } from "node:os";
|
|
415
|
+
import path3 from "node:path";
|
|
350
416
|
|
|
351
417
|
// ../shared/src/index.ts
|
|
352
418
|
var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
@@ -393,8 +459,8 @@ function tagsOf(value) {
|
|
|
393
459
|
if (!Array.isArray(value)) return [];
|
|
394
460
|
return value.filter((tag) => typeof tag === "string").map((tag) => tag.toLowerCase());
|
|
395
461
|
}
|
|
396
|
-
function withTags(current, add2,
|
|
397
|
-
const next = unique(tagsOf(current)).filter((tag) => !
|
|
462
|
+
function withTags(current, add2, remove5) {
|
|
463
|
+
const next = unique(tagsOf(current)).filter((tag) => !remove5.includes(tag));
|
|
398
464
|
for (const tag of add2) {
|
|
399
465
|
if (!next.includes(tag)) next.push(tag);
|
|
400
466
|
}
|
|
@@ -406,16 +472,326 @@ function sameTags(left, right) {
|
|
|
406
472
|
return a.length === b.length && a.every((tag, index) => tag === b[index]);
|
|
407
473
|
}
|
|
408
474
|
|
|
475
|
+
// src/tokenstore.ts
|
|
476
|
+
import process8 from "node:process";
|
|
477
|
+
|
|
478
|
+
// src/credfile.ts
|
|
479
|
+
import { chmod, mkdir as mkdir2, readFile as readFile2, rename as rename2, stat as stat2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
480
|
+
import { homedir as homedir2 } from "node:os";
|
|
481
|
+
import path2 from "node:path";
|
|
482
|
+
import process4 from "node:process";
|
|
483
|
+
var DIR = "rshow";
|
|
484
|
+
var FILE = "credentials";
|
|
485
|
+
var VERSION2 = 1;
|
|
486
|
+
var FILE_MODE = 384;
|
|
487
|
+
var DIR_MODE = 448;
|
|
488
|
+
var SHARED = 63;
|
|
489
|
+
var fixed = /* @__PURE__ */ new Set();
|
|
490
|
+
function messageOf2(err) {
|
|
491
|
+
return err instanceof Error ? err.message : String(err);
|
|
492
|
+
}
|
|
493
|
+
function isRecord(value) {
|
|
494
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
495
|
+
}
|
|
496
|
+
function credentialsPath(env) {
|
|
497
|
+
const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
|
|
498
|
+
const home = (env.HOME ?? "").trim();
|
|
499
|
+
const base = xdg !== "" ? xdg : path2.join(home !== "" ? home : homedir2(), ".config");
|
|
500
|
+
return path2.join(base, DIR, FILE);
|
|
501
|
+
}
|
|
502
|
+
async function tighten(file) {
|
|
503
|
+
await chmod(file, FILE_MODE).catch(() => {
|
|
504
|
+
});
|
|
505
|
+
if (fixed.has(file)) return;
|
|
506
|
+
fixed.add(file);
|
|
507
|
+
warn(`${file} was readable by group or other; its mode is now 600`);
|
|
508
|
+
}
|
|
509
|
+
async function load(file) {
|
|
510
|
+
const info = await stat2(file).catch(() => void 0);
|
|
511
|
+
if (info === void 0) return {};
|
|
512
|
+
if ((info.mode & SHARED) !== 0) await tighten(file);
|
|
513
|
+
let text;
|
|
514
|
+
try {
|
|
515
|
+
text = await readFile2(file, "utf8");
|
|
516
|
+
} catch (err) {
|
|
517
|
+
if (err.code === "ENOENT") return {};
|
|
518
|
+
throw new UsageError(`cannot read ${file}: ${messageOf2(err)}`);
|
|
519
|
+
}
|
|
520
|
+
let parsed;
|
|
521
|
+
try {
|
|
522
|
+
parsed = JSON.parse(text);
|
|
523
|
+
} catch {
|
|
524
|
+
throw new UsageError(`invalid JSON in ${file}`);
|
|
525
|
+
}
|
|
526
|
+
if (!isRecord(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
527
|
+
if (parsed.version !== void 0 && parsed.version !== VERSION2) {
|
|
528
|
+
throw new UsageError(`${file}: version must be ${VERSION2}`);
|
|
529
|
+
}
|
|
530
|
+
const raw = parsed.tokens ?? {};
|
|
531
|
+
if (!isRecord(raw)) throw new UsageError(`${file}: tokens must be an object`);
|
|
532
|
+
const tokens = {};
|
|
533
|
+
for (const [url, token] of Object.entries(raw)) {
|
|
534
|
+
if (typeof token !== "string") throw new UsageError(`${file}: tokens.${url} must be a string`);
|
|
535
|
+
tokens[url] = token;
|
|
536
|
+
}
|
|
537
|
+
return tokens;
|
|
538
|
+
}
|
|
539
|
+
async function store(file, tokens) {
|
|
540
|
+
const dir = path2.dirname(file);
|
|
541
|
+
let created;
|
|
542
|
+
try {
|
|
543
|
+
created = await mkdir2(dir, { recursive: true, mode: DIR_MODE });
|
|
544
|
+
} catch (err) {
|
|
545
|
+
throw new UsageError(`cannot make ${dir}: ${messageOf2(err)}`);
|
|
546
|
+
}
|
|
547
|
+
if (created !== void 0) await chmod(dir, DIR_MODE).catch(() => {
|
|
548
|
+
});
|
|
549
|
+
const temp = path2.join(dir, `.${path2.basename(file)}.${process4.pid}.${Date.now()}.tmp`);
|
|
550
|
+
const body = `${JSON.stringify({ version: VERSION2, tokens }, null, 2)}
|
|
551
|
+
`;
|
|
552
|
+
try {
|
|
553
|
+
await writeFile2(temp, body, { mode: FILE_MODE, flag: "wx" });
|
|
554
|
+
await chmod(temp, FILE_MODE);
|
|
555
|
+
await rename2(temp, file);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
await unlink(temp).catch(() => {
|
|
558
|
+
});
|
|
559
|
+
throw new UsageError(`cannot write ${file}: ${messageOf2(err)}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function read(file, url) {
|
|
563
|
+
return (await load(file))[url];
|
|
564
|
+
}
|
|
565
|
+
async function save(file, url, token) {
|
|
566
|
+
const tokens = await load(file);
|
|
567
|
+
tokens[url] = token;
|
|
568
|
+
await store(file, tokens);
|
|
569
|
+
}
|
|
570
|
+
async function remove(file, url) {
|
|
571
|
+
const tokens = await load(file);
|
|
572
|
+
if (tokens[url] === void 0) return false;
|
|
573
|
+
delete tokens[url];
|
|
574
|
+
if (Object.keys(tokens).length === 0) {
|
|
575
|
+
await unlink(file).catch((err) => {
|
|
576
|
+
if (err.code !== "ENOENT") throw new UsageError(`cannot remove ${file}: ${messageOf2(err)}`);
|
|
577
|
+
});
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
await store(file, tokens);
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/keychain.ts
|
|
585
|
+
import process6 from "node:process";
|
|
586
|
+
|
|
587
|
+
// src/tool.ts
|
|
588
|
+
import { spawn } from "node:child_process";
|
|
589
|
+
import process5 from "node:process";
|
|
590
|
+
function childEnv(env) {
|
|
591
|
+
const out = { ...process5.env };
|
|
592
|
+
for (const [key2, value] of Object.entries(env ?? {})) {
|
|
593
|
+
if (value === void 0) delete out[key2];
|
|
594
|
+
else out[key2] = value;
|
|
595
|
+
}
|
|
596
|
+
return out;
|
|
597
|
+
}
|
|
598
|
+
function runTool(bin3, args, input, env) {
|
|
599
|
+
return new Promise((resolve2, reject) => {
|
|
600
|
+
const child = spawn(bin3, args, { stdio: ["pipe", "pipe", "pipe"], env: childEnv(env) });
|
|
601
|
+
let stdout = "";
|
|
602
|
+
let stderr = "";
|
|
603
|
+
child.stdout.setEncoding("utf8");
|
|
604
|
+
child.stdout.on("data", (chunk2) => {
|
|
605
|
+
stdout += chunk2;
|
|
606
|
+
});
|
|
607
|
+
child.stderr.setEncoding("utf8");
|
|
608
|
+
child.stderr.on("data", (chunk2) => {
|
|
609
|
+
stderr += chunk2;
|
|
610
|
+
});
|
|
611
|
+
child.stdin.on("error", () => {
|
|
612
|
+
});
|
|
613
|
+
child.on("error", (err) => {
|
|
614
|
+
if (err.code === "ENOENT") resolve2({ code: -1, stdout, stderr, absent: true });
|
|
615
|
+
else reject(new ApiError(`${bin3}: ${err.message}`));
|
|
616
|
+
});
|
|
617
|
+
child.on("close", (code) => resolve2({ code: code ?? -1, stdout, stderr, absent: false }));
|
|
618
|
+
child.stdin.end(input ?? "");
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function toolFailure(doing, result, secret) {
|
|
622
|
+
const text = secret === void 0 ? result.stderr : result.stderr.split(secret).join("[redacted]");
|
|
623
|
+
const detail = text.trim();
|
|
624
|
+
return new ApiError(`${doing}: ${detail === "" ? `exit ${result.code}` : detail}`);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/keychain.ts
|
|
628
|
+
var SERVICE = "rshow";
|
|
629
|
+
var MISSING = 44;
|
|
630
|
+
function bin(env) {
|
|
631
|
+
const set = (env.RSHOW_SECURITY_BIN ?? "").trim();
|
|
632
|
+
return set === "" ? "security" : set;
|
|
633
|
+
}
|
|
634
|
+
function canSave(platform = process6.platform, securityBin = process6.env.RSHOW_SECURITY_BIN) {
|
|
635
|
+
return platform === "darwin" || (securityBin ?? "").trim() !== "";
|
|
636
|
+
}
|
|
637
|
+
async function save2(url, token, env = process6.env) {
|
|
638
|
+
if (!canSave(process6.platform, env.RSHOW_SECURITY_BIN)) {
|
|
639
|
+
throw new UsageError("the keychain needs macOS; set RSHOW_TOKEN instead");
|
|
640
|
+
}
|
|
641
|
+
const line = `add-generic-password -U -s ${SERVICE} -a ${url} -w ${token}
|
|
642
|
+
`;
|
|
643
|
+
const result = await runTool(bin(env), ["-i"], line, env);
|
|
644
|
+
if (result.code !== 0) throw toolFailure("cannot save the token in the keychain", result, token);
|
|
645
|
+
if (await read2(url, env) !== token) {
|
|
646
|
+
throw toolFailure("cannot save the token in the keychain", result, token);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
async function read2(url, env = process6.env) {
|
|
650
|
+
if (!canSave(process6.platform, env.RSHOW_SECURITY_BIN)) return void 0;
|
|
651
|
+
const args = ["find-generic-password", "-s", SERVICE, "-a", url, "-w"];
|
|
652
|
+
const result = await runTool(bin(env), args, void 0, env);
|
|
653
|
+
if (result.absent || result.code === MISSING) return void 0;
|
|
654
|
+
if (result.code !== 0) throw toolFailure("cannot read the token from the keychain", result);
|
|
655
|
+
const token = result.stdout.trim();
|
|
656
|
+
return token === "" ? void 0 : token;
|
|
657
|
+
}
|
|
658
|
+
async function remove2(url, env = process6.env) {
|
|
659
|
+
if (!canSave(process6.platform, env.RSHOW_SECURITY_BIN)) return false;
|
|
660
|
+
const args = ["delete-generic-password", "-s", SERVICE, "-a", url];
|
|
661
|
+
const result = await runTool(bin(env), args, void 0, env);
|
|
662
|
+
if (result.absent || result.code === MISSING) return false;
|
|
663
|
+
if (result.code !== 0) throw toolFailure("cannot remove the token from the keychain", result);
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/keyring.ts
|
|
668
|
+
import process7 from "node:process";
|
|
669
|
+
var SERVICE2 = "rshow";
|
|
670
|
+
var PROBE_SERVICE = "rshow-probe";
|
|
671
|
+
var PROBE_ACCOUNT = "probe";
|
|
672
|
+
var NOT_FOUND = 1;
|
|
673
|
+
function bin2(env) {
|
|
674
|
+
const set = (env.RSHOW_SECRET_TOOL_BIN ?? "").trim();
|
|
675
|
+
return set === "" ? "secret-tool" : set;
|
|
676
|
+
}
|
|
677
|
+
function canUse(env, platform = process7.platform) {
|
|
678
|
+
return platform === "linux" || (env.RSHOW_SECRET_TOOL_BIN ?? "").trim() !== "";
|
|
679
|
+
}
|
|
680
|
+
async function usable(env = process7.env) {
|
|
681
|
+
const args = ["lookup", "service", PROBE_SERVICE, "account", PROBE_ACCOUNT];
|
|
682
|
+
const result = await runTool(bin2(env), args, void 0, env);
|
|
683
|
+
return !result.absent && (result.code === 0 || result.code === NOT_FOUND);
|
|
684
|
+
}
|
|
685
|
+
async function save3(url, token, env = process7.env) {
|
|
686
|
+
const args = ["store", "--label", `rshow ${url}`, "service", SERVICE2, "account", url];
|
|
687
|
+
const result = await runTool(bin2(env), args, token, env);
|
|
688
|
+
if (result.code !== 0) throw toolFailure("cannot save the token in the keyring", result, token);
|
|
689
|
+
if (await read3(url, env) !== token) {
|
|
690
|
+
throw toolFailure("cannot save the token in the keyring", result, token);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async function read3(url, env = process7.env) {
|
|
694
|
+
const args = ["lookup", "service", SERVICE2, "account", url];
|
|
695
|
+
const result = await runTool(bin2(env), args, void 0, env);
|
|
696
|
+
if (result.absent || result.code === NOT_FOUND) return void 0;
|
|
697
|
+
if (result.code !== 0) throw toolFailure("cannot read the token from the keyring", result);
|
|
698
|
+
const token = result.stdout.trim();
|
|
699
|
+
return token === "" ? void 0 : token;
|
|
700
|
+
}
|
|
701
|
+
async function remove3(url, env = process7.env) {
|
|
702
|
+
const args = ["clear", "service", SERVICE2, "account", url];
|
|
703
|
+
const result = await runTool(bin2(env), args, void 0, env);
|
|
704
|
+
if (result.absent || result.code === NOT_FOUND) return false;
|
|
705
|
+
if (result.code !== 0) throw toolFailure("cannot remove the token from the keyring", result);
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/tokenstore.ts
|
|
710
|
+
var BACKENDS = ["keychain", "keyring", "file"];
|
|
711
|
+
function storeOf(backend, env) {
|
|
712
|
+
if (backend === "keychain") {
|
|
713
|
+
return {
|
|
714
|
+
backend,
|
|
715
|
+
detail: "keychain",
|
|
716
|
+
description: "the macOS Keychain",
|
|
717
|
+
read: (url) => read2(url, env),
|
|
718
|
+
save: (url, token) => save2(url, token, env),
|
|
719
|
+
remove: (url) => remove2(url, env)
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
if (backend === "keyring") {
|
|
723
|
+
return {
|
|
724
|
+
backend,
|
|
725
|
+
detail: "keyring",
|
|
726
|
+
description: "the Linux keyring",
|
|
727
|
+
read: (url) => read3(url, env),
|
|
728
|
+
save: (url, token) => save3(url, token, env),
|
|
729
|
+
remove: (url) => remove3(url, env)
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const file = credentialsPath(env);
|
|
733
|
+
return {
|
|
734
|
+
backend,
|
|
735
|
+
detail: `file ${file}`,
|
|
736
|
+
description: `${file} (mode 600)`,
|
|
737
|
+
read: (url) => read(file, url),
|
|
738
|
+
save: (url, token) => save(file, url, token),
|
|
739
|
+
remove: (url) => remove(file, url)
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function forced(env) {
|
|
743
|
+
const text = (env.RSHOW_TOKEN_STORE ?? "").trim();
|
|
744
|
+
if (text === "") return void 0;
|
|
745
|
+
for (const backend of BACKENDS) {
|
|
746
|
+
if (backend === text) return backend;
|
|
747
|
+
}
|
|
748
|
+
throw new UsageError(`RSHOW_TOKEN_STORE must be keychain, keyring or file, not ${text}`);
|
|
749
|
+
}
|
|
750
|
+
async function choose(env = process8.env, platform = process8.platform) {
|
|
751
|
+
const forcedBackend = forced(env);
|
|
752
|
+
if (forcedBackend !== void 0) return storeOf(forcedBackend, env);
|
|
753
|
+
if (canSave(platform, env.RSHOW_SECURITY_BIN)) return storeOf("keychain", env);
|
|
754
|
+
if (canUse(env, platform) && await usable(env)) return storeOf("keyring", env);
|
|
755
|
+
return storeOf("file", env);
|
|
756
|
+
}
|
|
757
|
+
function fileStore(env = process8.env) {
|
|
758
|
+
return storeOf("file", env);
|
|
759
|
+
}
|
|
760
|
+
async function every(env = process8.env, platform = process8.platform) {
|
|
761
|
+
const stores = [];
|
|
762
|
+
if (canSave(platform, env.RSHOW_SECURITY_BIN)) stores.push(storeOf("keychain", env));
|
|
763
|
+
if (canUse(env, platform) && await usable(env)) stores.push(storeOf("keyring", env));
|
|
764
|
+
stores.push(storeOf("file", env));
|
|
765
|
+
return stores;
|
|
766
|
+
}
|
|
767
|
+
async function find(env, url, platform = process8.platform) {
|
|
768
|
+
const forcedBackend = forced(env);
|
|
769
|
+
const stores = forcedBackend === void 0 ? await every(env, platform) : [storeOf(forcedBackend, env)];
|
|
770
|
+
let failed = false;
|
|
771
|
+
let firstError;
|
|
772
|
+
for (const store2 of stores) {
|
|
773
|
+
try {
|
|
774
|
+
const token = await store2.read(url);
|
|
775
|
+
if (token !== void 0) return { store: store2, token };
|
|
776
|
+
} catch (err) {
|
|
777
|
+
if (!failed) firstError = err;
|
|
778
|
+
failed = true;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (failed) throw firstError;
|
|
782
|
+
return void 0;
|
|
783
|
+
}
|
|
784
|
+
|
|
409
785
|
// src/config.ts
|
|
410
786
|
var DEFAULT_OUT = "dist";
|
|
411
787
|
var GLOBAL_DIR = "rshow";
|
|
412
788
|
var GLOBAL_FILE = "config.json";
|
|
413
789
|
var LOCAL_FILE = "rshow.json";
|
|
414
790
|
var NO_SERVER = "no server configured: run rshow init --url <admin-url> --art <viewer-url>";
|
|
415
|
-
function
|
|
791
|
+
function messageOf3(err) {
|
|
416
792
|
return err instanceof Error ? err.message : String(err);
|
|
417
793
|
}
|
|
418
|
-
function
|
|
794
|
+
function isRecord2(value) {
|
|
419
795
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
420
796
|
}
|
|
421
797
|
function withoutSlash(value) {
|
|
@@ -424,14 +800,14 @@ function withoutSlash(value) {
|
|
|
424
800
|
function globalConfigPath(env) {
|
|
425
801
|
const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
|
|
426
802
|
const home = (env.HOME ?? "").trim();
|
|
427
|
-
const base = xdg !== "" ? xdg :
|
|
428
|
-
return
|
|
803
|
+
const base = xdg !== "" ? xdg : path3.join(home !== "" ? home : homedir3(), ".config");
|
|
804
|
+
return path3.join(base, GLOBAL_DIR, GLOBAL_FILE);
|
|
429
805
|
}
|
|
430
806
|
function localConfigPath(dir) {
|
|
431
|
-
return
|
|
807
|
+
return path3.join(path3.resolve(dir), LOCAL_FILE);
|
|
432
808
|
}
|
|
433
809
|
function layerOf(file, value) {
|
|
434
|
-
if (!
|
|
810
|
+
if (!isRecord2(value)) throw new UsageError(`${file} must hold a JSON object`);
|
|
435
811
|
const fieldOf = (key2) => {
|
|
436
812
|
const raw = value[key2];
|
|
437
813
|
if (raw === void 0 || raw === null) return void 0;
|
|
@@ -451,7 +827,7 @@ function layerOf(file, value) {
|
|
|
451
827
|
};
|
|
452
828
|
const rawTokens = value.tokens;
|
|
453
829
|
if (rawTokens !== void 0 && rawTokens !== null) {
|
|
454
|
-
if (!
|
|
830
|
+
if (!isRecord2(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
|
|
455
831
|
const tokens = {};
|
|
456
832
|
for (const [key2, raw] of Object.entries(rawTokens)) {
|
|
457
833
|
if (typeof raw !== "string") throw new UsageError(`${file}: tokens.${key2} must be a string`);
|
|
@@ -473,10 +849,10 @@ function layerOf(file, value) {
|
|
|
473
849
|
async function readConfigFile(file) {
|
|
474
850
|
let text;
|
|
475
851
|
try {
|
|
476
|
-
text = await
|
|
852
|
+
text = await readFile3(file, "utf8");
|
|
477
853
|
} catch (err) {
|
|
478
854
|
if (err.code === "ENOENT") return {};
|
|
479
|
-
throw new UsageError(`cannot read ${file}: ${
|
|
855
|
+
throw new UsageError(`cannot read ${file}: ${messageOf3(err)}`);
|
|
480
856
|
}
|
|
481
857
|
let parsed;
|
|
482
858
|
try {
|
|
@@ -538,13 +914,13 @@ function requireArt(config) {
|
|
|
538
914
|
async function readToken(env, url) {
|
|
539
915
|
const token = (env.RSHOW_TOKEN ?? "").trim();
|
|
540
916
|
if (token !== "") return token;
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
917
|
+
const found = await find(env, url);
|
|
918
|
+
if (found !== void 0) return found.token;
|
|
543
919
|
throw new UsageError("no token: run rshow login, or set RSHOW_TOKEN");
|
|
544
920
|
}
|
|
545
921
|
|
|
546
922
|
// src/output.ts
|
|
547
|
-
import
|
|
923
|
+
import process9 from "node:process";
|
|
548
924
|
var Output = class {
|
|
549
925
|
json;
|
|
550
926
|
constructor(json) {
|
|
@@ -552,16 +928,16 @@ var Output = class {
|
|
|
552
928
|
}
|
|
553
929
|
data(value) {
|
|
554
930
|
if (!this.json) return;
|
|
555
|
-
|
|
931
|
+
process9.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
|
|
556
932
|
`);
|
|
557
933
|
}
|
|
558
934
|
line(text) {
|
|
559
935
|
if (this.json) return;
|
|
560
|
-
|
|
936
|
+
process9.stdout.write(`${text}
|
|
561
937
|
`);
|
|
562
938
|
}
|
|
563
939
|
raw(text) {
|
|
564
|
-
|
|
940
|
+
process9.stdout.write(`${text}
|
|
565
941
|
`);
|
|
566
942
|
}
|
|
567
943
|
};
|
|
@@ -591,7 +967,7 @@ commands:
|
|
|
591
967
|
share <slug> set mode or change shares
|
|
592
968
|
pin <slug> <n|live> serve version n, or live for the newest
|
|
593
969
|
delete <slug> --yes delete an artifact
|
|
594
|
-
login [--url u] save the API token
|
|
970
|
+
login [--url u] save the API token for this account
|
|
595
971
|
logout [--url u] remove the saved token for a server
|
|
596
972
|
|
|
597
973
|
global:
|
|
@@ -602,9 +978,13 @@ global:
|
|
|
602
978
|
environment:
|
|
603
979
|
RSHOW_URL API base URL, overrides the config file
|
|
604
980
|
RSHOW_ART art base URL, overrides the config file
|
|
605
|
-
RSHOW_TOKEN the API token, else the token
|
|
606
|
-
|
|
607
|
-
|
|
981
|
+
RSHOW_TOKEN the API token, else the token rshow login saved in
|
|
982
|
+
the macOS Keychain, the Linux keyring or a 0600 file
|
|
983
|
+
RSHOW_TOKEN_STORE force the keychain, keyring or file token store
|
|
984
|
+
XDG_CONFIG_HOME where rshow keeps config.json and credentials
|
|
985
|
+
RSHOW_SYSTEM_CA set to 0 to trust only the roots node bundles
|
|
986
|
+
RSHOW_EXTRA_CA_FILE a PEM file of extra roots, such as a cluster's CA
|
|
987
|
+
NODE_USE_ENV_PROXY set to 1 so fetch uses https_proxy (Node 24+)
|
|
608
988
|
|
|
609
989
|
Start here: rshow init --url <admin-url> --art <viewer-url>, then rshow login.
|
|
610
990
|
|
|
@@ -702,8 +1082,8 @@ files win. Run rshow init to write url and art.
|
|
|
702
1082
|
A theme with no version, or @latest, is looked up on the art host and printed
|
|
703
1083
|
as <name>@<n>; when that fails a warning goes to stderr. --offline skips it, and
|
|
704
1084
|
so does a config with no art host.
|
|
705
|
-
The token is RSHOW_TOKEN, else the token
|
|
706
|
-
Keychain
|
|
1085
|
+
The token is RSHOW_TOKEN, else the token rshow login saved in the macOS
|
|
1086
|
+
Keychain, the Linux keyring or a file with mode 600.`;
|
|
707
1087
|
var INIT_USAGE = `usage: rshow init --url <admin-url> --art <viewer-url> [--theme <name>] [--force]
|
|
708
1088
|
|
|
709
1089
|
Writes the global config file that rshow reads first:
|
|
@@ -723,6 +1103,7 @@ package, the config files, the token, the api, the art host, the theme and the
|
|
|
723
1103
|
skills, one line each: ok|warn|fail <check> <detail>. With [dir] it reads
|
|
724
1104
|
<dir>/rshow.json too. A skill line warns and never fails.
|
|
725
1105
|
Network calls time out after 5 s. Exit 0 when no check failed, 6 when one did.
|
|
1106
|
+
The token line names its source: env, keychain, keyring, or file and its path.
|
|
726
1107
|
The token is never printed.`;
|
|
727
1108
|
var THEME_USAGE = `usage: rshow theme <new|check|preview|publish|list> [options]
|
|
728
1109
|
|
|
@@ -849,14 +1230,19 @@ var DELETE_USAGE = `usage: rshow delete <slug> --yes
|
|
|
849
1230
|
Deletes the artifact and needs --yes.`;
|
|
850
1231
|
var LOGIN_USAGE = `usage: rshow login [--url u]
|
|
851
1232
|
|
|
852
|
-
Reads the token from stdin, checks it
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1233
|
+
Reads the token from stdin, checks it with the API, then saves it in the macOS
|
|
1234
|
+
Keychain, in the Linux keyring, or in $XDG_CONFIG_HOME/rshow/credentials with
|
|
1235
|
+
mode 600: the first store this machine has, and it reads it back to confirm.
|
|
1236
|
+
On a terminal it asks for the token with echo off, so it is never typed on a
|
|
1237
|
+
command line or pasted anywhere. The API base URL is the account, so each
|
|
1238
|
+
server keeps its own token; --url logs in to another one.
|
|
1239
|
+
Prints the email, the scope and where the token went.
|
|
1240
|
+
RSHOW_TOKEN still wins over the saved token, and RSHOW_TOKEN_STORE forces one
|
|
1241
|
+
of keychain, keyring or file.`;
|
|
857
1242
|
var LOGOUT_USAGE = `usage: rshow logout [--url u]
|
|
858
1243
|
|
|
859
|
-
Removes the token this server has
|
|
1244
|
+
Removes the token this server has from every store that holds it: the macOS
|
|
1245
|
+
Keychain, the Linux keyring and the 0600 credentials file.`;
|
|
860
1246
|
var PROJECT_USAGE = `usage: rshow project <create|list|show|share|delete> [options]
|
|
861
1247
|
|
|
862
1248
|
project create <slug> [--title t] [--description d]
|
|
@@ -982,14 +1368,14 @@ function usageLine(usage) {
|
|
|
982
1368
|
}
|
|
983
1369
|
|
|
984
1370
|
// src/commands/publish.ts
|
|
985
|
-
import { stat as
|
|
986
|
-
import
|
|
1371
|
+
import { stat as stat5 } from "node:fs/promises";
|
|
1372
|
+
import path8 from "node:path";
|
|
987
1373
|
|
|
988
1374
|
// src/args.ts
|
|
989
1375
|
import { parseArgs } from "node:util";
|
|
990
1376
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["--add", "--editor", "--remove", "--tag"]);
|
|
991
1377
|
var LOWERCASE_TAG = new RegExp(TAG_PATTERN.source, "i");
|
|
992
|
-
function
|
|
1378
|
+
function messageOf4(err) {
|
|
993
1379
|
return err instanceof Error ? err.message : String(err);
|
|
994
1380
|
}
|
|
995
1381
|
function isListValue(token) {
|
|
@@ -1025,7 +1411,7 @@ function parseCommandArgs(argv, options, minPositionals, maxPositionals, hint) {
|
|
|
1025
1411
|
try {
|
|
1026
1412
|
parsed = parseArgs({ args: expanded, options, allowPositionals: true, strict: true });
|
|
1027
1413
|
} catch (err) {
|
|
1028
|
-
throw new UsageError(`${
|
|
1414
|
+
throw new UsageError(`${messageOf4(err)} (try: ${usageLine(hint)})`);
|
|
1029
1415
|
}
|
|
1030
1416
|
const count = parsed.positionals.length;
|
|
1031
1417
|
if (count < minPositionals || count > maxPositionals) {
|
|
@@ -1057,21 +1443,21 @@ function emailList(args, name) {
|
|
|
1057
1443
|
|
|
1058
1444
|
// src/build.ts
|
|
1059
1445
|
import { spawn as spawn2 } from "node:child_process";
|
|
1060
|
-
import
|
|
1061
|
-
function
|
|
1446
|
+
import process10 from "node:process";
|
|
1447
|
+
function messageOf5(err) {
|
|
1062
1448
|
return err instanceof Error ? err.message : String(err);
|
|
1063
1449
|
}
|
|
1064
1450
|
function shellPath(env) {
|
|
1065
1451
|
const shell = (env.SHELL ?? "").trim();
|
|
1066
1452
|
return shell === "" ? "/bin/sh" : shell;
|
|
1067
1453
|
}
|
|
1068
|
-
async function runBuild(dir, command, env =
|
|
1454
|
+
async function runBuild(dir, command, env = process10.env) {
|
|
1069
1455
|
const code = await new Promise((resolve2, reject) => {
|
|
1070
1456
|
const child = spawn2(shellPath(env), ["-c", command], { cwd: dir, stdio: "inherit" });
|
|
1071
1457
|
child.once("error", reject);
|
|
1072
1458
|
child.once("close", (value) => resolve2(value));
|
|
1073
1459
|
}).catch((err) => {
|
|
1074
|
-
throw new BuildError(`build failed: ${
|
|
1460
|
+
throw new BuildError(`build failed: ${messageOf5(err)}`);
|
|
1075
1461
|
});
|
|
1076
1462
|
if (code !== 0) {
|
|
1077
1463
|
throw new BuildError(`build failed: ${code === null ? "killed by a signal" : `exit ${code}`}`);
|
|
@@ -1079,12 +1465,12 @@ async function runBuild(dir, command, env = process4.env) {
|
|
|
1079
1465
|
}
|
|
1080
1466
|
|
|
1081
1467
|
// src/check.ts
|
|
1082
|
-
import { readFile as
|
|
1083
|
-
import
|
|
1468
|
+
import { readFile as readFile6, readdir, stat as stat3 } from "node:fs/promises";
|
|
1469
|
+
import path6 from "node:path";
|
|
1084
1470
|
|
|
1085
1471
|
// src/mockup.ts
|
|
1086
|
-
import { readFile as
|
|
1087
|
-
import
|
|
1472
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1473
|
+
import path4 from "node:path";
|
|
1088
1474
|
var MAX_SCAN_BYTES = 4 * 1024 * 1024;
|
|
1089
1475
|
var HTML_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm"]);
|
|
1090
1476
|
var MIN_COLS = 80;
|
|
@@ -1154,20 +1540,20 @@ function idOf(value) {
|
|
|
1154
1540
|
const id = String(value ?? "").trim().replace(/^#/, "");
|
|
1155
1541
|
return id === "" ? void 0 : id;
|
|
1156
1542
|
}
|
|
1157
|
-
function
|
|
1543
|
+
function warn2(scan, line, message) {
|
|
1158
1544
|
scan.out.push({ path: scan.path, line, message });
|
|
1159
1545
|
}
|
|
1160
1546
|
function keyHint(scan, masked, from, to, line) {
|
|
1161
1547
|
const hit = TERMINAL_KEYS.exec(masked.slice(from, to).replace(ANY_TAG, ""));
|
|
1162
1548
|
if (hit === null) return;
|
|
1163
|
-
|
|
1549
|
+
warn2(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
|
|
1164
1550
|
}
|
|
1165
1551
|
function sizeGate(scan, attrs, line) {
|
|
1166
1552
|
if (attrs.has("data-allow-small")) return;
|
|
1167
1553
|
const cols = sizeOf(attrs, "data-cols", MIN_COLS);
|
|
1168
1554
|
const rows = sizeOf(attrs, "data-rows", MIN_ROWS);
|
|
1169
1555
|
if (cols >= MIN_COLS && rows >= MIN_ROWS) return;
|
|
1170
|
-
|
|
1556
|
+
warn2(scan, line, `tui below the 80x24 minimum (${cols}x${rows}), or mark it with data-allow-small`);
|
|
1171
1557
|
}
|
|
1172
1558
|
function walk(scan, masked) {
|
|
1173
1559
|
const stack = [];
|
|
@@ -1247,25 +1633,25 @@ function resolve(scan) {
|
|
|
1247
1633
|
ids.add(item.id);
|
|
1248
1634
|
const count = (seen.get(item.id) ?? 0) + 1;
|
|
1249
1635
|
seen.set(item.id, count);
|
|
1250
|
-
if (count === 2)
|
|
1636
|
+
if (count === 2) warn2(scan, item.line, `duplicate artboard id: ${item.id}`);
|
|
1251
1637
|
}
|
|
1252
1638
|
const unknown = /* @__PURE__ */ new Set();
|
|
1253
1639
|
for (const flow of scan.flows) {
|
|
1254
1640
|
if (ids.has(flow.id) || unknown.has(flow.id)) continue;
|
|
1255
1641
|
unknown.add(flow.id);
|
|
1256
|
-
|
|
1642
|
+
warn2(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
|
|
1257
1643
|
}
|
|
1258
1644
|
for (const item of scan.boards) {
|
|
1259
|
-
if (!item.title)
|
|
1645
|
+
if (!item.title) warn2(scan, item.line, `artboard ${item.label} has no data-title`);
|
|
1260
1646
|
for (const block of item.blocks) {
|
|
1261
1647
|
const missing = block.states.filter((state) => state !== "default" && !item.states.includes(state));
|
|
1262
1648
|
if (missing.length === 0) continue;
|
|
1263
|
-
|
|
1649
|
+
warn2(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
|
|
1264
1650
|
}
|
|
1265
1651
|
for (const state of item.states) {
|
|
1266
1652
|
if (state === "default") continue;
|
|
1267
1653
|
if (item.blocks.some((block) => block.states.includes(state))) continue;
|
|
1268
|
-
|
|
1654
|
+
warn2(scan, item.line, `artboard ${item.label} lists state "${state}" that no block uses`);
|
|
1269
1655
|
}
|
|
1270
1656
|
}
|
|
1271
1657
|
}
|
|
@@ -1279,9 +1665,9 @@ function htmlWarnings(rel, text) {
|
|
|
1279
1665
|
async function mockupWarnings(entries) {
|
|
1280
1666
|
const out = [];
|
|
1281
1667
|
for (const entry of entries) {
|
|
1282
|
-
const ext =
|
|
1668
|
+
const ext = path4.extname(entry.rel).toLowerCase();
|
|
1283
1669
|
if (!HTML_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES) continue;
|
|
1284
|
-
const text = await
|
|
1670
|
+
const text = await readFile4(entry.abs, "utf8").catch(() => void 0);
|
|
1285
1671
|
if (text === void 0) continue;
|
|
1286
1672
|
out.push(...htmlWarnings(entry.rel, text));
|
|
1287
1673
|
}
|
|
@@ -1289,8 +1675,8 @@ async function mockupWarnings(entries) {
|
|
|
1289
1675
|
}
|
|
1290
1676
|
|
|
1291
1677
|
// src/style.ts
|
|
1292
|
-
import { readFile as
|
|
1293
|
-
import
|
|
1678
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1679
|
+
import path5 from "node:path";
|
|
1294
1680
|
var MAX_SCAN_BYTES2 = 4 * 1024 * 1024;
|
|
1295
1681
|
var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css"]);
|
|
1296
1682
|
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
@@ -1687,9 +2073,9 @@ async function styleWarnings(entries) {
|
|
|
1687
2073
|
const loaded = /* @__PURE__ */ new Set();
|
|
1688
2074
|
const used = [];
|
|
1689
2075
|
for (const entry of entries) {
|
|
1690
|
-
const ext =
|
|
2076
|
+
const ext = path5.extname(entry.rel).toLowerCase();
|
|
1691
2077
|
if (!STYLE_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES2) continue;
|
|
1692
|
-
const text = await
|
|
2078
|
+
const text = await readFile5(entry.abs, "utf8").catch(() => void 0);
|
|
1693
2079
|
if (text === void 0) continue;
|
|
1694
2080
|
collectLoaded(text, loaded);
|
|
1695
2081
|
if (stylePathSkipped(entry.rel)) continue;
|
|
@@ -1740,9 +2126,6 @@ var CONNECT_PATTERNS = [
|
|
|
1740
2126
|
pattern: /\.open\s*\(\s*["'`][A-Za-z]+["'`]\s*,\s*["'`](https?:\/\/[^"'`\s)]+)/g
|
|
1741
2127
|
}
|
|
1742
2128
|
];
|
|
1743
|
-
function messageOf5(err) {
|
|
1744
|
-
return err instanceof Error ? err.message : String(err);
|
|
1745
|
-
}
|
|
1746
2129
|
function add(scan, file, line, severity, message) {
|
|
1747
2130
|
scan.findings.push({ path: file, line, severity, message });
|
|
1748
2131
|
}
|
|
@@ -1793,7 +2176,7 @@ function attributes2(raw) {
|
|
|
1793
2176
|
}
|
|
1794
2177
|
return found;
|
|
1795
2178
|
}
|
|
1796
|
-
function
|
|
2179
|
+
function hostOf2(value) {
|
|
1797
2180
|
if (!EXTERNAL.test(value) || IGNORED.test(value)) return void 0;
|
|
1798
2181
|
try {
|
|
1799
2182
|
const host = new URL(value, BASE_URL).hostname;
|
|
@@ -1817,7 +2200,7 @@ function resolvePath(file, ref) {
|
|
|
1817
2200
|
decoded = ref;
|
|
1818
2201
|
}
|
|
1819
2202
|
const wantsIndex = decoded.endsWith("/");
|
|
1820
|
-
const base =
|
|
2203
|
+
const base = path6.posix.dirname(file);
|
|
1821
2204
|
const parts2 = base === "." ? [] : base.split("/");
|
|
1822
2205
|
for (const segment of decoded.split("/")) {
|
|
1823
2206
|
if (segment === "" || segment === ".") continue;
|
|
@@ -1868,7 +2251,7 @@ function resolveRef(scan, file, raw, line) {
|
|
|
1868
2251
|
}
|
|
1869
2252
|
function checkScript(scan, file, line, attrs) {
|
|
1870
2253
|
const value = (attrs.get("src") ?? "").trim();
|
|
1871
|
-
const host =
|
|
2254
|
+
const host = hostOf2(value);
|
|
1872
2255
|
if (host === void 0) return;
|
|
1873
2256
|
if (!SCRIPT_HOSTS.includes(host)) {
|
|
1874
2257
|
add(scan, file, line, "error", `script host not allowed: ${value} (allowed: ${SCRIPT_HOSTS.join(", ")})`);
|
|
@@ -1884,7 +2267,7 @@ function checkLink(scan, file, line, attrs) {
|
|
|
1884
2267
|
const rel = (attrs.get("rel") ?? "").toLowerCase().split(/\s+/);
|
|
1885
2268
|
if (!rel.includes("stylesheet")) return;
|
|
1886
2269
|
const value = (attrs.get("href") ?? "").trim();
|
|
1887
|
-
const host =
|
|
2270
|
+
const host = hostOf2(value);
|
|
1888
2271
|
if (host === void 0 || STYLE_HOSTS.includes(host)) return;
|
|
1889
2272
|
add(scan, file, line, "error", `stylesheet host not allowed: ${value} (allowed: ${STYLE_HOSTS.join(", ")})`);
|
|
1890
2273
|
}
|
|
@@ -1940,9 +2323,9 @@ function scanHtml(scan, file, text, facts) {
|
|
|
1940
2323
|
scanConnect(scan, file, masked, at);
|
|
1941
2324
|
}
|
|
1942
2325
|
async function scanFile(scan, entry, facts) {
|
|
1943
|
-
const ext =
|
|
2326
|
+
const ext = path6.extname(entry.rel).toLowerCase();
|
|
1944
2327
|
if (!SCAN_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES3) return;
|
|
1945
|
-
const text = await
|
|
2328
|
+
const text = await readFile6(entry.abs, "utf8");
|
|
1946
2329
|
if (ext === ".html" || ext === ".htm") {
|
|
1947
2330
|
scanHtml(scan, entry.rel, text, facts);
|
|
1948
2331
|
return;
|
|
@@ -1957,7 +2340,7 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1957
2340
|
for (const item of items) {
|
|
1958
2341
|
if (item.name.startsWith(".") || SKIPPED_NAMES.has(item.name)) continue;
|
|
1959
2342
|
const rel = prefix === "" ? item.name : `${prefix}/${item.name}`;
|
|
1960
|
-
const abs =
|
|
2343
|
+
const abs = path6.join(dir, item.name);
|
|
1961
2344
|
const problem = pathProblem(rel);
|
|
1962
2345
|
if (problem !== void 0) add(scan, rel, 1, "error", problem);
|
|
1963
2346
|
if (item.isDirectory()) {
|
|
@@ -1965,7 +2348,7 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1965
2348
|
continue;
|
|
1966
2349
|
}
|
|
1967
2350
|
if (!item.isFile()) continue;
|
|
1968
|
-
const info = await
|
|
2351
|
+
const info = await stat3(abs).catch(() => void 0);
|
|
1969
2352
|
if (info === void 0) continue;
|
|
1970
2353
|
entries.push({ rel, abs, size: info.size });
|
|
1971
2354
|
}
|
|
@@ -1973,11 +2356,12 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1973
2356
|
async function checkThemeFile(scan, key2, rel, where) {
|
|
1974
2357
|
const url = `${scan.art}/_themes/${key2}/${rel}`;
|
|
1975
2358
|
let status2;
|
|
2359
|
+
await readyNetwork();
|
|
1976
2360
|
try {
|
|
1977
2361
|
const reply = await scan.fetchImpl(url, { method: "HEAD" });
|
|
1978
2362
|
status2 = reply.status;
|
|
1979
2363
|
} catch (err) {
|
|
1980
|
-
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${
|
|
2364
|
+
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${networkDetail(err, url)}`);
|
|
1981
2365
|
return;
|
|
1982
2366
|
}
|
|
1983
2367
|
if (status2 === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
|
|
@@ -1989,11 +2373,12 @@ async function checkTheme(scan, key2, where) {
|
|
|
1989
2373
|
if (scan.offline || scan.art === "") return;
|
|
1990
2374
|
const url = `${scan.art}/_themes/${key2}/tokens.css`;
|
|
1991
2375
|
let status2;
|
|
2376
|
+
await readyNetwork();
|
|
1992
2377
|
try {
|
|
1993
2378
|
const reply = await scan.fetchImpl(url, { method: "HEAD" });
|
|
1994
2379
|
status2 = reply.status;
|
|
1995
2380
|
} catch (err) {
|
|
1996
|
-
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${
|
|
2381
|
+
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${networkDetail(err, url)}`);
|
|
1997
2382
|
return;
|
|
1998
2383
|
}
|
|
1999
2384
|
if (status2 === 404) {
|
|
@@ -2011,8 +2396,8 @@ async function checkTheme(scan, key2, where) {
|
|
|
2011
2396
|
}
|
|
2012
2397
|
}
|
|
2013
2398
|
async function checkFolder(input, opts = {}) {
|
|
2014
|
-
const root2 =
|
|
2015
|
-
const info = await
|
|
2399
|
+
const root2 = path6.resolve(input);
|
|
2400
|
+
const info = await stat3(root2).catch(() => void 0);
|
|
2016
2401
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
2017
2402
|
const scan = {
|
|
2018
2403
|
files: /* @__PURE__ */ new Set(),
|
|
@@ -2104,7 +2489,7 @@ function reportCheck(out, result) {
|
|
|
2104
2489
|
out.line(summaryLine(result));
|
|
2105
2490
|
}
|
|
2106
2491
|
async function enforceCheck(out, target, opts = {}) {
|
|
2107
|
-
const info = await
|
|
2492
|
+
const info = await stat3(target).catch(() => void 0);
|
|
2108
2493
|
if (info === void 0 || !info.isDirectory()) return;
|
|
2109
2494
|
const result = await checkFolder(target, opts);
|
|
2110
2495
|
if (result.findings.length > 0) reportCheck(out, result);
|
|
@@ -2112,12 +2497,12 @@ async function enforceCheck(out, target, opts = {}) {
|
|
|
2112
2497
|
}
|
|
2113
2498
|
|
|
2114
2499
|
// src/publish.ts
|
|
2115
|
-
import { readFile as
|
|
2500
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
2116
2501
|
|
|
2117
2502
|
// src/files.ts
|
|
2118
2503
|
import { createHash } from "node:crypto";
|
|
2119
|
-
import { readdir as readdir2, readFile as
|
|
2120
|
-
import
|
|
2504
|
+
import { readdir as readdir2, readFile as readFile7, stat as stat4 } from "node:fs/promises";
|
|
2505
|
+
import path7 from "node:path";
|
|
2121
2506
|
var CONTENT_TYPES = {
|
|
2122
2507
|
html: "text/html; charset=utf-8",
|
|
2123
2508
|
css: "text/css; charset=utf-8",
|
|
@@ -2149,14 +2534,14 @@ var CONTENT_TYPES = {
|
|
|
2149
2534
|
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
2150
2535
|
var SKIPPED_NAMES2 = /* @__PURE__ */ new Set(["node_modules"]);
|
|
2151
2536
|
function contentTypeFor(filePath) {
|
|
2152
|
-
const ext =
|
|
2537
|
+
const ext = path7.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
2153
2538
|
return CONTENT_TYPES[ext] ?? DEFAULT_CONTENT_TYPE;
|
|
2154
2539
|
}
|
|
2155
2540
|
function sha256Hex(bytes) {
|
|
2156
2541
|
return createHash("sha256").update(bytes).digest("hex");
|
|
2157
2542
|
}
|
|
2158
2543
|
async function readEntry(abs, rel) {
|
|
2159
|
-
const bytes = await
|
|
2544
|
+
const bytes = await readFile7(abs);
|
|
2160
2545
|
return {
|
|
2161
2546
|
path: rel,
|
|
2162
2547
|
file: abs,
|
|
@@ -2174,7 +2559,7 @@ async function collectPaths(root2) {
|
|
|
2174
2559
|
if (entry.name.startsWith(".")) continue;
|
|
2175
2560
|
if (SKIPPED_NAMES2.has(entry.name)) continue;
|
|
2176
2561
|
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
2177
|
-
const abs =
|
|
2562
|
+
const abs = path7.join(dir, entry.name);
|
|
2178
2563
|
if (entry.isDirectory()) {
|
|
2179
2564
|
await walk2(abs, rel);
|
|
2180
2565
|
continue;
|
|
@@ -2187,8 +2572,8 @@ async function collectPaths(root2) {
|
|
|
2187
2572
|
return found;
|
|
2188
2573
|
}
|
|
2189
2574
|
async function buildManifest(input, opts = {}) {
|
|
2190
|
-
const root2 =
|
|
2191
|
-
const info = await
|
|
2575
|
+
const root2 = path7.resolve(input);
|
|
2576
|
+
const info = await stat4(root2).catch(() => void 0);
|
|
2192
2577
|
if (info === void 0) throw new UsageError(`no such file or folder: ${input}`);
|
|
2193
2578
|
const entries = [];
|
|
2194
2579
|
if (info.isFile()) {
|
|
@@ -2198,7 +2583,7 @@ async function buildManifest(input, opts = {}) {
|
|
|
2198
2583
|
if (opts.requireIndex !== false && !paths.includes("index.html")) {
|
|
2199
2584
|
throw new UsageError(`${input} has no index.html`);
|
|
2200
2585
|
}
|
|
2201
|
-
for (const rel of paths) entries.push(await readEntry(
|
|
2586
|
+
for (const rel of paths) entries.push(await readEntry(path7.join(root2, rel), rel));
|
|
2202
2587
|
} else {
|
|
2203
2588
|
throw new UsageError(`not a file or folder: ${input}`);
|
|
2204
2589
|
}
|
|
@@ -2316,7 +2701,7 @@ async function publishTarget(api, target, opts) {
|
|
|
2316
2701
|
let uploaded = 0;
|
|
2317
2702
|
await runPool(missing, UPLOAD_CONCURRENCY, async (hash) => {
|
|
2318
2703
|
const entry = byHash.get(hash);
|
|
2319
|
-
await api.putBlob(hash, await
|
|
2704
|
+
await api.putBlob(hash, await readFile8(entry.file));
|
|
2320
2705
|
uploaded += 1;
|
|
2321
2706
|
});
|
|
2322
2707
|
const version = await api.createVersion(opts.slug, { label: opts.label, files: manifest.files });
|
|
@@ -2344,7 +2729,7 @@ function sameFiles(local, remote) {
|
|
|
2344
2729
|
const paths = Object.keys(local);
|
|
2345
2730
|
const other = remote;
|
|
2346
2731
|
if (paths.length !== Object.keys(other).length) return false;
|
|
2347
|
-
return paths.every((
|
|
2732
|
+
return paths.every((path26) => other[path26]?.hash === local[path26]?.hash);
|
|
2348
2733
|
}
|
|
2349
2734
|
function reportUnchanged(out, result) {
|
|
2350
2735
|
out.data({ unchanged: true, slug: result.slug, version: result.version.n });
|
|
@@ -2372,26 +2757,19 @@ function validateSlug(slug) {
|
|
|
2372
2757
|
return slug;
|
|
2373
2758
|
}
|
|
2374
2759
|
|
|
2375
|
-
// src/warn.ts
|
|
2376
|
-
import process5 from "node:process";
|
|
2377
|
-
function warn2(message) {
|
|
2378
|
-
process5.stderr.write(`rshow: warning: ${message}
|
|
2379
|
-
`);
|
|
2380
|
-
}
|
|
2381
|
-
|
|
2382
2760
|
// src/commands/publish.ts
|
|
2383
2761
|
async function directoryOf(input) {
|
|
2384
|
-
const dir =
|
|
2385
|
-
const info = await
|
|
2762
|
+
const dir = path8.resolve(input);
|
|
2763
|
+
const info = await stat5(dir).catch(() => void 0);
|
|
2386
2764
|
return info !== void 0 && info.isDirectory() ? dir : void 0;
|
|
2387
2765
|
}
|
|
2388
2766
|
async function targetOf(input, dir, built, out) {
|
|
2389
|
-
if (dir === void 0) return
|
|
2390
|
-
if (built) return
|
|
2767
|
+
if (dir === void 0) return path8.resolve(input);
|
|
2768
|
+
if (built) return path8.resolve(dir, out);
|
|
2391
2769
|
const local = await readConfigFile(localConfigPath(dir));
|
|
2392
|
-
return local.out === void 0 ? dir :
|
|
2770
|
+
return local.out === void 0 ? dir : path8.resolve(dir, out);
|
|
2393
2771
|
}
|
|
2394
|
-
async function
|
|
2772
|
+
async function run(argv, ctx) {
|
|
2395
2773
|
const args = parseCommandArgs(
|
|
2396
2774
|
argv,
|
|
2397
2775
|
{
|
|
@@ -2418,7 +2796,7 @@ async function run2(argv, ctx) {
|
|
|
2418
2796
|
}
|
|
2419
2797
|
const target = await targetOf(input, dir, build !== void 0, config.out);
|
|
2420
2798
|
const slug = validateSlug(
|
|
2421
|
-
stringValue(args, "slug") ?? config.slug ?? deriveSlug(
|
|
2799
|
+
stringValue(args, "slug") ?? config.slug ?? deriveSlug(path8.basename(path8.resolve(input)))
|
|
2422
2800
|
);
|
|
2423
2801
|
const title = stringValue(args, "title") ?? config.title;
|
|
2424
2802
|
const label = stringValue(args, "label");
|
|
@@ -2429,7 +2807,7 @@ async function run2(argv, ctx) {
|
|
|
2429
2807
|
const opts = { slug, title, label, project, tags, force: boolValue(args, "force") };
|
|
2430
2808
|
if (!boolValue(args, "no-check")) {
|
|
2431
2809
|
opts.beforeUpload = () => {
|
|
2432
|
-
if (config.art === void 0)
|
|
2810
|
+
if (config.art === void 0) warn("no art host configured; skipping theme checks");
|
|
2433
2811
|
return enforceCheck(ctx.out, target, { art: config.art, offline: config.art === void 0 });
|
|
2434
2812
|
};
|
|
2435
2813
|
}
|
|
@@ -2453,9 +2831,9 @@ async function run2(argv, ctx) {
|
|
|
2453
2831
|
}
|
|
2454
2832
|
|
|
2455
2833
|
// src/commands/check.ts
|
|
2456
|
-
import { stat as
|
|
2457
|
-
import
|
|
2458
|
-
async function
|
|
2834
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
2835
|
+
import path9 from "node:path";
|
|
2836
|
+
async function run2(argv, ctx) {
|
|
2459
2837
|
const args = parseCommandArgs(
|
|
2460
2838
|
argv,
|
|
2461
2839
|
{ offline: { type: "boolean" }, "no-style": { type: "boolean" } },
|
|
@@ -2464,13 +2842,13 @@ async function run3(argv, ctx) {
|
|
|
2464
2842
|
CHECK_USAGE
|
|
2465
2843
|
);
|
|
2466
2844
|
const input = args.positionals[0];
|
|
2467
|
-
const dir =
|
|
2468
|
-
const info = await
|
|
2845
|
+
const dir = path9.resolve(input);
|
|
2846
|
+
const info = await stat6(dir).catch(() => void 0);
|
|
2469
2847
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
2470
2848
|
const config = await ctx.config(dir);
|
|
2471
2849
|
const offline = boolValue(args, "offline");
|
|
2472
2850
|
const art = config.art;
|
|
2473
|
-
if (art === void 0 && !offline)
|
|
2851
|
+
if (art === void 0 && !offline) warn("no art host configured; skipping theme checks");
|
|
2474
2852
|
const result = await checkFolder(dir, {
|
|
2475
2853
|
art,
|
|
2476
2854
|
offline: offline || art === void 0,
|
|
@@ -2488,21 +2866,21 @@ async function run3(argv, ctx) {
|
|
|
2488
2866
|
}
|
|
2489
2867
|
|
|
2490
2868
|
// src/commands/preview.ts
|
|
2491
|
-
import { stat as
|
|
2492
|
-
import
|
|
2869
|
+
import { stat as stat9 } from "node:fs/promises";
|
|
2870
|
+
import path12 from "node:path";
|
|
2493
2871
|
|
|
2494
2872
|
// src/serve.ts
|
|
2495
2873
|
import { watch } from "node:fs";
|
|
2496
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
2874
|
+
import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm3, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
|
|
2497
2875
|
import { createHash as createHash2 } from "node:crypto";
|
|
2498
2876
|
import { createServer } from "node:http";
|
|
2499
2877
|
import { tmpdir } from "node:os";
|
|
2500
|
-
import
|
|
2501
|
-
import
|
|
2878
|
+
import path11 from "node:path";
|
|
2879
|
+
import process11 from "node:process";
|
|
2502
2880
|
|
|
2503
2881
|
// src/themes.ts
|
|
2504
|
-
import { cp, mkdir as
|
|
2505
|
-
import
|
|
2882
|
+
import { cp, mkdir as mkdir3, readFile as readFile9, rm as rm2, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
|
|
2883
|
+
import path10 from "node:path";
|
|
2506
2884
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2507
2885
|
|
|
2508
2886
|
// src/color.ts
|
|
@@ -2837,7 +3215,7 @@ function flattenDtcg(tree) {
|
|
|
2837
3215
|
function sourceDir() {
|
|
2838
3216
|
try {
|
|
2839
3217
|
const url = import.meta.url;
|
|
2840
|
-
return typeof url === "string" && url !== "" ?
|
|
3218
|
+
return typeof url === "string" && url !== "" ? path10.dirname(fileURLToPath2(url)) : void 0;
|
|
2841
3219
|
} catch {
|
|
2842
3220
|
return void 0;
|
|
2843
3221
|
}
|
|
@@ -2845,7 +3223,7 @@ function sourceDir() {
|
|
|
2845
3223
|
var HERE = sourceDir();
|
|
2846
3224
|
function nearSource(...segments) {
|
|
2847
3225
|
if (HERE === void 0) return [];
|
|
2848
|
-
return [
|
|
3226
|
+
return [path10.resolve(HERE, "..", "..", "..", ...segments), path10.resolve(HERE, "..", ...segments)];
|
|
2849
3227
|
}
|
|
2850
3228
|
var BUILTIN_BASES = nearSource("themes");
|
|
2851
3229
|
var BUILTIN_SKILL_BASES = nearSource("skills");
|
|
@@ -2863,7 +3241,7 @@ var FONT_FORMATS = {
|
|
|
2863
3241
|
".ttf": "truetype",
|
|
2864
3242
|
".otf": "opentype"
|
|
2865
3243
|
};
|
|
2866
|
-
function
|
|
3244
|
+
function isRecord3(value) {
|
|
2867
3245
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2868
3246
|
}
|
|
2869
3247
|
function stringField(value) {
|
|
@@ -2873,16 +3251,16 @@ function messageOf6(err) {
|
|
|
2873
3251
|
return err instanceof Error ? err.message : String(err);
|
|
2874
3252
|
}
|
|
2875
3253
|
async function isFile(file) {
|
|
2876
|
-
const info = await
|
|
3254
|
+
const info = await stat7(file).catch(() => void 0);
|
|
2877
3255
|
return info !== void 0 && info.isFile();
|
|
2878
3256
|
}
|
|
2879
3257
|
async function isDir(dir) {
|
|
2880
|
-
const info = await
|
|
3258
|
+
const info = await stat7(dir).catch(() => void 0);
|
|
2881
3259
|
return info !== void 0 && info.isDirectory();
|
|
2882
3260
|
}
|
|
2883
3261
|
function tokenMap(value) {
|
|
2884
3262
|
const out = {};
|
|
2885
|
-
if (!
|
|
3263
|
+
if (!isRecord3(value)) return out;
|
|
2886
3264
|
for (const [name, raw] of Object.entries(value)) {
|
|
2887
3265
|
if (typeof raw === "string" && raw.trim() !== "") out[name] = raw.trim();
|
|
2888
3266
|
}
|
|
@@ -2892,7 +3270,7 @@ function fontList(value) {
|
|
|
2892
3270
|
if (!Array.isArray(value)) return [];
|
|
2893
3271
|
const out = [];
|
|
2894
3272
|
for (const item of value) {
|
|
2895
|
-
if (!
|
|
3273
|
+
if (!isRecord3(item)) continue;
|
|
2896
3274
|
const family = stringField(item.family);
|
|
2897
3275
|
const file = stringField(item.file);
|
|
2898
3276
|
if (family === void 0 || file === void 0) continue;
|
|
@@ -2903,8 +3281,8 @@ function fontList(value) {
|
|
|
2903
3281
|
return out;
|
|
2904
3282
|
}
|
|
2905
3283
|
function themeValues(json) {
|
|
2906
|
-
const doc =
|
|
2907
|
-
const modes =
|
|
3284
|
+
const doc = isRecord3(json) ? json : {};
|
|
3285
|
+
const modes = isRecord3(doc.modeTokens) ? doc.modeTokens : {};
|
|
2908
3286
|
return {
|
|
2909
3287
|
tokens: tokenMap(doc.tokens),
|
|
2910
3288
|
light: tokenMap(modes.light),
|
|
@@ -2912,10 +3290,10 @@ function themeValues(json) {
|
|
|
2912
3290
|
};
|
|
2913
3291
|
}
|
|
2914
3292
|
async function readThemeJson(dir) {
|
|
2915
|
-
const file =
|
|
3293
|
+
const file = path10.join(dir, "theme.json");
|
|
2916
3294
|
let text;
|
|
2917
3295
|
try {
|
|
2918
|
-
text = await
|
|
3296
|
+
text = await readFile9(file, "utf8");
|
|
2919
3297
|
} catch (err) {
|
|
2920
3298
|
if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
|
|
2921
3299
|
throw new UsageError(`cannot read ${file}: ${messageOf6(err)}`);
|
|
@@ -2926,13 +3304,13 @@ async function readThemeJson(dir) {
|
|
|
2926
3304
|
} catch {
|
|
2927
3305
|
throw new UsageError(`invalid JSON in ${file}`);
|
|
2928
3306
|
}
|
|
2929
|
-
if (!
|
|
3307
|
+
if (!isRecord3(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
2930
3308
|
return { dir, text, json: parsed };
|
|
2931
3309
|
}
|
|
2932
3310
|
async function builtinThemeDir(name, bases = BUILTIN_BASES) {
|
|
2933
3311
|
if (!THEME_NAME_PATTERN.test(name)) return void 0;
|
|
2934
3312
|
for (const base of [...await binaryThemeBases(), ...bases]) {
|
|
2935
|
-
const dir =
|
|
3313
|
+
const dir = path10.join(base, name);
|
|
2936
3314
|
if (await isDir(dir)) return dir;
|
|
2937
3315
|
}
|
|
2938
3316
|
return void 0;
|
|
@@ -2968,8 +3346,8 @@ function stripPrint(css) {
|
|
|
2968
3346
|
return out;
|
|
2969
3347
|
}
|
|
2970
3348
|
async function themeTokenSets(dir) {
|
|
2971
|
-
const file =
|
|
2972
|
-
const text = await
|
|
3349
|
+
const file = path10.join(dir, "tokens.css");
|
|
3350
|
+
const text = await readFile9(file, "utf8").catch(() => void 0);
|
|
2973
3351
|
return extractTokens(text === void 0 ? [] : [stripPrint(text)]);
|
|
2974
3352
|
}
|
|
2975
3353
|
function declMap(list3) {
|
|
@@ -3022,7 +3400,7 @@ function presetFontCss(fonts) {
|
|
|
3022
3400
|
lines.push(` font-weight: ${font.weight ?? "400"};`);
|
|
3023
3401
|
lines.push(" font-display: swap;");
|
|
3024
3402
|
const url = fontUrl(font.file);
|
|
3025
|
-
const format = FONT_FORMATS[
|
|
3403
|
+
const format = FONT_FORMATS[path10.extname(url).toLowerCase()];
|
|
3026
3404
|
const source = format === void 0 ? `url("${url}")` : `url("${url}") format("${format}")`;
|
|
3027
3405
|
lines.push(` src: ${source};`);
|
|
3028
3406
|
return `@font-face {
|
|
@@ -3047,15 +3425,15 @@ function mergeTheme(base, preset, baseName) {
|
|
|
3047
3425
|
}
|
|
3048
3426
|
async function appendFile(file, addition) {
|
|
3049
3427
|
if (addition === "") return;
|
|
3050
|
-
const before = await
|
|
3428
|
+
const before = await readFile9(file, "utf8").catch(() => "");
|
|
3051
3429
|
const glue = before === "" || before.endsWith("\n") ? "" : "\n";
|
|
3052
|
-
await
|
|
3430
|
+
await writeFile3(file, `${before}${glue}${addition}`);
|
|
3053
3431
|
}
|
|
3054
3432
|
async function buildPreset(presetDir, outDir) {
|
|
3055
3433
|
const preset = await readThemeJson(presetDir);
|
|
3056
3434
|
const baseName = stringField(preset.json.extends);
|
|
3057
3435
|
await rm2(outDir, { recursive: true, force: true });
|
|
3058
|
-
await
|
|
3436
|
+
await mkdir3(outDir, { recursive: true });
|
|
3059
3437
|
if (baseName === void 0) {
|
|
3060
3438
|
await cp(presetDir, outDir, { recursive: true });
|
|
3061
3439
|
return;
|
|
@@ -3066,15 +3444,15 @@ async function buildPreset(presetDir, outDir) {
|
|
|
3066
3444
|
await cp(baseDir, outDir, { recursive: true });
|
|
3067
3445
|
const values = themeValues(preset.json);
|
|
3068
3446
|
const name = stringField(preset.json.name) ?? baseName;
|
|
3069
|
-
await appendFile(
|
|
3447
|
+
await appendFile(path10.join(outDir, "tokens.css"), presetTokenCss(name, values));
|
|
3070
3448
|
const fonts = fontList(preset.json.fonts);
|
|
3071
3449
|
if (fonts.length > 0) {
|
|
3072
|
-
const from =
|
|
3073
|
-
if (await isDir(from)) await cp(from,
|
|
3074
|
-
await appendFile(
|
|
3450
|
+
const from = path10.join(presetDir, "fonts");
|
|
3451
|
+
if (await isDir(from)) await cp(from, path10.join(outDir, "fonts"), { recursive: true });
|
|
3452
|
+
await appendFile(path10.join(outDir, "fonts.css"), presetFontCss(fonts));
|
|
3075
3453
|
}
|
|
3076
3454
|
const merged = mergeTheme(base.json, preset.json, baseName);
|
|
3077
|
-
await
|
|
3455
|
+
await writeFile3(path10.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
|
|
3078
3456
|
`);
|
|
3079
3457
|
}
|
|
3080
3458
|
function keyLine(text, key2, mode) {
|
|
@@ -3089,8 +3467,8 @@ function keyLine(text, key2, mode) {
|
|
|
3089
3467
|
}
|
|
3090
3468
|
return 0;
|
|
3091
3469
|
}
|
|
3092
|
-
function finding(
|
|
3093
|
-
return { path:
|
|
3470
|
+
function finding(path26, line, severity, message) {
|
|
3471
|
+
return { path: path26, line: Math.max(1, line), severity, message };
|
|
3094
3472
|
}
|
|
3095
3473
|
function resolveValue(name, map, fallback) {
|
|
3096
3474
|
let current = map[name] ?? fallback[name];
|
|
@@ -3188,13 +3566,13 @@ async function checkPreset(presetDir) {
|
|
|
3188
3566
|
}
|
|
3189
3567
|
const fonts = fontList(preset.json.fonts);
|
|
3190
3568
|
for (const font of fonts) {
|
|
3191
|
-
const file =
|
|
3569
|
+
const file = path10.join(presetDir, font.file);
|
|
3192
3570
|
if (!await isFile(file)) {
|
|
3193
3571
|
findings.push(finding("theme.json", keyLine(text, "file"), "error", `missing font file: ${font.file}`));
|
|
3194
3572
|
}
|
|
3195
3573
|
}
|
|
3196
3574
|
if (fonts.length > 0) {
|
|
3197
|
-
const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(
|
|
3575
|
+
const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path10.join(presetDir, "fonts", rel))));
|
|
3198
3576
|
if (!found.some(Boolean)) {
|
|
3199
3577
|
findings.push(
|
|
3200
3578
|
finding(
|
|
@@ -3292,7 +3670,7 @@ var SCRIPT_TYPE = "text/javascript; charset=utf-8";
|
|
|
3292
3670
|
function messageOf7(err) {
|
|
3293
3671
|
return err instanceof Error ? err.message : String(err);
|
|
3294
3672
|
}
|
|
3295
|
-
function
|
|
3673
|
+
function isRecord4(value) {
|
|
3296
3674
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3297
3675
|
}
|
|
3298
3676
|
function etagOf(text) {
|
|
@@ -3327,25 +3705,26 @@ async function resolveFile(root2, pathname) {
|
|
|
3327
3705
|
return void 0;
|
|
3328
3706
|
}
|
|
3329
3707
|
if (decoded.includes("\0")) return void 0;
|
|
3330
|
-
const target =
|
|
3331
|
-
const rel =
|
|
3332
|
-
if (rel.startsWith("..") ||
|
|
3333
|
-
const info = await
|
|
3708
|
+
const target = path11.resolve(root2, decoded.replace(/^\/+/, ""));
|
|
3709
|
+
const rel = path11.relative(root2, target);
|
|
3710
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) return void 0;
|
|
3711
|
+
const info = await stat8(target).catch(() => void 0);
|
|
3334
3712
|
if (info === void 0) return void 0;
|
|
3335
3713
|
if (info.isFile()) return target;
|
|
3336
3714
|
if (!info.isDirectory()) return void 0;
|
|
3337
|
-
const index =
|
|
3338
|
-
const indexInfo = await
|
|
3715
|
+
const index = path11.join(target, "index.html");
|
|
3716
|
+
const indexInfo = await stat8(index).catch(() => void 0);
|
|
3339
3717
|
if (indexInfo === void 0 || !indexInfo.isFile()) return void 0;
|
|
3340
3718
|
return index;
|
|
3341
3719
|
}
|
|
3342
3720
|
async function proxy(art, pathname, method, res) {
|
|
3343
3721
|
let reply;
|
|
3722
|
+
await readyNetwork();
|
|
3344
3723
|
try {
|
|
3345
3724
|
reply = await fetch(art + pathname, { method: method === "HEAD" ? "HEAD" : "GET" });
|
|
3346
3725
|
} catch (err) {
|
|
3347
3726
|
res.writeHead(502, { "content-type": TEXT_TYPE, "x-rshow-source": "proxy" });
|
|
3348
|
-
res.end(`proxy error: ${
|
|
3727
|
+
res.end(`proxy error: ${networkText(err, art + pathname)}
|
|
3349
3728
|
`);
|
|
3350
3729
|
return;
|
|
3351
3730
|
}
|
|
@@ -3361,10 +3740,10 @@ async function proxy(art, pathname, method, res) {
|
|
|
3361
3740
|
async function prepareTheme(dir) {
|
|
3362
3741
|
const doc = await readThemeJson(dir);
|
|
3363
3742
|
const declared = typeof doc.json.name === "string" ? doc.json.name.trim() : "";
|
|
3364
|
-
const name = declared === "" ?
|
|
3743
|
+
const name = declared === "" ? path11.basename(dir) : declared;
|
|
3365
3744
|
const base = typeof doc.json.extends === "string" ? doc.json.extends.trim() : "";
|
|
3366
3745
|
if (base === "") return { name, source: dir, built: dir, label: "local" };
|
|
3367
|
-
const built = await mkdtemp2(
|
|
3746
|
+
const built = await mkdtemp2(path11.join(tmpdir(), "rshow-theme-"));
|
|
3368
3747
|
await buildPreset(dir, built);
|
|
3369
3748
|
return { name, source: dir, built, label: "preset" };
|
|
3370
3749
|
}
|
|
@@ -3435,9 +3814,9 @@ function createDataStore() {
|
|
|
3435
3814
|
const values = /* @__PURE__ */ new Map();
|
|
3436
3815
|
let dir;
|
|
3437
3816
|
const persist = async () => {
|
|
3438
|
-
if (dir === void 0) dir = await mkdtemp2(
|
|
3817
|
+
if (dir === void 0) dir = await mkdtemp2(path11.join(tmpdir(), "rshow-data-"));
|
|
3439
3818
|
const doc = Object.fromEntries(values);
|
|
3440
|
-
await
|
|
3819
|
+
await writeFile4(path11.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
|
|
3441
3820
|
`);
|
|
3442
3821
|
};
|
|
3443
3822
|
return {
|
|
@@ -3483,9 +3862,9 @@ async function readBody(req, limit) {
|
|
|
3483
3862
|
function valueText(value) {
|
|
3484
3863
|
return JSON.stringify(value === void 0 ? null : value);
|
|
3485
3864
|
}
|
|
3486
|
-
async function handleData(req, res,
|
|
3865
|
+
async function handleData(req, res, store2, key2, method) {
|
|
3487
3866
|
if (method === "GET" || method === "HEAD") {
|
|
3488
|
-
const entry =
|
|
3867
|
+
const entry = store2.get(key2);
|
|
3489
3868
|
if (entry === void 0) {
|
|
3490
3869
|
jsonError(res, "not found", 404);
|
|
3491
3870
|
return;
|
|
@@ -3526,12 +3905,12 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3526
3905
|
jsonError(res, "expected a JSON body", 400);
|
|
3527
3906
|
return;
|
|
3528
3907
|
}
|
|
3529
|
-
if (!
|
|
3908
|
+
if (!isRecord4(parsed) || !("value" in parsed)) {
|
|
3530
3909
|
jsonError(res, "expected {value}", 400);
|
|
3531
3910
|
return;
|
|
3532
3911
|
}
|
|
3533
3912
|
const text = valueText(parsed.value);
|
|
3534
|
-
const existing =
|
|
3913
|
+
const existing = store2.get(key2);
|
|
3535
3914
|
const ifMatch = req.headers["if-match"] ?? null;
|
|
3536
3915
|
if (ifMatch !== null && ifMatch.trim() !== "") {
|
|
3537
3916
|
if (existing === void 0 || !etagMatches(ifMatch, etagOf(existing.text))) {
|
|
@@ -3539,7 +3918,7 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3539
3918
|
return;
|
|
3540
3919
|
}
|
|
3541
3920
|
}
|
|
3542
|
-
if (existing === void 0 &&
|
|
3921
|
+
if (existing === void 0 && store2.count() >= MAX_DATA_KEYS) {
|
|
3543
3922
|
jsonError(res, "too many keys for this artifact", 409);
|
|
3544
3923
|
return;
|
|
3545
3924
|
}
|
|
@@ -3547,7 +3926,7 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3547
3926
|
jsonError(res, "value too large", 413);
|
|
3548
3927
|
return;
|
|
3549
3928
|
}
|
|
3550
|
-
const written = await
|
|
3929
|
+
const written = await store2.write(key2, text);
|
|
3551
3930
|
jsonReply(res, 200, { ok: true, key: key2, updated_at: written.updated_at }, { etag: written.etag });
|
|
3552
3931
|
}
|
|
3553
3932
|
function subscribe(res, session) {
|
|
@@ -3578,7 +3957,7 @@ function injectReload(html) {
|
|
|
3578
3957
|
${html.slice(at)}`;
|
|
3579
3958
|
}
|
|
3580
3959
|
async function sendFile(res, method, session, file, headers = {}) {
|
|
3581
|
-
const bytes = await
|
|
3960
|
+
const bytes = await readFile10(file);
|
|
3582
3961
|
const type = contentTypeFor(file);
|
|
3583
3962
|
const all = { "content-type": type, "cache-control": "no-store", ...headers };
|
|
3584
3963
|
if (method === "HEAD") {
|
|
@@ -3603,7 +3982,7 @@ function startWatchers(session, targets) {
|
|
|
3603
3982
|
const done = rebuild ? buildPreset(theme.source, theme.built) : Promise.resolve();
|
|
3604
3983
|
done.then(
|
|
3605
3984
|
() => broadcast(session),
|
|
3606
|
-
(err) =>
|
|
3985
|
+
(err) => warn(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
|
|
3607
3986
|
);
|
|
3608
3987
|
}, DEBOUNCE_MS);
|
|
3609
3988
|
timer.unref();
|
|
@@ -3613,7 +3992,7 @@ function startWatchers(session, targets) {
|
|
|
3613
3992
|
watcher.on("error", () => void 0);
|
|
3614
3993
|
watchers.push(watcher);
|
|
3615
3994
|
} catch (err) {
|
|
3616
|
-
|
|
3995
|
+
warn(`cannot watch ${target.dir}: ${messageOf7(err)}`);
|
|
3617
3996
|
}
|
|
3618
3997
|
}
|
|
3619
3998
|
return watchers;
|
|
@@ -3656,16 +4035,16 @@ async function handle(req, res, session) {
|
|
|
3656
4035
|
}
|
|
3657
4036
|
function closeOnSignals(preview2) {
|
|
3658
4037
|
const stop = () => {
|
|
3659
|
-
preview2.close().catch(() => void 0).finally(() =>
|
|
4038
|
+
preview2.close().catch(() => void 0).finally(() => process11.exit(0));
|
|
3660
4039
|
};
|
|
3661
|
-
|
|
3662
|
-
|
|
4040
|
+
process11.once("SIGINT", stop);
|
|
4041
|
+
process11.once("SIGTERM", stop);
|
|
3663
4042
|
}
|
|
3664
4043
|
async function startPreview(opts) {
|
|
3665
|
-
const dir =
|
|
4044
|
+
const dir = path11.resolve(opts.dir);
|
|
3666
4045
|
const art = (opts.art ?? "").replace(/\/+$/, "");
|
|
3667
4046
|
const host = opts.host ?? "127.0.0.1";
|
|
3668
|
-
const theme = opts.theme === void 0 ? void 0 : await prepareTheme(
|
|
4047
|
+
const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path11.resolve(opts.theme));
|
|
3669
4048
|
const root2 = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
|
|
3670
4049
|
const session = {
|
|
3671
4050
|
root: root2,
|
|
@@ -3733,12 +4112,12 @@ function portOf(value) {
|
|
|
3733
4112
|
return port;
|
|
3734
4113
|
}
|
|
3735
4114
|
async function requireFolder(input, what) {
|
|
3736
|
-
const dir =
|
|
3737
|
-
const info = await
|
|
4115
|
+
const dir = path12.resolve(input);
|
|
4116
|
+
const info = await stat9(dir).catch(() => void 0);
|
|
3738
4117
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such ${what}: ${input}`);
|
|
3739
4118
|
return dir;
|
|
3740
4119
|
}
|
|
3741
|
-
async function
|
|
4120
|
+
async function run3(argv, ctx) {
|
|
3742
4121
|
const args = parseCommandArgs(
|
|
3743
4122
|
argv,
|
|
3744
4123
|
{
|
|
@@ -3757,7 +4136,7 @@ async function run4(argv, ctx) {
|
|
|
3757
4136
|
const offline = boolValue(args, "offline");
|
|
3758
4137
|
const port = portOf(stringValue(args, "port"));
|
|
3759
4138
|
const config = await ctx.config(dir);
|
|
3760
|
-
if (config.art === void 0 && !offline)
|
|
4139
|
+
if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
|
|
3761
4140
|
const preview2 = await startPreview({
|
|
3762
4141
|
dir,
|
|
3763
4142
|
art: offline ? "" : config.art ?? "",
|
|
@@ -3781,7 +4160,14 @@ function themeRefOf(theme) {
|
|
|
3781
4160
|
return { name: theme.slice(0, at), version: theme.slice(at + 1) };
|
|
3782
4161
|
}
|
|
3783
4162
|
async function fetchVersion(art, name) {
|
|
3784
|
-
const
|
|
4163
|
+
const url = `${art}/_themes/${name}.json`;
|
|
4164
|
+
await readyNetwork();
|
|
4165
|
+
let reply;
|
|
4166
|
+
try {
|
|
4167
|
+
reply = await fetch(url, { signal: AbortSignal.timeout(THEME_TIMEOUT_MS) });
|
|
4168
|
+
} catch (err) {
|
|
4169
|
+
throw new Error(networkDetail(err, url));
|
|
4170
|
+
}
|
|
3785
4171
|
if (reply.status !== 200) throw new Error(`HTTP ${reply.status}`);
|
|
3786
4172
|
const body = await reply.json();
|
|
3787
4173
|
const version = body?.version;
|
|
@@ -3800,17 +4186,17 @@ async function resolveTheme2(config) {
|
|
|
3800
4186
|
const ref = themeRefOf(theme);
|
|
3801
4187
|
if (ref.version !== void 0 && ref.version !== "latest") return config;
|
|
3802
4188
|
if (config.art === void 0) {
|
|
3803
|
-
|
|
4189
|
+
warn("no art host configured; skipping theme version lookup");
|
|
3804
4190
|
return config;
|
|
3805
4191
|
}
|
|
3806
4192
|
try {
|
|
3807
4193
|
return { ...config, theme: await resolvedTheme(config.art, theme) };
|
|
3808
4194
|
} catch (err) {
|
|
3809
|
-
|
|
4195
|
+
warn(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
|
|
3810
4196
|
return config.theme === void 0 ? config : { ...config, theme: ref.name };
|
|
3811
4197
|
}
|
|
3812
4198
|
}
|
|
3813
|
-
async function
|
|
4199
|
+
async function run4(argv, ctx) {
|
|
3814
4200
|
const args = parseCommandArgs(argv, { offline: { type: "boolean" } }, 0, 1, CONFIG_USAGE);
|
|
3815
4201
|
const dir = args.positionals[0];
|
|
3816
4202
|
const loaded = await ctx.config(dir);
|
|
@@ -3820,14 +4206,14 @@ async function run5(argv, ctx) {
|
|
|
3820
4206
|
}
|
|
3821
4207
|
|
|
3822
4208
|
// src/commands/init.ts
|
|
3823
|
-
import { mkdir as
|
|
3824
|
-
import
|
|
3825
|
-
import
|
|
4209
|
+
import { mkdir as mkdir4, readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
|
|
4210
|
+
import path13 from "node:path";
|
|
4211
|
+
import process12 from "node:process";
|
|
3826
4212
|
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
|
|
3827
4213
|
function messageOf9(err) {
|
|
3828
4214
|
return err instanceof Error ? err.message : String(err);
|
|
3829
4215
|
}
|
|
3830
|
-
function
|
|
4216
|
+
function isRecord5(value) {
|
|
3831
4217
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3832
4218
|
}
|
|
3833
4219
|
function serverUrl(value, flag) {
|
|
@@ -3849,16 +4235,16 @@ function serverUrl(value, flag) {
|
|
|
3849
4235
|
async function rawConfig(file) {
|
|
3850
4236
|
let text;
|
|
3851
4237
|
try {
|
|
3852
|
-
text = await
|
|
4238
|
+
text = await readFile11(file, "utf8");
|
|
3853
4239
|
} catch (err) {
|
|
3854
4240
|
if (err.code === "ENOENT") return {};
|
|
3855
4241
|
throw new UsageError(`cannot read ${file}: ${messageOf9(err)}`);
|
|
3856
4242
|
}
|
|
3857
4243
|
const parsed = JSON.parse(text);
|
|
3858
|
-
if (!
|
|
4244
|
+
if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
3859
4245
|
return parsed;
|
|
3860
4246
|
}
|
|
3861
|
-
async function
|
|
4247
|
+
async function run5(argv, ctx) {
|
|
3862
4248
|
const args = parseCommandArgs(
|
|
3863
4249
|
argv,
|
|
3864
4250
|
{
|
|
@@ -3874,7 +4260,7 @@ async function run6(argv, ctx) {
|
|
|
3874
4260
|
const url = serverUrl(stringValue(args, "url"), "url");
|
|
3875
4261
|
const art = serverUrl(stringValue(args, "art"), "art");
|
|
3876
4262
|
const theme = (stringValue(args, "theme") ?? "").trim();
|
|
3877
|
-
const file = globalConfigPath(
|
|
4263
|
+
const file = globalConfigPath(process12.env);
|
|
3878
4264
|
const current = await readConfigFile(file);
|
|
3879
4265
|
const same = (have, want) => have === void 0 || have.replace(/\/+$/, "") === want;
|
|
3880
4266
|
if (!boolValue(args, "force")) {
|
|
@@ -3889,8 +4275,8 @@ async function run6(argv, ctx) {
|
|
|
3889
4275
|
raw.url = url;
|
|
3890
4276
|
raw.art = art;
|
|
3891
4277
|
if (theme !== "") raw.theme = theme;
|
|
3892
|
-
await
|
|
3893
|
-
await
|
|
4278
|
+
await mkdir4(path13.dirname(file), { recursive: true });
|
|
4279
|
+
await writeFile5(file, `${JSON.stringify(raw, null, 2)}
|
|
3894
4280
|
`, "utf8");
|
|
3895
4281
|
ctx.out.data({ path: file, url, art });
|
|
3896
4282
|
ctx.out.line(`wrote ${file}`);
|
|
@@ -3898,15 +4284,15 @@ async function run6(argv, ctx) {
|
|
|
3898
4284
|
}
|
|
3899
4285
|
|
|
3900
4286
|
// src/commands/doctor.ts
|
|
3901
|
-
import { readFile as
|
|
3902
|
-
import
|
|
3903
|
-
import
|
|
4287
|
+
import { readFile as readFile13, stat as stat11 } from "node:fs/promises";
|
|
4288
|
+
import path15 from "node:path";
|
|
4289
|
+
import process13 from "node:process";
|
|
3904
4290
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3905
4291
|
|
|
3906
4292
|
// src/skills.ts
|
|
3907
|
-
import { cp as cp2, lstat, mkdir as
|
|
3908
|
-
import { homedir as
|
|
3909
|
-
import
|
|
4293
|
+
import { cp as cp2, lstat, mkdir as mkdir5, readFile as readFile12, readdir as readdir3, readlink, rm as rm4, stat as stat10, symlink, writeFile as writeFile6 } from "node:fs/promises";
|
|
4294
|
+
import { homedir as homedir4 } from "node:os";
|
|
4295
|
+
import path14 from "node:path";
|
|
3910
4296
|
var SKILL_TARGETS = ["claude", "pi", "codex"];
|
|
3911
4297
|
var SKILL_MARKER = ".rshow-skill";
|
|
3912
4298
|
var SKILL_SOURCE = "rshow";
|
|
@@ -3916,26 +4302,26 @@ var TARGET_SEGMENTS = {
|
|
|
3916
4302
|
codex: [".codex"]
|
|
3917
4303
|
};
|
|
3918
4304
|
async function isDir2(dir) {
|
|
3919
|
-
const info = await
|
|
4305
|
+
const info = await stat10(dir).catch(() => void 0);
|
|
3920
4306
|
return info !== void 0 && info.isDirectory();
|
|
3921
4307
|
}
|
|
3922
4308
|
async function isFile2(file) {
|
|
3923
|
-
const info = await
|
|
4309
|
+
const info = await stat10(file).catch(() => void 0);
|
|
3924
4310
|
return info !== void 0 && info.isFile();
|
|
3925
4311
|
}
|
|
3926
4312
|
function isUnder(file, dir) {
|
|
3927
|
-
const rel =
|
|
3928
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
4313
|
+
const rel = path14.relative(dir, file);
|
|
4314
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
|
|
3929
4315
|
}
|
|
3930
4316
|
function homeDir(env) {
|
|
3931
4317
|
const home = (env.HOME ?? "").trim();
|
|
3932
|
-
return home !== "" ? home :
|
|
4318
|
+
return home !== "" ? home : homedir4();
|
|
3933
4319
|
}
|
|
3934
4320
|
function targetParent(home, target) {
|
|
3935
|
-
return
|
|
4321
|
+
return path14.join(home, ...TARGET_SEGMENTS[target]);
|
|
3936
4322
|
}
|
|
3937
4323
|
function targetSkillsDir(home, target) {
|
|
3938
|
-
return
|
|
4324
|
+
return path14.join(targetParent(home, target), "skills");
|
|
3939
4325
|
}
|
|
3940
4326
|
async function detectedTargets(home) {
|
|
3941
4327
|
const out = [];
|
|
@@ -3959,13 +4345,13 @@ function targetHint() {
|
|
|
3959
4345
|
return SKILL_TARGETS.join(", ");
|
|
3960
4346
|
}
|
|
3961
4347
|
function defaultModeOf(dir) {
|
|
3962
|
-
return dir.split(
|
|
4348
|
+
return dir.split(path14.sep).join("/").includes("/_npx/") ? "copy" : "link";
|
|
3963
4349
|
}
|
|
3964
4350
|
async function skillsSource(env) {
|
|
3965
4351
|
const override = (env.RSHOW_SKILLS_DIR ?? "").trim();
|
|
3966
4352
|
let dir;
|
|
3967
4353
|
if (override !== "") {
|
|
3968
|
-
dir =
|
|
4354
|
+
dir = path14.resolve(override);
|
|
3969
4355
|
if (!await isDir2(dir)) throw new UsageError(`RSHOW_SKILLS_DIR is not a folder: ${dir}`);
|
|
3970
4356
|
} else {
|
|
3971
4357
|
dir = await builtinSkillsDir();
|
|
@@ -3973,22 +4359,22 @@ async function skillsSource(env) {
|
|
|
3973
4359
|
if (dir === void 0) return void 0;
|
|
3974
4360
|
const skills = [];
|
|
3975
4361
|
for (const entry of await readdir3(dir, { withFileTypes: true }).catch(() => [])) {
|
|
3976
|
-
if (entry.isDirectory() && await isFile2(
|
|
4362
|
+
if (entry.isDirectory() && await isFile2(path14.join(dir, entry.name, "SKILL.md"))) skills.push(entry.name);
|
|
3977
4363
|
}
|
|
3978
4364
|
skills.sort();
|
|
3979
4365
|
return { dir, mode: await isBinary() ? "copy" : defaultModeOf(dir), skills };
|
|
3980
4366
|
}
|
|
3981
4367
|
function isOurLink(target, source) {
|
|
3982
|
-
const resolved =
|
|
3983
|
-
if (isUnder(resolved,
|
|
3984
|
-
return /(^|\/)rshow\/skills\//.test(resolved.split(
|
|
4368
|
+
const resolved = path14.resolve(target);
|
|
4369
|
+
if (isUnder(resolved, path14.resolve(source))) return true;
|
|
4370
|
+
return /(^|\/)rshow\/skills\//.test(resolved.split(path14.sep).join("/"));
|
|
3985
4371
|
}
|
|
3986
4372
|
async function linkTargetOf(dst) {
|
|
3987
4373
|
const value = await readlink(dst);
|
|
3988
|
-
return
|
|
4374
|
+
return path14.isAbsolute(value) ? value : path14.resolve(path14.dirname(dst), value);
|
|
3989
4375
|
}
|
|
3990
4376
|
async function readMarker(dir) {
|
|
3991
|
-
const text = await
|
|
4377
|
+
const text = await readFile12(path14.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
|
|
3992
4378
|
if (text === void 0) return void 0;
|
|
3993
4379
|
let parsed;
|
|
3994
4380
|
try {
|
|
@@ -4007,7 +4393,7 @@ async function inspectSkill(dst, source, skill) {
|
|
|
4007
4393
|
if (info.isSymbolicLink()) {
|
|
4008
4394
|
const link = await linkTargetOf(dst);
|
|
4009
4395
|
if (!isOurLink(link, source)) return { kind: "foreign" };
|
|
4010
|
-
return { kind: "link", path: link, current:
|
|
4396
|
+
return { kind: "link", path: link, current: path14.resolve(link) === path14.join(path14.resolve(source), skill) };
|
|
4011
4397
|
}
|
|
4012
4398
|
if (info.isDirectory()) {
|
|
4013
4399
|
const marker = await readMarker(dst);
|
|
@@ -4045,17 +4431,17 @@ async function writeSkill(dst, src, mode, version) {
|
|
|
4045
4431
|
}
|
|
4046
4432
|
await cp2(src, dst, { recursive: true });
|
|
4047
4433
|
const marker = { version, source: SKILL_SOURCE };
|
|
4048
|
-
await
|
|
4434
|
+
await writeFile6(path14.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
|
|
4049
4435
|
`, "utf8");
|
|
4050
4436
|
}
|
|
4051
4437
|
async function installSkills(options) {
|
|
4052
4438
|
const entries = [];
|
|
4053
4439
|
for (const target of options.targets) {
|
|
4054
4440
|
const dir = targetSkillsDir(options.home, target);
|
|
4055
|
-
if (!options.dryRun) await
|
|
4441
|
+
if (!options.dryRun) await mkdir5(dir, { recursive: true });
|
|
4056
4442
|
for (const skill of options.source.skills) {
|
|
4057
|
-
const dst =
|
|
4058
|
-
const src =
|
|
4443
|
+
const dst = path14.join(dir, skill);
|
|
4444
|
+
const src = path14.join(options.source.dir, skill);
|
|
4059
4445
|
const state = await inspectSkill(dst, options.source.dir, skill);
|
|
4060
4446
|
const action = actionFor(state, options.version);
|
|
4061
4447
|
if (!options.dryRun && (action === "installed" || action === "updated")) {
|
|
@@ -4072,7 +4458,7 @@ async function uninstallSkills(options) {
|
|
|
4072
4458
|
for (const target of options.targets) {
|
|
4073
4459
|
const dir = targetSkillsDir(options.home, target);
|
|
4074
4460
|
for (const skill of (await readdir3(dir).catch(() => [])).sort()) {
|
|
4075
|
-
const dst =
|
|
4461
|
+
const dst = path14.join(dir, skill);
|
|
4076
4462
|
const state = await inspectSkill(dst, options.source.dir, skill);
|
|
4077
4463
|
if (state.kind !== "link" && state.kind !== "copy") continue;
|
|
4078
4464
|
if (!options.dryRun) await rm4(dst, { recursive: true, force: true });
|
|
@@ -4086,7 +4472,7 @@ async function skillsStatus(options) {
|
|
|
4086
4472
|
for (const target of options.targets) {
|
|
4087
4473
|
const dir = targetSkillsDir(options.home, target);
|
|
4088
4474
|
for (const skill of options.source.skills) {
|
|
4089
|
-
const dst =
|
|
4475
|
+
const dst = path14.join(dir, skill);
|
|
4090
4476
|
out.push({ target, skill, state: await inspectSkill(dst, options.source.dir, skill), path: dst });
|
|
4091
4477
|
}
|
|
4092
4478
|
}
|
|
@@ -4099,13 +4485,13 @@ function messageOf10(err) {
|
|
|
4099
4485
|
return err instanceof Error ? err.message : String(err);
|
|
4100
4486
|
}
|
|
4101
4487
|
async function isFile3(file) {
|
|
4102
|
-
const info = await
|
|
4488
|
+
const info = await stat11(file).catch(() => void 0);
|
|
4103
4489
|
return info !== void 0 && info.isFile();
|
|
4104
4490
|
}
|
|
4105
4491
|
function sourceDir2() {
|
|
4106
4492
|
try {
|
|
4107
4493
|
const url = import.meta.url;
|
|
4108
|
-
return typeof url === "string" && url !== "" ?
|
|
4494
|
+
return typeof url === "string" && url !== "" ? path15.dirname(fileURLToPath3(url)) : void 0;
|
|
4109
4495
|
} catch {
|
|
4110
4496
|
return void 0;
|
|
4111
4497
|
}
|
|
@@ -4115,9 +4501,9 @@ async function packageJsonPath() {
|
|
|
4115
4501
|
if (start === void 0) return void 0;
|
|
4116
4502
|
let dir = start;
|
|
4117
4503
|
for (let i = 0; i < 6; i += 1) {
|
|
4118
|
-
const file =
|
|
4504
|
+
const file = path15.join(dir, "package.json");
|
|
4119
4505
|
if (await isFile3(file)) return file;
|
|
4120
|
-
const up =
|
|
4506
|
+
const up = path15.dirname(dir);
|
|
4121
4507
|
if (up === dir) break;
|
|
4122
4508
|
dir = up;
|
|
4123
4509
|
}
|
|
@@ -4141,7 +4527,7 @@ function minimumOf(range) {
|
|
|
4141
4527
|
}
|
|
4142
4528
|
async function nodeCheck() {
|
|
4143
4529
|
const name = "node";
|
|
4144
|
-
const current =
|
|
4530
|
+
const current = process13.versions.node;
|
|
4145
4531
|
const file = await packageJsonPath();
|
|
4146
4532
|
if (file === void 0) {
|
|
4147
4533
|
if (await isBinary()) return { name, status: "ok", detail: `v${current} (inside the binary)` };
|
|
@@ -4149,7 +4535,7 @@ async function nodeCheck() {
|
|
|
4149
4535
|
}
|
|
4150
4536
|
let range;
|
|
4151
4537
|
try {
|
|
4152
|
-
const body = JSON.parse(await
|
|
4538
|
+
const body = JSON.parse(await readFile13(file, "utf8"));
|
|
4153
4539
|
range = typeof body.engines?.node === "string" ? body.engines.node : "";
|
|
4154
4540
|
} catch (err) {
|
|
4155
4541
|
return { name, status: "warn", detail: `v${current} (${messageOf10(err)})` };
|
|
@@ -4160,20 +4546,21 @@ async function nodeCheck() {
|
|
|
4160
4546
|
return { name, status: status2, detail: `v${current} (needs ${range})` };
|
|
4161
4547
|
}
|
|
4162
4548
|
async function tokenOf(url) {
|
|
4163
|
-
const env = (
|
|
4549
|
+
const env = (process13.env.RSHOW_TOKEN ?? "").trim();
|
|
4164
4550
|
if (env !== "") return { token: env, source: "env" };
|
|
4165
|
-
if (url === void 0) return {
|
|
4551
|
+
if (url === void 0) return {};
|
|
4166
4552
|
try {
|
|
4167
|
-
const
|
|
4168
|
-
if (
|
|
4169
|
-
return {
|
|
4553
|
+
const found = await find(process13.env, url);
|
|
4554
|
+
if (found !== void 0) return { token: found.token, source: found.store.detail };
|
|
4555
|
+
return {};
|
|
4170
4556
|
} catch (err) {
|
|
4171
|
-
return {
|
|
4557
|
+
return { error: messageOf10(err) };
|
|
4172
4558
|
}
|
|
4173
4559
|
}
|
|
4174
4560
|
async function getJson(url, token) {
|
|
4175
4561
|
const headers = {};
|
|
4176
4562
|
if (token !== void 0) headers.authorization = `Bearer ${token}`;
|
|
4563
|
+
await readyNetwork();
|
|
4177
4564
|
const reply = await fetch(url, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
4178
4565
|
if (reply.status !== 200) return { status: reply.status };
|
|
4179
4566
|
const body = await reply.json().catch(() => void 0);
|
|
@@ -4199,7 +4586,7 @@ function tokenCheck(token) {
|
|
|
4199
4586
|
const reason = token.error === void 0 ? "" : ` (${token.error})`;
|
|
4200
4587
|
return { name, status: "fail", detail: `none${reason}: run rshow login, or set RSHOW_TOKEN` };
|
|
4201
4588
|
}
|
|
4202
|
-
return { name, status: "ok", detail: `source: ${token.source}` };
|
|
4589
|
+
return { name, status: "ok", detail: `source: ${token.source ?? "none"}` };
|
|
4203
4590
|
}
|
|
4204
4591
|
async function apiCheck(url, token) {
|
|
4205
4592
|
const name = "api";
|
|
@@ -4213,7 +4600,7 @@ async function apiCheck(url, token) {
|
|
|
4213
4600
|
const scope = typeof body.scope === "string" ? body.scope : "";
|
|
4214
4601
|
return { name, status: "ok", detail: `${email} (scope ${scope})` };
|
|
4215
4602
|
} catch (err) {
|
|
4216
|
-
return { name, status: "fail", detail:
|
|
4603
|
+
return { name, status: "fail", detail: networkDetail(err, `${url}/api/me`) };
|
|
4217
4604
|
}
|
|
4218
4605
|
}
|
|
4219
4606
|
async function artCheck(art) {
|
|
@@ -4228,7 +4615,7 @@ async function artCheck(art) {
|
|
|
4228
4615
|
}
|
|
4229
4616
|
return { name, status: "ok", detail: `version ${version}` };
|
|
4230
4617
|
} catch (err) {
|
|
4231
|
-
return { name, status: "warn", detail:
|
|
4618
|
+
return { name, status: "warn", detail: networkDetail(err, `${art}/_themes/default.json`) };
|
|
4232
4619
|
}
|
|
4233
4620
|
}
|
|
4234
4621
|
async function themeCheck(config) {
|
|
@@ -4247,10 +4634,10 @@ async function themeCheck(config) {
|
|
|
4247
4634
|
}
|
|
4248
4635
|
async function skillsCheck() {
|
|
4249
4636
|
const name = "skills";
|
|
4250
|
-
const home = homeDir(
|
|
4637
|
+
const home = homeDir(process13.env);
|
|
4251
4638
|
const targets = await detectedTargets(home);
|
|
4252
4639
|
if (targets.length === 0) return { name, status: "ok", detail: "no target detected" };
|
|
4253
|
-
const source = await skillsSource(
|
|
4640
|
+
const source = await skillsSource(process13.env).catch(() => void 0);
|
|
4254
4641
|
if (source === void 0) return { name, status: "warn", detail: "no skills folder next to rshow" };
|
|
4255
4642
|
const list3 = await skillsStatus({ home, source, targets });
|
|
4256
4643
|
const problems = [];
|
|
@@ -4261,11 +4648,11 @@ async function skillsCheck() {
|
|
|
4261
4648
|
if (problems.length === 0) return { name, status: "ok", detail: targets.join(", ") };
|
|
4262
4649
|
return { name, status: "warn", detail: `${problems.join("; ")} (run rshow skills install)` };
|
|
4263
4650
|
}
|
|
4264
|
-
async function
|
|
4651
|
+
async function run6(argv, ctx) {
|
|
4265
4652
|
const args = parseCommandArgs(argv, {}, 0, 1, DOCTOR_USAGE);
|
|
4266
4653
|
const dir = args.positionals[0];
|
|
4267
4654
|
const found = [];
|
|
4268
|
-
for (const file of [globalConfigPath(
|
|
4655
|
+
for (const file of [globalConfigPath(process13.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
|
|
4269
4656
|
if (file !== void 0 && await isFile3(file)) found.push(file);
|
|
4270
4657
|
}
|
|
4271
4658
|
let config;
|
|
@@ -4293,13 +4680,13 @@ async function run7(argv, ctx) {
|
|
|
4293
4680
|
}
|
|
4294
4681
|
|
|
4295
4682
|
// src/commands/theme.ts
|
|
4296
|
-
import { mkdir as
|
|
4683
|
+
import { mkdir as mkdir6, mkdtemp as mkdtemp3, rm as rm5, stat as stat13, writeFile as writeFile7 } from "node:fs/promises";
|
|
4297
4684
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4298
|
-
import
|
|
4685
|
+
import path17 from "node:path";
|
|
4299
4686
|
|
|
4300
4687
|
// src/theme.ts
|
|
4301
|
-
import { readFile as
|
|
4302
|
-
import
|
|
4688
|
+
import { readFile as readFile14, stat as stat12 } from "node:fs/promises";
|
|
4689
|
+
import path16 from "node:path";
|
|
4303
4690
|
function themeSlug(name) {
|
|
4304
4691
|
return `theme-${name}`;
|
|
4305
4692
|
}
|
|
@@ -4307,10 +4694,10 @@ function themeUrl(name, n) {
|
|
|
4307
4694
|
return `/_themes/${name}@${n}/`;
|
|
4308
4695
|
}
|
|
4309
4696
|
async function readThemeMeta(dir) {
|
|
4310
|
-
const file =
|
|
4697
|
+
const file = path16.join(dir, "theme.json");
|
|
4311
4698
|
let text;
|
|
4312
4699
|
try {
|
|
4313
|
-
text = await
|
|
4700
|
+
text = await readFile14(file, "utf8");
|
|
4314
4701
|
} catch (err) {
|
|
4315
4702
|
if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
|
|
4316
4703
|
throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4331,8 +4718,8 @@ async function readThemeMeta(dir) {
|
|
|
4331
4718
|
return { name };
|
|
4332
4719
|
}
|
|
4333
4720
|
async function requireThemeDir(input) {
|
|
4334
|
-
const dir =
|
|
4335
|
-
const info = await
|
|
4721
|
+
const dir = path16.resolve(input);
|
|
4722
|
+
const info = await stat12(dir).catch(() => void 0);
|
|
4336
4723
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
4337
4724
|
return dir;
|
|
4338
4725
|
}
|
|
@@ -4340,7 +4727,7 @@ async function requireThemeDir(input) {
|
|
|
4340
4727
|
// src/commands/theme.ts
|
|
4341
4728
|
async function presetCheck(dir) {
|
|
4342
4729
|
const findings = await checkPreset(dir);
|
|
4343
|
-
const info = await
|
|
4730
|
+
const info = await stat13(path17.join(dir, "theme.json")).catch(() => void 0);
|
|
4344
4731
|
const errors = findings.filter((finding2) => finding2.severity === "error").length;
|
|
4345
4732
|
return { findings, errors, warnings: findings.length - errors, files: 1, bytes: info?.size ?? 0 };
|
|
4346
4733
|
}
|
|
@@ -4369,14 +4756,14 @@ async function publish(argv, ctx) {
|
|
|
4369
4756
|
if (!boolValue(args, "no-check")) {
|
|
4370
4757
|
if (preset) await enforcePresetCheck(ctx.out, dir);
|
|
4371
4758
|
else {
|
|
4372
|
-
if (config.art === void 0)
|
|
4759
|
+
if (config.art === void 0) warn("no art host configured; skipping theme checks");
|
|
4373
4760
|
await enforceCheck(ctx.out, dir, { art: config.art, offline: config.art === void 0 });
|
|
4374
4761
|
}
|
|
4375
4762
|
}
|
|
4376
4763
|
let target = dir;
|
|
4377
4764
|
let temp;
|
|
4378
4765
|
if (preset) {
|
|
4379
|
-
temp = await mkdtemp3(
|
|
4766
|
+
temp = await mkdtemp3(path17.join(tmpdir2(), "rshow-theme-"));
|
|
4380
4767
|
await buildPreset(dir, temp);
|
|
4381
4768
|
target = temp;
|
|
4382
4769
|
}
|
|
@@ -4464,14 +4851,14 @@ async function create(argv, ctx) {
|
|
|
4464
4851
|
const from = stringValue(args, "from") ?? "default";
|
|
4465
4852
|
const baseDir = await builtinThemeDir(from);
|
|
4466
4853
|
if (baseDir === void 0) throw new UsageError(`no built-in theme: ${from}`);
|
|
4467
|
-
const parent =
|
|
4468
|
-
const target =
|
|
4469
|
-
const exists2 = await
|
|
4854
|
+
const parent = path17.resolve(stringValue(args, "dir") ?? ".");
|
|
4855
|
+
const target = path17.join(parent, name);
|
|
4856
|
+
const exists2 = await stat13(target).catch(() => void 0);
|
|
4470
4857
|
if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
|
|
4471
4858
|
const values = await baseRoleValues(baseDir, ROLE_TOKENS);
|
|
4472
4859
|
const doc = { name, extends: from, tokens: {}, modeTokens: values };
|
|
4473
|
-
await
|
|
4474
|
-
await
|
|
4860
|
+
await mkdir6(target, { recursive: true });
|
|
4861
|
+
await writeFile7(path17.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
|
|
4475
4862
|
`);
|
|
4476
4863
|
ctx.out.data({ path: target, name, extends: from });
|
|
4477
4864
|
ctx.out.line(target);
|
|
@@ -4506,7 +4893,7 @@ async function preview(argv, ctx) {
|
|
|
4506
4893
|
const port = portOf(stringValue(args, "port"));
|
|
4507
4894
|
const offline = boolValue(args, "offline");
|
|
4508
4895
|
const config = await ctx.config(dir);
|
|
4509
|
-
if (config.art === void 0 && !offline)
|
|
4896
|
+
if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
|
|
4510
4897
|
const server = await startPreview({
|
|
4511
4898
|
dir,
|
|
4512
4899
|
theme: dir,
|
|
@@ -4520,7 +4907,7 @@ async function preview(argv, ctx) {
|
|
|
4520
4907
|
ctx.out.raw(`${server.url}/index.html`);
|
|
4521
4908
|
return EXIT.ok;
|
|
4522
4909
|
}
|
|
4523
|
-
async function
|
|
4910
|
+
async function run7(argv, ctx) {
|
|
4524
4911
|
const [sub, ...rest] = argv;
|
|
4525
4912
|
if (sub === void 0) throw new UsageError(`expected a theme command (try: ${usageLine(THEME_USAGE)})`);
|
|
4526
4913
|
if (sub === "new") return create(rest, ctx);
|
|
@@ -4538,11 +4925,11 @@ async function run8(argv, ctx) {
|
|
|
4538
4925
|
}
|
|
4539
4926
|
|
|
4540
4927
|
// src/commands/skills.ts
|
|
4541
|
-
import
|
|
4542
|
-
import
|
|
4928
|
+
import path18 from "node:path";
|
|
4929
|
+
import process14 from "node:process";
|
|
4543
4930
|
var CHANGE_ACTIONS = /* @__PURE__ */ new Set(["installed", "updated", "removed"]);
|
|
4544
4931
|
async function sourceOf() {
|
|
4545
|
-
const source = await skillsSource(
|
|
4932
|
+
const source = await skillsSource(process14.env);
|
|
4546
4933
|
if (source === void 0) {
|
|
4547
4934
|
throw new UsageError(`no skills folder next to rshow (set RSHOW_SKILLS_DIR to one)`);
|
|
4548
4935
|
}
|
|
@@ -4555,7 +4942,7 @@ async function targetsWithDetection(names, home) {
|
|
|
4555
4942
|
}
|
|
4556
4943
|
function noTargetsError(home) {
|
|
4557
4944
|
return new UsageError(
|
|
4558
|
-
`no target detected (looked for ${
|
|
4945
|
+
`no target detected (looked for ${path18.join(home, ".claude")}, ${path18.join(home, ".pi/agent")} and ${path18.join(home, ".codex")}); name one of ${targetHint()}`
|
|
4559
4946
|
);
|
|
4560
4947
|
}
|
|
4561
4948
|
function actionLine(entry, dryRun) {
|
|
@@ -4581,7 +4968,7 @@ async function install(argv, ctx) {
|
|
|
4581
4968
|
const link = boolValue(args, "link");
|
|
4582
4969
|
const copy = boolValue(args, "copy");
|
|
4583
4970
|
if (link && copy) throw new UsageError(`--link and --copy cannot be used together (try: ${usageLine(SKILLS_INSTALL_USAGE)})`);
|
|
4584
|
-
const home = homeDir(
|
|
4971
|
+
const home = homeDir(process14.env);
|
|
4585
4972
|
const targets = await targetsWithDetection(args.positionals, home);
|
|
4586
4973
|
if (targets.length === 0) throw noTargetsError(home);
|
|
4587
4974
|
const source = await sourceOf();
|
|
@@ -4595,13 +4982,13 @@ async function install(argv, ctx) {
|
|
|
4595
4982
|
else ctx.out.line(actionLine(entry, dryRun));
|
|
4596
4983
|
}
|
|
4597
4984
|
ctx.out.line(summaryLine2("installed", entries, targets, dryRun));
|
|
4598
|
-
for (const dst of skipped)
|
|
4985
|
+
for (const dst of skipped) process14.stderr.write(`skip ${dst}: exists and is not from rshow
|
|
4599
4986
|
`);
|
|
4600
4987
|
return skipped.length > 0 ? EXIT.check : EXIT.ok;
|
|
4601
4988
|
}
|
|
4602
4989
|
async function uninstall(argv, ctx) {
|
|
4603
4990
|
const args = parseCommandArgs(argv, { "dry-run": { type: "boolean" } }, 0, 3, SKILLS_UNINSTALL_USAGE);
|
|
4604
|
-
const home = homeDir(
|
|
4991
|
+
const home = homeDir(process14.env);
|
|
4605
4992
|
const targets = await targetsWithDetection(args.positionals, home);
|
|
4606
4993
|
if (targets.length === 0) return EXIT.ok;
|
|
4607
4994
|
const source = await sourceOf();
|
|
@@ -4619,7 +5006,7 @@ function actionOf(item) {
|
|
|
4619
5006
|
}
|
|
4620
5007
|
async function status(argv, ctx) {
|
|
4621
5008
|
const args = parseCommandArgs(argv, {}, 0, 3, SKILLS_STATUS_USAGE);
|
|
4622
|
-
const home = homeDir(
|
|
5009
|
+
const home = homeDir(process14.env);
|
|
4623
5010
|
const targets = await targetsWithDetection(args.positionals, home);
|
|
4624
5011
|
if (targets.length === 0) return EXIT.ok;
|
|
4625
5012
|
const source = await sourceOf();
|
|
@@ -4634,12 +5021,12 @@ async function status(argv, ctx) {
|
|
|
4634
5021
|
const name = `${item.target.padEnd(targetWidth)} ${item.skill.padEnd(skillWidth)}`;
|
|
4635
5022
|
ctx.out.line(`${name} ${skillStateLabel(item.state)}`);
|
|
4636
5023
|
if (item.state.kind === "copy" && !skillIsCurrent(item.state, VERSION)) {
|
|
4637
|
-
|
|
5024
|
+
warn(`${item.path} is a copy of v${item.state.version}, this rshow is ${VERSION}: run rshow skills install`);
|
|
4638
5025
|
}
|
|
4639
5026
|
}
|
|
4640
5027
|
return EXIT.ok;
|
|
4641
5028
|
}
|
|
4642
|
-
async function
|
|
5029
|
+
async function run8(argv, ctx) {
|
|
4643
5030
|
const [sub, ...rest] = argv;
|
|
4644
5031
|
switch (sub) {
|
|
4645
5032
|
case "install":
|
|
@@ -4654,7 +5041,7 @@ async function run9(argv, ctx) {
|
|
|
4654
5041
|
}
|
|
4655
5042
|
|
|
4656
5043
|
// src/commands/list.ts
|
|
4657
|
-
async function
|
|
5044
|
+
async function run9(argv, ctx) {
|
|
4658
5045
|
const args = parseCommandArgs(
|
|
4659
5046
|
argv,
|
|
4660
5047
|
{ project: { type: "string" }, tag: { type: "string" }, q: { type: "string" } },
|
|
@@ -4687,7 +5074,7 @@ async function run10(argv, ctx) {
|
|
|
4687
5074
|
}
|
|
4688
5075
|
|
|
4689
5076
|
// src/commands/show.ts
|
|
4690
|
-
async function
|
|
5077
|
+
async function run10(argv, ctx) {
|
|
4691
5078
|
const args = parseCommandArgs(argv, {}, 1, 1, SHOW_USAGE);
|
|
4692
5079
|
const slug = validateSlug(args.positionals[0]);
|
|
4693
5080
|
const api = await ctx.api();
|
|
@@ -4720,7 +5107,7 @@ async function run11(argv, ctx) {
|
|
|
4720
5107
|
|
|
4721
5108
|
// src/commands/share.ts
|
|
4722
5109
|
var MODES = /* @__PURE__ */ new Set(["private", "people", "public"]);
|
|
4723
|
-
async function
|
|
5110
|
+
async function run11(argv, ctx) {
|
|
4724
5111
|
const args = parseCommandArgs(
|
|
4725
5112
|
argv,
|
|
4726
5113
|
{
|
|
@@ -4760,7 +5147,7 @@ async function run12(argv, ctx) {
|
|
|
4760
5147
|
}
|
|
4761
5148
|
|
|
4762
5149
|
// src/commands/pin.ts
|
|
4763
|
-
async function
|
|
5150
|
+
async function run12(argv, ctx) {
|
|
4764
5151
|
const args = parseCommandArgs(argv, {}, 2, 2, PIN_USAGE);
|
|
4765
5152
|
const slug = validateSlug(args.positionals[0]);
|
|
4766
5153
|
const target = args.positionals[1];
|
|
@@ -4782,7 +5169,7 @@ async function run13(argv, ctx) {
|
|
|
4782
5169
|
}
|
|
4783
5170
|
|
|
4784
5171
|
// src/commands/delete.ts
|
|
4785
|
-
async function
|
|
5172
|
+
async function run13(argv, ctx) {
|
|
4786
5173
|
const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, DELETE_USAGE);
|
|
4787
5174
|
const slug = validateSlug(args.positionals[0]);
|
|
4788
5175
|
if (!boolValue(args, "yes")) throw new UsageError(`delete needs --yes (try: ${usageLine(DELETE_USAGE)})`);
|
|
@@ -4896,7 +5283,7 @@ async function share(argv, ctx) {
|
|
|
4896
5283
|
for (const member of members) ctx.out.line(`member ${member.email} ${member.role}`);
|
|
4897
5284
|
for (const email of removals) ctx.out.line(`removed ${email}`);
|
|
4898
5285
|
}
|
|
4899
|
-
async function
|
|
5286
|
+
async function remove4(argv, ctx) {
|
|
4900
5287
|
const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, PROJECT_DELETE_USAGE);
|
|
4901
5288
|
const slug = validateSlug(args.positionals[0]);
|
|
4902
5289
|
if (!boolValue(args, "yes")) {
|
|
@@ -4907,7 +5294,7 @@ async function remove2(argv, ctx) {
|
|
|
4907
5294
|
ctx.out.data(null);
|
|
4908
5295
|
ctx.out.line(`deleted: ${slug}`);
|
|
4909
5296
|
}
|
|
4910
|
-
async function
|
|
5297
|
+
async function run14(argv, ctx) {
|
|
4911
5298
|
const [sub, ...rest] = argv;
|
|
4912
5299
|
if (sub === void 0) {
|
|
4913
5300
|
throw new UsageError(`expected a project command (try: ${usageLine(PROJECT_USAGE)})`);
|
|
@@ -4916,12 +5303,12 @@ async function run15(argv, ctx) {
|
|
|
4916
5303
|
if (sub === "list") return list2(rest, ctx);
|
|
4917
5304
|
if (sub === "show") return show(rest, ctx);
|
|
4918
5305
|
if (sub === "share") return share(rest, ctx);
|
|
4919
|
-
if (sub === "delete") return
|
|
5306
|
+
if (sub === "delete") return remove4(rest, ctx);
|
|
4920
5307
|
throw new UsageError(`unknown project command: ${sub} (try: ${usageLine(PROJECT_USAGE)})`);
|
|
4921
5308
|
}
|
|
4922
5309
|
|
|
4923
5310
|
// src/commands/tag.ts
|
|
4924
|
-
async function
|
|
5311
|
+
async function run15(argv, ctx) {
|
|
4925
5312
|
const args = parseCommandArgs(
|
|
4926
5313
|
argv,
|
|
4927
5314
|
{ add: { type: "string", multiple: true }, remove: { type: "string", multiple: true } },
|
|
@@ -4931,13 +5318,13 @@ async function run16(argv, ctx) {
|
|
|
4931
5318
|
);
|
|
4932
5319
|
const slug = validateSlug(args.positionals[0]);
|
|
4933
5320
|
const add2 = normalizeTags(stringList(args, "add"));
|
|
4934
|
-
const
|
|
4935
|
-
if (add2.length === 0 &&
|
|
5321
|
+
const remove5 = normalizeTags(stringList(args, "remove"));
|
|
5322
|
+
if (add2.length === 0 && remove5.length === 0) {
|
|
4936
5323
|
throw new UsageError(`nothing to change (try: ${usageLine(TAG_USAGE)})`);
|
|
4937
5324
|
}
|
|
4938
5325
|
const api = await ctx.api();
|
|
4939
5326
|
const current = await api.artifact(slug);
|
|
4940
|
-
const tags = withTags(tagsOf(current?.tags), add2,
|
|
5327
|
+
const tags = withTags(tagsOf(current?.tags), add2, remove5);
|
|
4941
5328
|
const artifact = await api.patchArtifact(slug, { tags });
|
|
4942
5329
|
ctx.out.data({ artifact, tags });
|
|
4943
5330
|
ctx.out.line(`slug: ${slug}`);
|
|
@@ -4945,20 +5332,20 @@ async function run16(argv, ctx) {
|
|
|
4945
5332
|
}
|
|
4946
5333
|
|
|
4947
5334
|
// src/commands/handoff.ts
|
|
4948
|
-
import
|
|
5335
|
+
import process18 from "node:process";
|
|
4949
5336
|
|
|
4950
5337
|
// src/handoff.ts
|
|
4951
|
-
import { mkdir as
|
|
4952
|
-
import
|
|
4953
|
-
import
|
|
5338
|
+
import { mkdir as mkdir7, stat as stat14, writeFile as writeFile9 } from "node:fs/promises";
|
|
5339
|
+
import path21 from "node:path";
|
|
5340
|
+
import process17 from "node:process";
|
|
4954
5341
|
|
|
4955
5342
|
// src/cdp.ts
|
|
4956
5343
|
import { spawn as spawn3 } from "node:child_process";
|
|
4957
5344
|
import { existsSync, rmSync } from "node:fs";
|
|
4958
5345
|
import { mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
|
|
4959
5346
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
4960
|
-
import
|
|
4961
|
-
import
|
|
5347
|
+
import path19 from "node:path";
|
|
5348
|
+
import process15 from "node:process";
|
|
4962
5349
|
var CHROME_CANDIDATES = [
|
|
4963
5350
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
4964
5351
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
@@ -4978,9 +5365,9 @@ var LOAD_TIMEOUT_MS = 2e4;
|
|
|
4978
5365
|
var liveBrowsers = /* @__PURE__ */ new Map();
|
|
4979
5366
|
var signalHandlersInstalled = false;
|
|
4980
5367
|
function killBrowser(child, signal) {
|
|
4981
|
-
if (
|
|
5368
|
+
if (process15.platform !== "win32" && child.pid !== void 0) {
|
|
4982
5369
|
try {
|
|
4983
|
-
|
|
5370
|
+
process15.kill(-child.pid, signal);
|
|
4984
5371
|
return;
|
|
4985
5372
|
} catch {
|
|
4986
5373
|
}
|
|
@@ -5005,24 +5392,24 @@ function reapBrowsers() {
|
|
|
5005
5392
|
}
|
|
5006
5393
|
function onInterrupt() {
|
|
5007
5394
|
reapBrowsers();
|
|
5008
|
-
|
|
5395
|
+
process15.exit(130);
|
|
5009
5396
|
}
|
|
5010
5397
|
function onTerminate() {
|
|
5011
5398
|
reapBrowsers();
|
|
5012
|
-
|
|
5399
|
+
process15.exit(143);
|
|
5013
5400
|
}
|
|
5014
5401
|
function dropSignalHandlers() {
|
|
5015
5402
|
if (!signalHandlersInstalled) return;
|
|
5016
5403
|
signalHandlersInstalled = false;
|
|
5017
|
-
|
|
5018
|
-
|
|
5404
|
+
process15.off("SIGINT", onInterrupt);
|
|
5405
|
+
process15.off("SIGTERM", onTerminate);
|
|
5019
5406
|
}
|
|
5020
5407
|
function trackBrowser(child, profile) {
|
|
5021
5408
|
liveBrowsers.set(child, profile);
|
|
5022
5409
|
if (!signalHandlersInstalled) {
|
|
5023
5410
|
signalHandlersInstalled = true;
|
|
5024
|
-
|
|
5025
|
-
|
|
5411
|
+
process15.on("SIGINT", onInterrupt);
|
|
5412
|
+
process15.on("SIGTERM", onTerminate);
|
|
5026
5413
|
}
|
|
5027
5414
|
}
|
|
5028
5415
|
function forgetBrowser(child) {
|
|
@@ -5033,20 +5420,20 @@ var exitHookInstalled = false;
|
|
|
5033
5420
|
function installExitHook() {
|
|
5034
5421
|
if (exitHookInstalled) return;
|
|
5035
5422
|
exitHookInstalled = true;
|
|
5036
|
-
|
|
5423
|
+
process15.once("exit", reapBrowsers);
|
|
5037
5424
|
}
|
|
5038
5425
|
function messageOf11(err) {
|
|
5039
5426
|
return err instanceof Error ? err.message : String(err);
|
|
5040
5427
|
}
|
|
5041
5428
|
function whichInPath(name, pathValue) {
|
|
5042
|
-
for (const dir of String(pathValue ?? "").split(
|
|
5429
|
+
for (const dir of String(pathValue ?? "").split(path19.delimiter)) {
|
|
5043
5430
|
if (dir === "") continue;
|
|
5044
|
-
const file =
|
|
5431
|
+
const file = path19.join(dir, name);
|
|
5045
5432
|
if (existsSync(file)) return file;
|
|
5046
5433
|
}
|
|
5047
5434
|
return void 0;
|
|
5048
5435
|
}
|
|
5049
|
-
function findChrome(env =
|
|
5436
|
+
function findChrome(env = process15.env) {
|
|
5050
5437
|
const explicit = (env.CHROME ?? "").trim();
|
|
5051
5438
|
if (explicit !== "") {
|
|
5052
5439
|
if (existsSync(explicit)) return explicit;
|
|
@@ -5217,7 +5604,7 @@ var Chrome = class _Chrome {
|
|
|
5217
5604
|
static async launch(opts = {}) {
|
|
5218
5605
|
const binary = opts.binary ?? findChrome();
|
|
5219
5606
|
if (binary === void 0) throw new UsageError("no Chrome or Chromium found, set CHROME");
|
|
5220
|
-
const profile = await mkdtemp4(
|
|
5607
|
+
const profile = await mkdtemp4(path19.join(tmpdir3(), "rshow-chrome-"));
|
|
5221
5608
|
const child = spawn3(
|
|
5222
5609
|
binary,
|
|
5223
5610
|
[
|
|
@@ -5237,7 +5624,7 @@ var Chrome = class _Chrome {
|
|
|
5237
5624
|
"--window-size=1440,900",
|
|
5238
5625
|
"about:blank"
|
|
5239
5626
|
],
|
|
5240
|
-
{ stdio: ["ignore", "pipe", "pipe"], detached:
|
|
5627
|
+
{ stdio: ["ignore", "pipe", "pipe"], detached: process15.platform !== "win32" }
|
|
5241
5628
|
);
|
|
5242
5629
|
installExitHook();
|
|
5243
5630
|
trackBrowser(child, profile);
|
|
@@ -5369,16 +5756,16 @@ var Page = class _Page {
|
|
|
5369
5756
|
};
|
|
5370
5757
|
|
|
5371
5758
|
// src/map.ts
|
|
5372
|
-
import { readFile as
|
|
5373
|
-
import
|
|
5374
|
-
import
|
|
5759
|
+
import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
|
|
5760
|
+
import path20 from "node:path";
|
|
5761
|
+
import process16 from "node:process";
|
|
5375
5762
|
var MAP_FILE = "rshow.map.json";
|
|
5376
|
-
function
|
|
5763
|
+
function isRecord6(value) {
|
|
5377
5764
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5378
5765
|
}
|
|
5379
5766
|
function textMap(file, key2, value) {
|
|
5380
5767
|
if (value === void 0 || value === null) return {};
|
|
5381
|
-
if (!
|
|
5768
|
+
if (!isRecord6(value)) throw new UsageError(`${file}: ${key2} must be an object`);
|
|
5382
5769
|
const out = {};
|
|
5383
5770
|
for (const [name, target] of Object.entries(value)) {
|
|
5384
5771
|
if (typeof target !== "string") throw new UsageError(`${file}: ${key2}.${name} must be a string`);
|
|
@@ -5387,12 +5774,12 @@ function textMap(file, key2, value) {
|
|
|
5387
5774
|
return out;
|
|
5388
5775
|
}
|
|
5389
5776
|
function mapPath(dir) {
|
|
5390
|
-
return
|
|
5777
|
+
return path20.resolve(dir ?? process16.cwd(), MAP_FILE);
|
|
5391
5778
|
}
|
|
5392
5779
|
async function readMapFile(file) {
|
|
5393
5780
|
let text;
|
|
5394
5781
|
try {
|
|
5395
|
-
text = await
|
|
5782
|
+
text = await readFile15(file, "utf8");
|
|
5396
5783
|
} catch (err) {
|
|
5397
5784
|
if (err.code === "ENOENT") return void 0;
|
|
5398
5785
|
throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -5403,7 +5790,7 @@ async function readMapFile(file) {
|
|
|
5403
5790
|
} catch {
|
|
5404
5791
|
throw new UsageError(`invalid JSON in ${file}`);
|
|
5405
5792
|
}
|
|
5406
|
-
if (!
|
|
5793
|
+
if (!isRecord6(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
5407
5794
|
return {
|
|
5408
5795
|
components: textMap(file, "components", parsed.components),
|
|
5409
5796
|
tokens: textMap(file, "tokens", parsed.tokens),
|
|
@@ -5411,7 +5798,7 @@ async function readMapFile(file) {
|
|
|
5411
5798
|
};
|
|
5412
5799
|
}
|
|
5413
5800
|
async function writeMapFile(file, map) {
|
|
5414
|
-
await
|
|
5801
|
+
await writeFile8(file, `${JSON.stringify(sortMap(map), null, 2)}
|
|
5415
5802
|
`);
|
|
5416
5803
|
}
|
|
5417
5804
|
function sortRecord(value) {
|
|
@@ -5855,14 +6242,14 @@ function parseTarget(input) {
|
|
|
5855
6242
|
if (remote !== null) {
|
|
5856
6243
|
return { kind: "remote", slug: remote[1], version: Number(remote[2]) };
|
|
5857
6244
|
}
|
|
5858
|
-
const dir =
|
|
5859
|
-
return { kind: "local", dir, slug: deriveSlug(
|
|
6245
|
+
const dir = path21.resolve(input);
|
|
6246
|
+
return { kind: "local", dir, slug: deriveSlug(path21.basename(dir)) || "handoff" };
|
|
5860
6247
|
}
|
|
5861
6248
|
async function requireLocalDir(target) {
|
|
5862
6249
|
if (target.kind !== "local") throw new UsageError("expected a local folder");
|
|
5863
|
-
const info = await
|
|
6250
|
+
const info = await stat14(target.dir).catch(() => void 0);
|
|
5864
6251
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${target.dir}`);
|
|
5865
|
-
const index = await
|
|
6252
|
+
const index = await stat14(path21.join(target.dir, "index.html")).catch(() => void 0);
|
|
5866
6253
|
if (index === void 0 || !index.isFile()) throw new UsageError(`${target.dir} has no index.html`);
|
|
5867
6254
|
return target.dir;
|
|
5868
6255
|
}
|
|
@@ -5877,10 +6264,10 @@ function safeName(text) {
|
|
|
5877
6264
|
return cleaned === "" ? "screen" : cleaned;
|
|
5878
6265
|
}
|
|
5879
6266
|
function displayPath(target) {
|
|
5880
|
-
const resolved =
|
|
5881
|
-
const relative =
|
|
6267
|
+
const resolved = path21.resolve(target);
|
|
6268
|
+
const relative = path21.relative(process17.cwd(), resolved);
|
|
5882
6269
|
if (relative === "") return ".";
|
|
5883
|
-
if (relative.startsWith("..") ||
|
|
6270
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative)) return resolved;
|
|
5884
6271
|
return relative;
|
|
5885
6272
|
}
|
|
5886
6273
|
function baselineName(screen, state, width, mode) {
|
|
@@ -5915,10 +6302,11 @@ async function collectStyleTexts(url, fetchImpl = fetch) {
|
|
|
5915
6302
|
}
|
|
5916
6303
|
async function assertPublic(url, art, fetchImpl = fetch) {
|
|
5917
6304
|
let reply;
|
|
6305
|
+
await readyNetwork();
|
|
5918
6306
|
try {
|
|
5919
6307
|
reply = await fetchImpl(url, { headers: { accept: "text/html" }, redirect: "follow" });
|
|
5920
6308
|
} catch (err) {
|
|
5921
|
-
throw new UsageError(`cannot reach ${url}: ${err
|
|
6309
|
+
throw new UsageError(`cannot reach ${url}: ${networkDetail(err, url)}`);
|
|
5922
6310
|
}
|
|
5923
6311
|
const host = new URL(art).host;
|
|
5924
6312
|
const wandered = new URL(reply.url === "" ? url : reply.url).host !== host;
|
|
@@ -6041,7 +6429,7 @@ async function runHandoff(target, opts) {
|
|
|
6041
6429
|
const version = target.kind === "remote" ? target.version : null;
|
|
6042
6430
|
const slug = target.slug;
|
|
6043
6431
|
const url = target.kind === "remote" ? `${art}/${slug}/v${version}/` : "";
|
|
6044
|
-
const out =
|
|
6432
|
+
const out = path21.resolve(opts.out);
|
|
6045
6433
|
let closePreview;
|
|
6046
6434
|
let pageUrl = url;
|
|
6047
6435
|
if (target.kind === "remote") {
|
|
@@ -6056,8 +6444,8 @@ async function runHandoff(target, opts) {
|
|
|
6056
6444
|
const styleTexts = await collectStyleTexts(pageUrl, fetchImpl);
|
|
6057
6445
|
const tokens = extractTokens(styleTexts);
|
|
6058
6446
|
say(`tokens: ${tokens.light.length} light, ${tokens.dark.length} dark`);
|
|
6059
|
-
await
|
|
6060
|
-
await
|
|
6447
|
+
await mkdir7(path21.join(out, "baseline"), { recursive: true });
|
|
6448
|
+
await writeFile9(path21.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
|
|
6061
6449
|
`);
|
|
6062
6450
|
const screens = [];
|
|
6063
6451
|
const baselines = [];
|
|
@@ -6108,7 +6496,7 @@ async function runHandoff(target, opts) {
|
|
|
6108
6496
|
}
|
|
6109
6497
|
const png = await capture(page, screen);
|
|
6110
6498
|
const name = baselineName(screen.id, state, width, mode);
|
|
6111
|
-
await
|
|
6499
|
+
await writeFile9(path21.join(out, "baseline", name), png);
|
|
6112
6500
|
baselines.push({ screen: screen.id, state, width, mode, file: `baseline/${name}` });
|
|
6113
6501
|
}
|
|
6114
6502
|
}
|
|
@@ -6137,7 +6525,7 @@ async function runHandoff(target, opts) {
|
|
|
6137
6525
|
screens,
|
|
6138
6526
|
baselines
|
|
6139
6527
|
};
|
|
6140
|
-
await
|
|
6528
|
+
await writeFile9(path21.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
|
|
6141
6529
|
`);
|
|
6142
6530
|
const mapFile = MAP_FILE;
|
|
6143
6531
|
let mapWritten = false;
|
|
@@ -6156,7 +6544,7 @@ async function runHandoff(target, opts) {
|
|
|
6156
6544
|
createdAt,
|
|
6157
6545
|
mapFile
|
|
6158
6546
|
});
|
|
6159
|
-
await
|
|
6547
|
+
await writeFile9(path21.join(out, "HANDOFF.md"), markdown);
|
|
6160
6548
|
return {
|
|
6161
6549
|
out,
|
|
6162
6550
|
url: pageUrl,
|
|
@@ -6197,7 +6585,7 @@ function modeList(text) {
|
|
|
6197
6585
|
}
|
|
6198
6586
|
return out;
|
|
6199
6587
|
}
|
|
6200
|
-
async function
|
|
6588
|
+
async function run16(argv, ctx) {
|
|
6201
6589
|
const args = parseCommandArgs(
|
|
6202
6590
|
argv,
|
|
6203
6591
|
{ out: { type: "string" }, widths: { type: "string" }, modes: { type: "string" } },
|
|
@@ -6213,14 +6601,14 @@ async function run17(argv, ctx) {
|
|
|
6213
6601
|
requireUrl(config);
|
|
6214
6602
|
requireArt(config);
|
|
6215
6603
|
} else if (config.art === void 0) {
|
|
6216
|
-
|
|
6604
|
+
warn("no art host configured; skipping the /_themes proxy");
|
|
6217
6605
|
}
|
|
6218
6606
|
const result = await runHandoff(target, {
|
|
6219
6607
|
out,
|
|
6220
6608
|
widths: widthList(stringValue(args, "widths")),
|
|
6221
6609
|
modes: modeList(stringValue(args, "modes")),
|
|
6222
6610
|
art: config.art ?? "",
|
|
6223
|
-
chrome:
|
|
6611
|
+
chrome: process18.env.CHROME,
|
|
6224
6612
|
onProgress: (line) => ctx.out.line(line)
|
|
6225
6613
|
});
|
|
6226
6614
|
const display = displayPath(result.out);
|
|
@@ -6246,11 +6634,11 @@ async function run17(argv, ctx) {
|
|
|
6246
6634
|
}
|
|
6247
6635
|
|
|
6248
6636
|
// src/commands/compare.ts
|
|
6249
|
-
import
|
|
6637
|
+
import process20 from "node:process";
|
|
6250
6638
|
|
|
6251
6639
|
// src/compare.ts
|
|
6252
|
-
import { copyFile, mkdir as
|
|
6253
|
-
import
|
|
6640
|
+
import { copyFile, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile10 } from "node:fs/promises";
|
|
6641
|
+
import path23 from "node:path";
|
|
6254
6642
|
|
|
6255
6643
|
// src/diff.ts
|
|
6256
6644
|
var MAX_Y = 35215;
|
|
@@ -6412,10 +6800,10 @@ function diffImages(before, after, opts = {}) {
|
|
|
6412
6800
|
|
|
6413
6801
|
// src/native.ts
|
|
6414
6802
|
import { spawn as spawn4 } from "node:child_process";
|
|
6415
|
-
import { readFile as
|
|
6803
|
+
import { readFile as readFile16, unlink as unlink2 } from "node:fs/promises";
|
|
6416
6804
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
6417
|
-
import
|
|
6418
|
-
import
|
|
6805
|
+
import path22 from "node:path";
|
|
6806
|
+
import process19 from "node:process";
|
|
6419
6807
|
var FILE_PREFIX = "file:";
|
|
6420
6808
|
var WINDOW_PREFIX = "window:";
|
|
6421
6809
|
var TMUX_PREFIX = "tmux:";
|
|
@@ -6444,9 +6832,9 @@ function isNative(source) {
|
|
|
6444
6832
|
}
|
|
6445
6833
|
async function captureNative(source, mapDir, deps = {}) {
|
|
6446
6834
|
if (source.kind === "file") {
|
|
6447
|
-
const file =
|
|
6835
|
+
const file = path22.resolve(mapDir, source.file);
|
|
6448
6836
|
try {
|
|
6449
|
-
return new Uint8Array(await
|
|
6837
|
+
return new Uint8Array(await readFile16(file));
|
|
6450
6838
|
} catch {
|
|
6451
6839
|
throw new UsageError(`no screenshot at ${source.file}, which is relative to rshow.map.json`);
|
|
6452
6840
|
}
|
|
@@ -6494,8 +6882,8 @@ function noWindow(title, seen) {
|
|
|
6494
6882
|
return new UsageError(`no window matches ${JSON.stringify(title)} (${list3}); use ${WINDOW_PREFIX} with an owner name or a title, or ${NATIVE_HINT}`);
|
|
6495
6883
|
}
|
|
6496
6884
|
var systemRunner = {
|
|
6497
|
-
run: (
|
|
6498
|
-
const child = spawn4(
|
|
6885
|
+
run: (bin3, args) => new Promise((resolve2, reject) => {
|
|
6886
|
+
const child = spawn4(bin3, [...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
6499
6887
|
let stdout = "";
|
|
6500
6888
|
let stderr = "";
|
|
6501
6889
|
child.stdout?.setEncoding("utf8");
|
|
@@ -6506,10 +6894,10 @@ var systemRunner = {
|
|
|
6506
6894
|
child.stderr?.on("data", (chunk2) => {
|
|
6507
6895
|
stderr += chunk2;
|
|
6508
6896
|
});
|
|
6509
|
-
child.on("error", (err) => reject(new UsageError(`cannot run ${
|
|
6897
|
+
child.on("error", (err) => reject(new UsageError(`cannot run ${bin3}: ${err.message}`)));
|
|
6510
6898
|
child.on("close", (code) => {
|
|
6511
6899
|
if (code !== 0) {
|
|
6512
|
-
reject(new UsageError(stderr.trim() === "" ? `${
|
|
6900
|
+
reject(new UsageError(stderr.trim() === "" ? `${bin3} exited ${code}` : `${bin3}: ${stderr.trim()}`));
|
|
6513
6901
|
return;
|
|
6514
6902
|
}
|
|
6515
6903
|
resolve2(stdout);
|
|
@@ -6517,9 +6905,9 @@ var systemRunner = {
|
|
|
6517
6905
|
})
|
|
6518
6906
|
};
|
|
6519
6907
|
async function captureWindow(title, deps = {}) {
|
|
6520
|
-
const platform = deps.platform ??
|
|
6908
|
+
const platform = deps.platform ?? process19.platform;
|
|
6521
6909
|
const runner = deps.runner ?? systemRunner;
|
|
6522
|
-
const scratch =
|
|
6910
|
+
const scratch = path22.join(tmpdir4(), `rshow-window-${process19.pid}-${Date.now()}.png`);
|
|
6523
6911
|
const wrap = (err) => err instanceof UsageError ? err : new UsageError(`window: ${title}: ${err instanceof Error ? err.message : String(err)}; ${NATIVE_HINT}`);
|
|
6524
6912
|
try {
|
|
6525
6913
|
if (platform === "darwin") {
|
|
@@ -6535,11 +6923,11 @@ async function captureWindow(title, deps = {}) {
|
|
|
6535
6923
|
} else {
|
|
6536
6924
|
throw new UsageError(`window: capture is not supported on ${platform}; ${NATIVE_HINT}`);
|
|
6537
6925
|
}
|
|
6538
|
-
return new Uint8Array(await
|
|
6926
|
+
return new Uint8Array(await readFile16(scratch));
|
|
6539
6927
|
} catch (err) {
|
|
6540
6928
|
throw wrap(err);
|
|
6541
6929
|
} finally {
|
|
6542
|
-
await
|
|
6930
|
+
await unlink2(scratch).catch(() => {
|
|
6543
6931
|
});
|
|
6544
6932
|
}
|
|
6545
6933
|
}
|
|
@@ -6576,23 +6964,23 @@ function extractPalette(image, k = 8) {
|
|
|
6576
6964
|
const g = image.data[pos + 1];
|
|
6577
6965
|
const b = image.data[pos + 2];
|
|
6578
6966
|
const key2 = r >> BIN_SHIFT << BITS * 2 | g >> BIN_SHIFT << BITS | b >> BIN_SHIFT;
|
|
6579
|
-
const
|
|
6580
|
-
if (
|
|
6967
|
+
const bin3 = bins.get(key2);
|
|
6968
|
+
if (bin3 === void 0) bins.set(key2, { count: 1, r, g, b });
|
|
6581
6969
|
else {
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6970
|
+
bin3.count += 1;
|
|
6971
|
+
bin3.r += r;
|
|
6972
|
+
bin3.g += g;
|
|
6973
|
+
bin3.b += b;
|
|
6586
6974
|
}
|
|
6587
6975
|
used += 1;
|
|
6588
6976
|
}
|
|
6589
6977
|
}
|
|
6590
6978
|
if (used === 0) return [];
|
|
6591
6979
|
const seeds = [...bins.values()].sort((left, right) => right.count - left.count).slice(0, k);
|
|
6592
|
-
let centers = seeds.map((
|
|
6593
|
-
const points = [...bins.values()].map((
|
|
6594
|
-
color: { r:
|
|
6595
|
-
weight:
|
|
6980
|
+
let centers = seeds.map((bin3) => ({ r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count }));
|
|
6981
|
+
const points = [...bins.values()].map((bin3) => ({
|
|
6982
|
+
color: { r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count },
|
|
6983
|
+
weight: bin3.count
|
|
6596
6984
|
}));
|
|
6597
6985
|
for (let round2 = 0; round2 < ROUNDS && centers.length > 1; round2 += 1) {
|
|
6598
6986
|
const sums = centers.map(() => ({ r: 0, g: 0, b: 0, weight: 0 }));
|
|
@@ -7307,9 +7695,9 @@ function compareGrid(expected, actual) {
|
|
|
7307
7695
|
}
|
|
7308
7696
|
return { rows, differing, pass: differing.length === 0 };
|
|
7309
7697
|
}
|
|
7310
|
-
function captureTmux(target,
|
|
7698
|
+
function captureTmux(target, bin3 = "tmux") {
|
|
7311
7699
|
return new Promise((resolve2, reject) => {
|
|
7312
|
-
const child = spawn5(
|
|
7700
|
+
const child = spawn5(bin3, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
|
|
7313
7701
|
let stdout = "";
|
|
7314
7702
|
let stderr = "";
|
|
7315
7703
|
child.stdout?.setEncoding("utf8");
|
|
@@ -7321,11 +7709,11 @@ function captureTmux(target, bin2 = "tmux") {
|
|
|
7321
7709
|
stderr += chunk2;
|
|
7322
7710
|
});
|
|
7323
7711
|
child.on("error", (err) => {
|
|
7324
|
-
reject(new ApiError(`cannot run ${
|
|
7712
|
+
reject(new ApiError(`cannot run ${bin3}: ${err.message}`));
|
|
7325
7713
|
});
|
|
7326
7714
|
child.on("close", (code) => {
|
|
7327
7715
|
if (code !== 0) {
|
|
7328
|
-
reject(new ApiError(stderr.trim() === "" ? `${
|
|
7716
|
+
reject(new ApiError(stderr.trim() === "" ? `${bin3} exited ${code}` : stderr.trim()));
|
|
7329
7717
|
return;
|
|
7330
7718
|
}
|
|
7331
7719
|
resolve2(stdout);
|
|
@@ -7349,9 +7737,9 @@ var EMPTY_MAP = { components: {}, tokens: {}, screens: {} };
|
|
|
7349
7737
|
async function readJson(file, what) {
|
|
7350
7738
|
let text;
|
|
7351
7739
|
try {
|
|
7352
|
-
text = await
|
|
7740
|
+
text = await readFile17(file, "utf8");
|
|
7353
7741
|
} catch {
|
|
7354
|
-
throw new UsageError(`${what} is missing ${
|
|
7742
|
+
throw new UsageError(`${what} is missing ${path23.basename(file)} (run rshow handoff first)`);
|
|
7355
7743
|
}
|
|
7356
7744
|
try {
|
|
7357
7745
|
return JSON.parse(text);
|
|
@@ -7360,13 +7748,13 @@ async function readJson(file, what) {
|
|
|
7360
7748
|
}
|
|
7361
7749
|
}
|
|
7362
7750
|
async function readHandoff(dir) {
|
|
7363
|
-
const spec = await readJson(
|
|
7751
|
+
const spec = await readJson(path23.join(dir, "spec.json"), dir);
|
|
7364
7752
|
if (spec === null || typeof spec !== "object" || !Array.isArray(spec.screens)) {
|
|
7365
7753
|
throw new UsageError(`${dir}/spec.json does not hold a handoff spec`);
|
|
7366
7754
|
}
|
|
7367
7755
|
let tokens = { light: [], dark: [] };
|
|
7368
7756
|
try {
|
|
7369
|
-
tokens = flattenDtcg(JSON.parse(await
|
|
7757
|
+
tokens = flattenDtcg(JSON.parse(await readFile17(path23.join(dir, "tokens.json"), "utf8")));
|
|
7370
7758
|
} catch {
|
|
7371
7759
|
tokens = { light: [], dark: [] };
|
|
7372
7760
|
}
|
|
@@ -7389,15 +7777,15 @@ function clipOf(info, screenId) {
|
|
|
7389
7777
|
async function runCompare(opts) {
|
|
7390
7778
|
const say = opts.onProgress ?? (() => {
|
|
7391
7779
|
});
|
|
7392
|
-
const dir =
|
|
7780
|
+
const dir = path23.resolve(opts.against);
|
|
7393
7781
|
const { spec, tokens } = await readHandoff(dir);
|
|
7394
|
-
const mapFile = opts.map === void 0 ? mapPath() :
|
|
7782
|
+
const mapFile = opts.map === void 0 ? mapPath() : path23.resolve(opts.map);
|
|
7395
7783
|
const map = await readMapFile(mapFile) ?? EMPTY_MAP;
|
|
7396
|
-
const mapDir =
|
|
7397
|
-
const reportDir =
|
|
7398
|
-
const imagesDir =
|
|
7399
|
-
await
|
|
7400
|
-
await
|
|
7784
|
+
const mapDir = path23.dirname(mapFile);
|
|
7785
|
+
const reportDir = path23.join(dir, "compare-report");
|
|
7786
|
+
const imagesDir = path23.join(reportDir, "images");
|
|
7787
|
+
await mkdir8(imagesDir, { recursive: true });
|
|
7788
|
+
await mkdir8(path23.join(dir, "diff"), { recursive: true });
|
|
7401
7789
|
const results = [];
|
|
7402
7790
|
const collected = [];
|
|
7403
7791
|
let skipped = 0;
|
|
@@ -7432,16 +7820,16 @@ async function runCompare(opts) {
|
|
|
7432
7820
|
throw new UsageError(`tmux capture failed for ${target}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7433
7821
|
});
|
|
7434
7822
|
const text = opts.tuiColors ? captured : stripAnsi(captured);
|
|
7435
|
-
const baselineFile =
|
|
7823
|
+
const baselineFile = path23.join(dir, "baseline", `${safeName(screen)}.txt`);
|
|
7436
7824
|
let expected;
|
|
7437
7825
|
try {
|
|
7438
|
-
expected = await
|
|
7826
|
+
expected = await readFile17(baselineFile, "utf8");
|
|
7439
7827
|
} catch {
|
|
7440
7828
|
expected = void 0;
|
|
7441
7829
|
}
|
|
7442
7830
|
if (expected === void 0) {
|
|
7443
|
-
await
|
|
7444
|
-
await
|
|
7831
|
+
await mkdir8(path23.dirname(baselineFile), { recursive: true });
|
|
7832
|
+
await writeFile10(baselineFile, text);
|
|
7445
7833
|
results.push({
|
|
7446
7834
|
screen,
|
|
7447
7835
|
state: "default",
|
|
@@ -7486,12 +7874,12 @@ async function runCompare(opts) {
|
|
|
7486
7874
|
const raw = (map.screens[screen] ?? "").trim();
|
|
7487
7875
|
say(`capture: ${raw}`);
|
|
7488
7876
|
let capture2;
|
|
7489
|
-
let
|
|
7877
|
+
let failure;
|
|
7490
7878
|
try {
|
|
7491
7879
|
capture2 = decodePng(await captureNative(group.source, mapDir));
|
|
7492
7880
|
} catch (err) {
|
|
7493
7881
|
if (err instanceof UsageError) throw err;
|
|
7494
|
-
|
|
7882
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
7495
7883
|
}
|
|
7496
7884
|
const screenSpec = spec.screens.find((item) => item.id === screen);
|
|
7497
7885
|
const rubricFile = nativeName(screen);
|
|
@@ -7510,13 +7898,13 @@ async function runCompare(opts) {
|
|
|
7510
7898
|
rubric: rubricFile,
|
|
7511
7899
|
mismatches: []
|
|
7512
7900
|
};
|
|
7513
|
-
if (
|
|
7514
|
-
results.push({ ...base, note: `capture failed: ${
|
|
7901
|
+
if (failure !== void 0 || capture2 === void 0) {
|
|
7902
|
+
results.push({ ...base, note: `capture failed: ${failure ?? "no pixels"}` });
|
|
7515
7903
|
continue;
|
|
7516
7904
|
}
|
|
7517
7905
|
let expectedImage;
|
|
7518
7906
|
try {
|
|
7519
|
-
expectedImage = decodePng(new Uint8Array(await
|
|
7907
|
+
expectedImage = decodePng(new Uint8Array(await readFile17(path23.join(dir, baseline.file))));
|
|
7520
7908
|
} catch {
|
|
7521
7909
|
results.push({ ...base, note: `missing baseline ${baseline.file}` });
|
|
7522
7910
|
continue;
|
|
@@ -7528,10 +7916,10 @@ async function runCompare(opts) {
|
|
|
7528
7916
|
threshold: opts.threshold
|
|
7529
7917
|
});
|
|
7530
7918
|
const diffPng = encodePng(diff.image);
|
|
7531
|
-
await
|
|
7532
|
-
await
|
|
7533
|
-
await
|
|
7534
|
-
await copyFile(
|
|
7919
|
+
await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
|
|
7920
|
+
await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
|
|
7921
|
+
await writeFile10(path23.join(imagesDir, `${stem}-capture.png`), encodePng(scaled));
|
|
7922
|
+
await copyFile(path23.join(dir, baseline.file), path23.join(imagesDir, `${stem}-baseline.png`));
|
|
7535
7923
|
const expected = screenSpec === void 0 ? [] : normativeColors(screenSpec, tokens, baseline.state, baseline.mode);
|
|
7536
7924
|
const palette = checkPalette(extractPalette(scaled), expected);
|
|
7537
7925
|
const pixelOk = diff.ratio <= opts.nativeMaxDiffRatio;
|
|
@@ -7576,7 +7964,7 @@ async function runCompare(opts) {
|
|
|
7576
7964
|
});
|
|
7577
7965
|
}
|
|
7578
7966
|
if (screenSpec !== void 0) {
|
|
7579
|
-
await
|
|
7967
|
+
await writeFile10(path23.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
|
|
7580
7968
|
`);
|
|
7581
7969
|
}
|
|
7582
7970
|
}
|
|
@@ -7633,12 +8021,12 @@ async function runCompare(opts) {
|
|
|
7633
8021
|
}
|
|
7634
8022
|
const clip = clipOf(info, screenId) ? await screenClip(page, screenId) : null;
|
|
7635
8023
|
const actual = await page.screenshot(clip ?? void 0);
|
|
7636
|
-
const actualFile =
|
|
7637
|
-
await
|
|
7638
|
-
const baselineFile =
|
|
8024
|
+
const actualFile = path23.join(imagesDir, `${stem}-actual.png`);
|
|
8025
|
+
await writeFile10(actualFile, actual);
|
|
8026
|
+
const baselineFile = path23.join(dir, baseline.file);
|
|
7639
8027
|
let expected;
|
|
7640
8028
|
try {
|
|
7641
|
-
expected = new Uint8Array(await
|
|
8029
|
+
expected = new Uint8Array(await readFile17(baselineFile));
|
|
7642
8030
|
} catch {
|
|
7643
8031
|
results.push({
|
|
7644
8032
|
screen: baseline.screen,
|
|
@@ -7653,14 +8041,14 @@ async function runCompare(opts) {
|
|
|
7653
8041
|
});
|
|
7654
8042
|
continue;
|
|
7655
8043
|
}
|
|
7656
|
-
const baselineCopy =
|
|
8044
|
+
const baselineCopy = path23.join(imagesDir, `${stem}-baseline.png`);
|
|
7657
8045
|
await copyFile(baselineFile, baselineCopy);
|
|
7658
8046
|
const left = decodePng(expected);
|
|
7659
8047
|
const right = decodePng(actual);
|
|
7660
8048
|
const diff = diffImages(left, right, { threshold: opts.threshold });
|
|
7661
8049
|
const diffPng = encodePng(diff.image);
|
|
7662
|
-
await
|
|
7663
|
-
await
|
|
8050
|
+
await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
|
|
8051
|
+
await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
|
|
7664
8052
|
const note = left.width === right.width && left.height === right.height ? void 0 : `size ${left.width}x${left.height} against ${right.width}x${right.height}`;
|
|
7665
8053
|
results.push({
|
|
7666
8054
|
screen: baseline.screen,
|
|
@@ -7729,8 +8117,8 @@ async function runCompare(opts) {
|
|
|
7729
8117
|
const checked = results.filter((item) => item.source !== "").length;
|
|
7730
8118
|
const review = results.filter((item) => item.status === "review").length;
|
|
7731
8119
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7732
|
-
await
|
|
7733
|
-
|
|
8120
|
+
await writeFile10(
|
|
8121
|
+
path23.join(reportDir, "report.json"),
|
|
7734
8122
|
`${JSON.stringify(
|
|
7735
8123
|
{
|
|
7736
8124
|
schema: 1,
|
|
@@ -7766,8 +8154,8 @@ async function runCompare(opts) {
|
|
|
7766
8154
|
)}
|
|
7767
8155
|
`
|
|
7768
8156
|
);
|
|
7769
|
-
await
|
|
7770
|
-
|
|
8157
|
+
await writeFile10(
|
|
8158
|
+
path23.join(reportDir, "index.html"),
|
|
7771
8159
|
reportHtml({
|
|
7772
8160
|
spec,
|
|
7773
8161
|
results,
|
|
@@ -7888,7 +8276,7 @@ function ratioValue(text, name, fallback) {
|
|
|
7888
8276
|
}
|
|
7889
8277
|
return value;
|
|
7890
8278
|
}
|
|
7891
|
-
async function
|
|
8279
|
+
async function run17(argv, ctx) {
|
|
7892
8280
|
const args = parseCommandArgs(
|
|
7893
8281
|
argv,
|
|
7894
8282
|
{
|
|
@@ -7918,7 +8306,7 @@ async function run18(argv, ctx) {
|
|
|
7918
8306
|
DEFAULT_NATIVE_MAX_DIFF_RATIO
|
|
7919
8307
|
),
|
|
7920
8308
|
tuiColors: boolValue(args, "tui-colors"),
|
|
7921
|
-
chrome:
|
|
8309
|
+
chrome: process20.env.CHROME,
|
|
7922
8310
|
onProgress: (line) => ctx.out.line(line)
|
|
7923
8311
|
});
|
|
7924
8312
|
const failing = result.results.filter((item) => !item.pass);
|
|
@@ -7966,8 +8354,8 @@ async function run18(argv, ctx) {
|
|
|
7966
8354
|
}
|
|
7967
8355
|
|
|
7968
8356
|
// src/commands/docs.ts
|
|
7969
|
-
import { mkdir as
|
|
7970
|
-
import
|
|
8357
|
+
import { mkdir as mkdir9, readFile as readFile18, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
|
|
8358
|
+
import path24 from "node:path";
|
|
7971
8359
|
var USAGE = {
|
|
7972
8360
|
publish: PUBLISH_USAGE,
|
|
7973
8361
|
check: CHECK_USAGE,
|
|
@@ -8246,7 +8634,7 @@ function heading(label) {
|
|
|
8246
8634
|
async function themeTokensPage() {
|
|
8247
8635
|
const dir = await builtinThemeDir("default");
|
|
8248
8636
|
if (dir === void 0) throw new Error("no built-in theme: default");
|
|
8249
|
-
const css = await
|
|
8637
|
+
const css = await readFile18(path24.join(dir, "tokens.css"), "utf8");
|
|
8250
8638
|
const end = css.indexOf("*/");
|
|
8251
8639
|
const roles = parseRoles(end < 0 ? css : css.slice(0, end));
|
|
8252
8640
|
const sections = parseSections(rootBlock(css));
|
|
@@ -8294,45 +8682,45 @@ function exitCodesPage() {
|
|
|
8294
8682
|
`;
|
|
8295
8683
|
}
|
|
8296
8684
|
async function generate(out) {
|
|
8297
|
-
const dir =
|
|
8685
|
+
const dir = path24.join(out, "commands");
|
|
8298
8686
|
await rm7(dir, { recursive: true, force: true });
|
|
8299
|
-
await
|
|
8687
|
+
await mkdir9(dir, { recursive: true });
|
|
8300
8688
|
const rows = commandRows();
|
|
8301
8689
|
const files = [];
|
|
8302
8690
|
for (const row of rows) {
|
|
8303
|
-
const file =
|
|
8304
|
-
await
|
|
8691
|
+
const file = path24.join(dir, row.file);
|
|
8692
|
+
await writeFile11(file, commandPage(row, rows));
|
|
8305
8693
|
files.push(file);
|
|
8306
8694
|
}
|
|
8307
|
-
const index =
|
|
8308
|
-
await
|
|
8695
|
+
const index = path24.join(dir, "index.md");
|
|
8696
|
+
await writeFile11(index, indexPage(rows));
|
|
8309
8697
|
files.push(index);
|
|
8310
8698
|
for (const [name, text] of [
|
|
8311
8699
|
["theme-tokens.md", await themeTokensPage()],
|
|
8312
8700
|
["exit-codes.md", exitCodesPage()]
|
|
8313
8701
|
]) {
|
|
8314
|
-
const file =
|
|
8315
|
-
await
|
|
8702
|
+
const file = path24.join(out, name);
|
|
8703
|
+
await writeFile11(file, text);
|
|
8316
8704
|
files.push(file);
|
|
8317
8705
|
}
|
|
8318
|
-
return { commands: rows.length, files: files.map((file) =>
|
|
8706
|
+
return { commands: rows.length, files: files.map((file) => path24.relative(out, file)).sort() };
|
|
8319
8707
|
}
|
|
8320
|
-
async function
|
|
8708
|
+
async function run18(argv, ctx) {
|
|
8321
8709
|
const args = parseCommandArgs(argv, { out: { type: "string" } }, 0, 0, DOCS_USAGE);
|
|
8322
8710
|
const out = stringValue(args, "out");
|
|
8323
8711
|
if (out === void 0) throw new UsageError(`docs needs --out <dir> (try: ${usageLine(DOCS_USAGE)})`);
|
|
8324
8712
|
const result = await generate(out);
|
|
8325
8713
|
ctx.out.data(result);
|
|
8326
|
-
ctx.out.line(`wrote ${result.commands} command pages and the index under ${
|
|
8327
|
-
ctx.out.line(`wrote ${
|
|
8328
|
-
ctx.out.line(`wrote ${
|
|
8714
|
+
ctx.out.line(`wrote ${result.commands} command pages and the index under ${path24.join(out, "commands")}`);
|
|
8715
|
+
ctx.out.line(`wrote ${path24.join(out, "theme-tokens.md")}`);
|
|
8716
|
+
ctx.out.line(`wrote ${path24.join(out, "exit-codes.md")}`);
|
|
8329
8717
|
}
|
|
8330
8718
|
|
|
8331
8719
|
// src/commands/login.ts
|
|
8332
|
-
import
|
|
8720
|
+
import process21 from "node:process";
|
|
8333
8721
|
async function hiddenLine() {
|
|
8334
|
-
|
|
8335
|
-
const stdin =
|
|
8722
|
+
process21.stderr.write("token: ");
|
|
8723
|
+
const stdin = process21.stdin;
|
|
8336
8724
|
stdin.setRawMode(true);
|
|
8337
8725
|
stdin.resume();
|
|
8338
8726
|
stdin.setEncoding("utf8");
|
|
@@ -8342,7 +8730,7 @@ async function hiddenLine() {
|
|
|
8342
8730
|
stdin.off("data", onData);
|
|
8343
8731
|
stdin.setRawMode(false);
|
|
8344
8732
|
stdin.pause();
|
|
8345
|
-
|
|
8733
|
+
process21.stderr.write("\n");
|
|
8346
8734
|
if (err === void 0) resolve2(text);
|
|
8347
8735
|
else reject(err);
|
|
8348
8736
|
};
|
|
@@ -8365,16 +8753,27 @@ async function hiddenLine() {
|
|
|
8365
8753
|
}
|
|
8366
8754
|
async function pipedText() {
|
|
8367
8755
|
const chunks = [];
|
|
8368
|
-
for await (const chunk2 of
|
|
8756
|
+
for await (const chunk2 of process21.stdin) chunks.push(chunk2);
|
|
8369
8757
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
8370
8758
|
}
|
|
8371
8759
|
async function tokenFromStdin() {
|
|
8372
|
-
const token =
|
|
8760
|
+
const token = process21.stdin.isTTY === true ? await hiddenLine() : await pipedText();
|
|
8373
8761
|
if (token === "") throw new UsageError("expected a token on stdin");
|
|
8374
8762
|
if (!token.startsWith(TOKEN_PREFIX)) throw new UsageError(`that is not an rshow token: it must start with ${TOKEN_PREFIX}`);
|
|
8375
8763
|
return token;
|
|
8376
8764
|
}
|
|
8377
|
-
async function
|
|
8765
|
+
async function saveIn(store2, url, token) {
|
|
8766
|
+
await store2.save(url, token);
|
|
8767
|
+
if (await store2.read(url) !== token) {
|
|
8768
|
+
throw new ApiError(`cannot save the token in ${store2.description}`);
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8771
|
+
function reasonOf(err) {
|
|
8772
|
+
const text = (err instanceof Error ? err.message : String(err)).replace(/^cannot save the token in the keyring: /, "");
|
|
8773
|
+
const lines = text.split("\n").map((part) => part.trim()).filter((part) => part !== "");
|
|
8774
|
+
return lines.at(-1) ?? "";
|
|
8775
|
+
}
|
|
8776
|
+
async function run19(argv, ctx, platform = process21.platform) {
|
|
8378
8777
|
const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGIN_USAGE);
|
|
8379
8778
|
const settings = await ctx.config();
|
|
8380
8779
|
const given = (stringValue(args, "url") ?? "").trim();
|
|
@@ -8383,48 +8782,69 @@ async function run20(argv, ctx) {
|
|
|
8383
8782
|
const me = (await new Client(url, token).send("GET", "/api/me")).body;
|
|
8384
8783
|
const email = typeof me?.email === "string" ? me.email : "";
|
|
8385
8784
|
const scope = typeof me?.scope === "string" ? me.scope : "";
|
|
8386
|
-
await
|
|
8785
|
+
let store2 = await choose(process21.env, platform);
|
|
8786
|
+
if (store2.backend === "keyring" && forced(process21.env) === void 0) {
|
|
8787
|
+
try {
|
|
8788
|
+
await saveIn(store2, url, token);
|
|
8789
|
+
} catch (err) {
|
|
8790
|
+
warn(`the Linux keyring cannot save the token (${reasonOf(err)}); saving it in a file`);
|
|
8791
|
+
await store2.remove(url).catch(() => {
|
|
8792
|
+
});
|
|
8793
|
+
store2 = fileStore(process21.env);
|
|
8794
|
+
await saveIn(store2, url, token);
|
|
8795
|
+
}
|
|
8796
|
+
} else {
|
|
8797
|
+
await saveIn(store2, url, token);
|
|
8798
|
+
}
|
|
8387
8799
|
ctx.out.data({ email, scope, url });
|
|
8388
|
-
ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}`);
|
|
8800
|
+
ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}, saved in ${store2.description}`);
|
|
8389
8801
|
}
|
|
8390
8802
|
|
|
8391
8803
|
// src/commands/logout.ts
|
|
8392
|
-
|
|
8804
|
+
import process22 from "node:process";
|
|
8805
|
+
async function run20(argv, ctx, platform = process22.platform) {
|
|
8393
8806
|
const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
|
|
8394
8807
|
const settings = await ctx.config();
|
|
8395
8808
|
const given = (stringValue(args, "url") ?? "").trim();
|
|
8396
8809
|
const url = (given === "" ? requireUrl(settings) : given).replace(/\/+$/, "");
|
|
8397
|
-
const removed =
|
|
8398
|
-
|
|
8810
|
+
const removed = [];
|
|
8811
|
+
for (const store2 of await every(process22.env, platform)) {
|
|
8812
|
+
if (await store2.remove(url)) removed.push(store2.description);
|
|
8813
|
+
}
|
|
8814
|
+
if (removed.length === 0) {
|
|
8815
|
+
ctx.out.line(`no saved token for ${url}`);
|
|
8816
|
+
return;
|
|
8817
|
+
}
|
|
8818
|
+
ctx.out.line(`logged out of ${url}, removed from ${removed.join(" and ")}`);
|
|
8399
8819
|
}
|
|
8400
8820
|
|
|
8401
8821
|
// src/main.ts
|
|
8402
8822
|
var COMMANDS = {
|
|
8403
|
-
publish: { usage: PUBLISH_USAGE, run
|
|
8404
|
-
check: { usage: CHECK_USAGE, run:
|
|
8405
|
-
handoff: { usage: HANDOFF_USAGE, run:
|
|
8406
|
-
compare: { usage: COMPARE_USAGE, run:
|
|
8407
|
-
preview: { usage: PREVIEW_USAGE, run:
|
|
8408
|
-
config: { usage: CONFIG_USAGE, run:
|
|
8409
|
-
init: { usage: INIT_USAGE, run:
|
|
8410
|
-
doctor: { usage: DOCTOR_USAGE, run:
|
|
8411
|
-
theme: { usage: THEME_USAGE, run:
|
|
8412
|
-
skills: { usage: SKILLS_USAGE, run:
|
|
8413
|
-
project: { usage: PROJECT_USAGE, run:
|
|
8414
|
-
tag: { usage: TAG_USAGE, run:
|
|
8415
|
-
list: { usage: LIST_USAGE, run:
|
|
8416
|
-
show: { usage: SHOW_USAGE, run:
|
|
8417
|
-
share: { usage: SHARE_USAGE, run:
|
|
8418
|
-
pin: { usage: PIN_USAGE, run:
|
|
8419
|
-
delete: { usage: DELETE_USAGE, run:
|
|
8420
|
-
login: { usage: LOGIN_USAGE, run:
|
|
8421
|
-
logout: { usage: LOGOUT_USAGE, run:
|
|
8823
|
+
publish: { usage: PUBLISH_USAGE, run },
|
|
8824
|
+
check: { usage: CHECK_USAGE, run: run2 },
|
|
8825
|
+
handoff: { usage: HANDOFF_USAGE, run: run16 },
|
|
8826
|
+
compare: { usage: COMPARE_USAGE, run: run17 },
|
|
8827
|
+
preview: { usage: PREVIEW_USAGE, run: run3 },
|
|
8828
|
+
config: { usage: CONFIG_USAGE, run: run4 },
|
|
8829
|
+
init: { usage: INIT_USAGE, run: run5 },
|
|
8830
|
+
doctor: { usage: DOCTOR_USAGE, run: run6 },
|
|
8831
|
+
theme: { usage: THEME_USAGE, run: run7 },
|
|
8832
|
+
skills: { usage: SKILLS_USAGE, run: run8 },
|
|
8833
|
+
project: { usage: PROJECT_USAGE, run: run14 },
|
|
8834
|
+
tag: { usage: TAG_USAGE, run: run15 },
|
|
8835
|
+
list: { usage: LIST_USAGE, run: run9 },
|
|
8836
|
+
show: { usage: SHOW_USAGE, run: run10 },
|
|
8837
|
+
share: { usage: SHARE_USAGE, run: run11 },
|
|
8838
|
+
pin: { usage: PIN_USAGE, run: run12 },
|
|
8839
|
+
delete: { usage: DELETE_USAGE, run: run13 },
|
|
8840
|
+
login: { usage: LOGIN_USAGE, run: run19 },
|
|
8841
|
+
logout: { usage: LOGOUT_USAGE, run: run20 },
|
|
8422
8842
|
// Kept out of the general help: the docs site runs it, a person rarely does.
|
|
8423
|
-
docs: { usage: DOCS_USAGE, run:
|
|
8843
|
+
docs: { usage: DOCS_USAGE, run: run18 }
|
|
8424
8844
|
};
|
|
8425
8845
|
function fail(err) {
|
|
8426
8846
|
const message = err instanceof Error ? err.message : String(err);
|
|
8427
|
-
|
|
8847
|
+
process23.stderr.write(`rshow: ${message}
|
|
8428
8848
|
`);
|
|
8429
8849
|
if (err instanceof UsageError) return EXIT.usage;
|
|
8430
8850
|
if (err instanceof CheckError) return EXIT.check;
|
|
@@ -8442,17 +8862,17 @@ async function main(argv) {
|
|
|
8442
8862
|
const spec = name === void 0 ? void 0 : COMMANDS[name];
|
|
8443
8863
|
if (wantsVersion) {
|
|
8444
8864
|
const suffix = await isBinary() ? " (binary)" : "";
|
|
8445
|
-
|
|
8865
|
+
process23.stdout.write(`rshow ${VERSION}${suffix}
|
|
8446
8866
|
`);
|
|
8447
8867
|
return EXIT.ok;
|
|
8448
8868
|
}
|
|
8449
8869
|
if (wantsHelp) {
|
|
8450
|
-
|
|
8870
|
+
process23.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
|
|
8451
8871
|
`);
|
|
8452
8872
|
return EXIT.ok;
|
|
8453
8873
|
}
|
|
8454
8874
|
if (name === void 0) {
|
|
8455
|
-
|
|
8875
|
+
process23.stderr.write(`${GENERAL_USAGE}
|
|
8456
8876
|
`);
|
|
8457
8877
|
return EXIT.usage;
|
|
8458
8878
|
}
|
|
@@ -8463,12 +8883,12 @@ async function main(argv) {
|
|
|
8463
8883
|
const rest = [...args.slice(0, index), ...args.slice(index + 1)];
|
|
8464
8884
|
const configs = /* @__PURE__ */ new Map();
|
|
8465
8885
|
const clients = /* @__PURE__ */ new Map();
|
|
8466
|
-
const keyOf = (dir) => dir === void 0 ? "" :
|
|
8886
|
+
const keyOf = (dir) => dir === void 0 ? "" : path25.resolve(dir);
|
|
8467
8887
|
const configFor = (dir) => {
|
|
8468
8888
|
const key2 = keyOf(dir);
|
|
8469
8889
|
let pending = configs.get(key2);
|
|
8470
8890
|
if (pending === void 0) {
|
|
8471
|
-
pending = loadConfig(
|
|
8891
|
+
pending = loadConfig(process23.env, dir);
|
|
8472
8892
|
configs.set(key2, pending);
|
|
8473
8893
|
}
|
|
8474
8894
|
return pending;
|
|
@@ -8481,7 +8901,7 @@ async function main(argv) {
|
|
|
8481
8901
|
if (cached !== void 0) return cached;
|
|
8482
8902
|
const settings = await configFor(dir);
|
|
8483
8903
|
const url = requireUrl(settings);
|
|
8484
|
-
const client = new Client(url, await readToken(
|
|
8904
|
+
const client = new Client(url, await readToken(process23.env, url));
|
|
8485
8905
|
clients.set(key2, client);
|
|
8486
8906
|
return client;
|
|
8487
8907
|
},
|
|
@@ -8490,11 +8910,11 @@ async function main(argv) {
|
|
|
8490
8910
|
const code = await spec.run(rest, ctx);
|
|
8491
8911
|
return code ?? EXIT.ok;
|
|
8492
8912
|
}
|
|
8493
|
-
main(
|
|
8913
|
+
main(process23.argv.slice(2)).then(
|
|
8494
8914
|
(code) => {
|
|
8495
|
-
|
|
8915
|
+
process23.exitCode = code;
|
|
8496
8916
|
},
|
|
8497
8917
|
(err) => {
|
|
8498
|
-
|
|
8918
|
+
process23.exitCode = fail(err);
|
|
8499
8919
|
}
|
|
8500
8920
|
);
|