rshow 0.0.2 → 0.0.3
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 +904 -522
- 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.3" : 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,306 @@ 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
|
+
async function every(env = process8.env, platform = process8.platform) {
|
|
758
|
+
const stores = [];
|
|
759
|
+
if (canSave(platform, env.RSHOW_SECURITY_BIN)) stores.push(storeOf("keychain", env));
|
|
760
|
+
if (canUse(env, platform) && await usable(env)) stores.push(storeOf("keyring", env));
|
|
761
|
+
stores.push(storeOf("file", env));
|
|
762
|
+
return stores;
|
|
763
|
+
}
|
|
764
|
+
|
|
409
765
|
// src/config.ts
|
|
410
766
|
var DEFAULT_OUT = "dist";
|
|
411
767
|
var GLOBAL_DIR = "rshow";
|
|
412
768
|
var GLOBAL_FILE = "config.json";
|
|
413
769
|
var LOCAL_FILE = "rshow.json";
|
|
414
770
|
var NO_SERVER = "no server configured: run rshow init --url <admin-url> --art <viewer-url>";
|
|
415
|
-
function
|
|
771
|
+
function messageOf3(err) {
|
|
416
772
|
return err instanceof Error ? err.message : String(err);
|
|
417
773
|
}
|
|
418
|
-
function
|
|
774
|
+
function isRecord2(value) {
|
|
419
775
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
420
776
|
}
|
|
421
777
|
function withoutSlash(value) {
|
|
@@ -424,14 +780,14 @@ function withoutSlash(value) {
|
|
|
424
780
|
function globalConfigPath(env) {
|
|
425
781
|
const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
|
|
426
782
|
const home = (env.HOME ?? "").trim();
|
|
427
|
-
const base = xdg !== "" ? xdg :
|
|
428
|
-
return
|
|
783
|
+
const base = xdg !== "" ? xdg : path3.join(home !== "" ? home : homedir3(), ".config");
|
|
784
|
+
return path3.join(base, GLOBAL_DIR, GLOBAL_FILE);
|
|
429
785
|
}
|
|
430
786
|
function localConfigPath(dir) {
|
|
431
|
-
return
|
|
787
|
+
return path3.join(path3.resolve(dir), LOCAL_FILE);
|
|
432
788
|
}
|
|
433
789
|
function layerOf(file, value) {
|
|
434
|
-
if (!
|
|
790
|
+
if (!isRecord2(value)) throw new UsageError(`${file} must hold a JSON object`);
|
|
435
791
|
const fieldOf = (key2) => {
|
|
436
792
|
const raw = value[key2];
|
|
437
793
|
if (raw === void 0 || raw === null) return void 0;
|
|
@@ -451,7 +807,7 @@ function layerOf(file, value) {
|
|
|
451
807
|
};
|
|
452
808
|
const rawTokens = value.tokens;
|
|
453
809
|
if (rawTokens !== void 0 && rawTokens !== null) {
|
|
454
|
-
if (!
|
|
810
|
+
if (!isRecord2(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
|
|
455
811
|
const tokens = {};
|
|
456
812
|
for (const [key2, raw] of Object.entries(rawTokens)) {
|
|
457
813
|
if (typeof raw !== "string") throw new UsageError(`${file}: tokens.${key2} must be a string`);
|
|
@@ -473,10 +829,10 @@ function layerOf(file, value) {
|
|
|
473
829
|
async function readConfigFile(file) {
|
|
474
830
|
let text;
|
|
475
831
|
try {
|
|
476
|
-
text = await
|
|
832
|
+
text = await readFile3(file, "utf8");
|
|
477
833
|
} catch (err) {
|
|
478
834
|
if (err.code === "ENOENT") return {};
|
|
479
|
-
throw new UsageError(`cannot read ${file}: ${
|
|
835
|
+
throw new UsageError(`cannot read ${file}: ${messageOf3(err)}`);
|
|
480
836
|
}
|
|
481
837
|
let parsed;
|
|
482
838
|
try {
|
|
@@ -538,13 +894,14 @@ function requireArt(config) {
|
|
|
538
894
|
async function readToken(env, url) {
|
|
539
895
|
const token = (env.RSHOW_TOKEN ?? "").trim();
|
|
540
896
|
if (token !== "") return token;
|
|
541
|
-
const
|
|
897
|
+
const store2 = await choose(env);
|
|
898
|
+
const saved = await store2.read(url);
|
|
542
899
|
if (saved !== void 0) return saved;
|
|
543
900
|
throw new UsageError("no token: run rshow login, or set RSHOW_TOKEN");
|
|
544
901
|
}
|
|
545
902
|
|
|
546
903
|
// src/output.ts
|
|
547
|
-
import
|
|
904
|
+
import process9 from "node:process";
|
|
548
905
|
var Output = class {
|
|
549
906
|
json;
|
|
550
907
|
constructor(json) {
|
|
@@ -552,16 +909,16 @@ var Output = class {
|
|
|
552
909
|
}
|
|
553
910
|
data(value) {
|
|
554
911
|
if (!this.json) return;
|
|
555
|
-
|
|
912
|
+
process9.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
|
|
556
913
|
`);
|
|
557
914
|
}
|
|
558
915
|
line(text) {
|
|
559
916
|
if (this.json) return;
|
|
560
|
-
|
|
917
|
+
process9.stdout.write(`${text}
|
|
561
918
|
`);
|
|
562
919
|
}
|
|
563
920
|
raw(text) {
|
|
564
|
-
|
|
921
|
+
process9.stdout.write(`${text}
|
|
565
922
|
`);
|
|
566
923
|
}
|
|
567
924
|
};
|
|
@@ -591,7 +948,7 @@ commands:
|
|
|
591
948
|
share <slug> set mode or change shares
|
|
592
949
|
pin <slug> <n|live> serve version n, or live for the newest
|
|
593
950
|
delete <slug> --yes delete an artifact
|
|
594
|
-
login [--url u] save the API token
|
|
951
|
+
login [--url u] save the API token for this account
|
|
595
952
|
logout [--url u] remove the saved token for a server
|
|
596
953
|
|
|
597
954
|
global:
|
|
@@ -602,9 +959,13 @@ global:
|
|
|
602
959
|
environment:
|
|
603
960
|
RSHOW_URL API base URL, overrides the config file
|
|
604
961
|
RSHOW_ART art base URL, overrides the config file
|
|
605
|
-
RSHOW_TOKEN the API token, else the token
|
|
606
|
-
|
|
607
|
-
|
|
962
|
+
RSHOW_TOKEN the API token, else the token rshow login saved in
|
|
963
|
+
the macOS Keychain, the Linux keyring or a 0600 file
|
|
964
|
+
RSHOW_TOKEN_STORE force the keychain, keyring or file token store
|
|
965
|
+
XDG_CONFIG_HOME where rshow keeps config.json and credentials
|
|
966
|
+
RSHOW_SYSTEM_CA set to 0 to trust only the roots node bundles
|
|
967
|
+
RSHOW_EXTRA_CA_FILE a PEM file of extra roots, such as a cluster's CA
|
|
968
|
+
NODE_USE_ENV_PROXY set to 1 so fetch uses https_proxy (Node 24+)
|
|
608
969
|
|
|
609
970
|
Start here: rshow init --url <admin-url> --art <viewer-url>, then rshow login.
|
|
610
971
|
|
|
@@ -702,8 +1063,8 @@ files win. Run rshow init to write url and art.
|
|
|
702
1063
|
A theme with no version, or @latest, is looked up on the art host and printed
|
|
703
1064
|
as <name>@<n>; when that fails a warning goes to stderr. --offline skips it, and
|
|
704
1065
|
so does a config with no art host.
|
|
705
|
-
The token is RSHOW_TOKEN, else the token
|
|
706
|
-
Keychain
|
|
1066
|
+
The token is RSHOW_TOKEN, else the token rshow login saved in the macOS
|
|
1067
|
+
Keychain, the Linux keyring or a file with mode 600.`;
|
|
707
1068
|
var INIT_USAGE = `usage: rshow init --url <admin-url> --art <viewer-url> [--theme <name>] [--force]
|
|
708
1069
|
|
|
709
1070
|
Writes the global config file that rshow reads first:
|
|
@@ -723,6 +1084,7 @@ package, the config files, the token, the api, the art host, the theme and the
|
|
|
723
1084
|
skills, one line each: ok|warn|fail <check> <detail>. With [dir] it reads
|
|
724
1085
|
<dir>/rshow.json too. A skill line warns and never fails.
|
|
725
1086
|
Network calls time out after 5 s. Exit 0 when no check failed, 6 when one did.
|
|
1087
|
+
The token line names its source: env, keychain, keyring, or file and its path.
|
|
726
1088
|
The token is never printed.`;
|
|
727
1089
|
var THEME_USAGE = `usage: rshow theme <new|check|preview|publish|list> [options]
|
|
728
1090
|
|
|
@@ -849,14 +1211,19 @@ var DELETE_USAGE = `usage: rshow delete <slug> --yes
|
|
|
849
1211
|
Deletes the artifact and needs --yes.`;
|
|
850
1212
|
var LOGIN_USAGE = `usage: rshow login [--url u]
|
|
851
1213
|
|
|
852
|
-
Reads the token from stdin, checks it
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1214
|
+
Reads the token from stdin, checks it with the API, then saves it in the macOS
|
|
1215
|
+
Keychain, in the Linux keyring, or in $XDG_CONFIG_HOME/rshow/credentials with
|
|
1216
|
+
mode 600: the first store this machine has, and it reads it back to confirm.
|
|
1217
|
+
On a terminal it asks for the token with echo off, so it is never typed on a
|
|
1218
|
+
command line or pasted anywhere. The API base URL is the account, so each
|
|
1219
|
+
server keeps its own token; --url logs in to another one.
|
|
1220
|
+
Prints the email, the scope and where the token went.
|
|
1221
|
+
RSHOW_TOKEN still wins over the saved token, and RSHOW_TOKEN_STORE forces one
|
|
1222
|
+
of keychain, keyring or file.`;
|
|
857
1223
|
var LOGOUT_USAGE = `usage: rshow logout [--url u]
|
|
858
1224
|
|
|
859
|
-
Removes the token this server has
|
|
1225
|
+
Removes the token this server has from every store that holds it: the macOS
|
|
1226
|
+
Keychain, the Linux keyring and the 0600 credentials file.`;
|
|
860
1227
|
var PROJECT_USAGE = `usage: rshow project <create|list|show|share|delete> [options]
|
|
861
1228
|
|
|
862
1229
|
project create <slug> [--title t] [--description d]
|
|
@@ -982,14 +1349,14 @@ function usageLine(usage) {
|
|
|
982
1349
|
}
|
|
983
1350
|
|
|
984
1351
|
// src/commands/publish.ts
|
|
985
|
-
import { stat as
|
|
986
|
-
import
|
|
1352
|
+
import { stat as stat5 } from "node:fs/promises";
|
|
1353
|
+
import path8 from "node:path";
|
|
987
1354
|
|
|
988
1355
|
// src/args.ts
|
|
989
1356
|
import { parseArgs } from "node:util";
|
|
990
1357
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["--add", "--editor", "--remove", "--tag"]);
|
|
991
1358
|
var LOWERCASE_TAG = new RegExp(TAG_PATTERN.source, "i");
|
|
992
|
-
function
|
|
1359
|
+
function messageOf4(err) {
|
|
993
1360
|
return err instanceof Error ? err.message : String(err);
|
|
994
1361
|
}
|
|
995
1362
|
function isListValue(token) {
|
|
@@ -1025,7 +1392,7 @@ function parseCommandArgs(argv, options, minPositionals, maxPositionals, hint) {
|
|
|
1025
1392
|
try {
|
|
1026
1393
|
parsed = parseArgs({ args: expanded, options, allowPositionals: true, strict: true });
|
|
1027
1394
|
} catch (err) {
|
|
1028
|
-
throw new UsageError(`${
|
|
1395
|
+
throw new UsageError(`${messageOf4(err)} (try: ${usageLine(hint)})`);
|
|
1029
1396
|
}
|
|
1030
1397
|
const count = parsed.positionals.length;
|
|
1031
1398
|
if (count < minPositionals || count > maxPositionals) {
|
|
@@ -1057,21 +1424,21 @@ function emailList(args, name) {
|
|
|
1057
1424
|
|
|
1058
1425
|
// src/build.ts
|
|
1059
1426
|
import { spawn as spawn2 } from "node:child_process";
|
|
1060
|
-
import
|
|
1061
|
-
function
|
|
1427
|
+
import process10 from "node:process";
|
|
1428
|
+
function messageOf5(err) {
|
|
1062
1429
|
return err instanceof Error ? err.message : String(err);
|
|
1063
1430
|
}
|
|
1064
1431
|
function shellPath(env) {
|
|
1065
1432
|
const shell = (env.SHELL ?? "").trim();
|
|
1066
1433
|
return shell === "" ? "/bin/sh" : shell;
|
|
1067
1434
|
}
|
|
1068
|
-
async function runBuild(dir, command, env =
|
|
1435
|
+
async function runBuild(dir, command, env = process10.env) {
|
|
1069
1436
|
const code = await new Promise((resolve2, reject) => {
|
|
1070
1437
|
const child = spawn2(shellPath(env), ["-c", command], { cwd: dir, stdio: "inherit" });
|
|
1071
1438
|
child.once("error", reject);
|
|
1072
1439
|
child.once("close", (value) => resolve2(value));
|
|
1073
1440
|
}).catch((err) => {
|
|
1074
|
-
throw new BuildError(`build failed: ${
|
|
1441
|
+
throw new BuildError(`build failed: ${messageOf5(err)}`);
|
|
1075
1442
|
});
|
|
1076
1443
|
if (code !== 0) {
|
|
1077
1444
|
throw new BuildError(`build failed: ${code === null ? "killed by a signal" : `exit ${code}`}`);
|
|
@@ -1079,12 +1446,12 @@ async function runBuild(dir, command, env = process4.env) {
|
|
|
1079
1446
|
}
|
|
1080
1447
|
|
|
1081
1448
|
// src/check.ts
|
|
1082
|
-
import { readFile as
|
|
1083
|
-
import
|
|
1449
|
+
import { readFile as readFile6, readdir, stat as stat3 } from "node:fs/promises";
|
|
1450
|
+
import path6 from "node:path";
|
|
1084
1451
|
|
|
1085
1452
|
// src/mockup.ts
|
|
1086
|
-
import { readFile as
|
|
1087
|
-
import
|
|
1453
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1454
|
+
import path4 from "node:path";
|
|
1088
1455
|
var MAX_SCAN_BYTES = 4 * 1024 * 1024;
|
|
1089
1456
|
var HTML_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm"]);
|
|
1090
1457
|
var MIN_COLS = 80;
|
|
@@ -1154,20 +1521,20 @@ function idOf(value) {
|
|
|
1154
1521
|
const id = String(value ?? "").trim().replace(/^#/, "");
|
|
1155
1522
|
return id === "" ? void 0 : id;
|
|
1156
1523
|
}
|
|
1157
|
-
function
|
|
1524
|
+
function warn2(scan, line, message) {
|
|
1158
1525
|
scan.out.push({ path: scan.path, line, message });
|
|
1159
1526
|
}
|
|
1160
1527
|
function keyHint(scan, masked, from, to, line) {
|
|
1161
1528
|
const hit = TERMINAL_KEYS.exec(masked.slice(from, to).replace(ANY_TAG, ""));
|
|
1162
1529
|
if (hit === null) return;
|
|
1163
|
-
|
|
1530
|
+
warn2(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
|
|
1164
1531
|
}
|
|
1165
1532
|
function sizeGate(scan, attrs, line) {
|
|
1166
1533
|
if (attrs.has("data-allow-small")) return;
|
|
1167
1534
|
const cols = sizeOf(attrs, "data-cols", MIN_COLS);
|
|
1168
1535
|
const rows = sizeOf(attrs, "data-rows", MIN_ROWS);
|
|
1169
1536
|
if (cols >= MIN_COLS && rows >= MIN_ROWS) return;
|
|
1170
|
-
|
|
1537
|
+
warn2(scan, line, `tui below the 80x24 minimum (${cols}x${rows}), or mark it with data-allow-small`);
|
|
1171
1538
|
}
|
|
1172
1539
|
function walk(scan, masked) {
|
|
1173
1540
|
const stack = [];
|
|
@@ -1247,25 +1614,25 @@ function resolve(scan) {
|
|
|
1247
1614
|
ids.add(item.id);
|
|
1248
1615
|
const count = (seen.get(item.id) ?? 0) + 1;
|
|
1249
1616
|
seen.set(item.id, count);
|
|
1250
|
-
if (count === 2)
|
|
1617
|
+
if (count === 2) warn2(scan, item.line, `duplicate artboard id: ${item.id}`);
|
|
1251
1618
|
}
|
|
1252
1619
|
const unknown = /* @__PURE__ */ new Set();
|
|
1253
1620
|
for (const flow of scan.flows) {
|
|
1254
1621
|
if (ids.has(flow.id) || unknown.has(flow.id)) continue;
|
|
1255
1622
|
unknown.add(flow.id);
|
|
1256
|
-
|
|
1623
|
+
warn2(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
|
|
1257
1624
|
}
|
|
1258
1625
|
for (const item of scan.boards) {
|
|
1259
|
-
if (!item.title)
|
|
1626
|
+
if (!item.title) warn2(scan, item.line, `artboard ${item.label} has no data-title`);
|
|
1260
1627
|
for (const block of item.blocks) {
|
|
1261
1628
|
const missing = block.states.filter((state) => state !== "default" && !item.states.includes(state));
|
|
1262
1629
|
if (missing.length === 0) continue;
|
|
1263
|
-
|
|
1630
|
+
warn2(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
|
|
1264
1631
|
}
|
|
1265
1632
|
for (const state of item.states) {
|
|
1266
1633
|
if (state === "default") continue;
|
|
1267
1634
|
if (item.blocks.some((block) => block.states.includes(state))) continue;
|
|
1268
|
-
|
|
1635
|
+
warn2(scan, item.line, `artboard ${item.label} lists state "${state}" that no block uses`);
|
|
1269
1636
|
}
|
|
1270
1637
|
}
|
|
1271
1638
|
}
|
|
@@ -1279,9 +1646,9 @@ function htmlWarnings(rel, text) {
|
|
|
1279
1646
|
async function mockupWarnings(entries) {
|
|
1280
1647
|
const out = [];
|
|
1281
1648
|
for (const entry of entries) {
|
|
1282
|
-
const ext =
|
|
1649
|
+
const ext = path4.extname(entry.rel).toLowerCase();
|
|
1283
1650
|
if (!HTML_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES) continue;
|
|
1284
|
-
const text = await
|
|
1651
|
+
const text = await readFile4(entry.abs, "utf8").catch(() => void 0);
|
|
1285
1652
|
if (text === void 0) continue;
|
|
1286
1653
|
out.push(...htmlWarnings(entry.rel, text));
|
|
1287
1654
|
}
|
|
@@ -1289,8 +1656,8 @@ async function mockupWarnings(entries) {
|
|
|
1289
1656
|
}
|
|
1290
1657
|
|
|
1291
1658
|
// src/style.ts
|
|
1292
|
-
import { readFile as
|
|
1293
|
-
import
|
|
1659
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1660
|
+
import path5 from "node:path";
|
|
1294
1661
|
var MAX_SCAN_BYTES2 = 4 * 1024 * 1024;
|
|
1295
1662
|
var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css"]);
|
|
1296
1663
|
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
@@ -1687,9 +2054,9 @@ async function styleWarnings(entries) {
|
|
|
1687
2054
|
const loaded = /* @__PURE__ */ new Set();
|
|
1688
2055
|
const used = [];
|
|
1689
2056
|
for (const entry of entries) {
|
|
1690
|
-
const ext =
|
|
2057
|
+
const ext = path5.extname(entry.rel).toLowerCase();
|
|
1691
2058
|
if (!STYLE_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES2) continue;
|
|
1692
|
-
const text = await
|
|
2059
|
+
const text = await readFile5(entry.abs, "utf8").catch(() => void 0);
|
|
1693
2060
|
if (text === void 0) continue;
|
|
1694
2061
|
collectLoaded(text, loaded);
|
|
1695
2062
|
if (stylePathSkipped(entry.rel)) continue;
|
|
@@ -1740,9 +2107,6 @@ var CONNECT_PATTERNS = [
|
|
|
1740
2107
|
pattern: /\.open\s*\(\s*["'`][A-Za-z]+["'`]\s*,\s*["'`](https?:\/\/[^"'`\s)]+)/g
|
|
1741
2108
|
}
|
|
1742
2109
|
];
|
|
1743
|
-
function messageOf5(err) {
|
|
1744
|
-
return err instanceof Error ? err.message : String(err);
|
|
1745
|
-
}
|
|
1746
2110
|
function add(scan, file, line, severity, message) {
|
|
1747
2111
|
scan.findings.push({ path: file, line, severity, message });
|
|
1748
2112
|
}
|
|
@@ -1793,7 +2157,7 @@ function attributes2(raw) {
|
|
|
1793
2157
|
}
|
|
1794
2158
|
return found;
|
|
1795
2159
|
}
|
|
1796
|
-
function
|
|
2160
|
+
function hostOf2(value) {
|
|
1797
2161
|
if (!EXTERNAL.test(value) || IGNORED.test(value)) return void 0;
|
|
1798
2162
|
try {
|
|
1799
2163
|
const host = new URL(value, BASE_URL).hostname;
|
|
@@ -1817,7 +2181,7 @@ function resolvePath(file, ref) {
|
|
|
1817
2181
|
decoded = ref;
|
|
1818
2182
|
}
|
|
1819
2183
|
const wantsIndex = decoded.endsWith("/");
|
|
1820
|
-
const base =
|
|
2184
|
+
const base = path6.posix.dirname(file);
|
|
1821
2185
|
const parts2 = base === "." ? [] : base.split("/");
|
|
1822
2186
|
for (const segment of decoded.split("/")) {
|
|
1823
2187
|
if (segment === "" || segment === ".") continue;
|
|
@@ -1868,7 +2232,7 @@ function resolveRef(scan, file, raw, line) {
|
|
|
1868
2232
|
}
|
|
1869
2233
|
function checkScript(scan, file, line, attrs) {
|
|
1870
2234
|
const value = (attrs.get("src") ?? "").trim();
|
|
1871
|
-
const host =
|
|
2235
|
+
const host = hostOf2(value);
|
|
1872
2236
|
if (host === void 0) return;
|
|
1873
2237
|
if (!SCRIPT_HOSTS.includes(host)) {
|
|
1874
2238
|
add(scan, file, line, "error", `script host not allowed: ${value} (allowed: ${SCRIPT_HOSTS.join(", ")})`);
|
|
@@ -1884,7 +2248,7 @@ function checkLink(scan, file, line, attrs) {
|
|
|
1884
2248
|
const rel = (attrs.get("rel") ?? "").toLowerCase().split(/\s+/);
|
|
1885
2249
|
if (!rel.includes("stylesheet")) return;
|
|
1886
2250
|
const value = (attrs.get("href") ?? "").trim();
|
|
1887
|
-
const host =
|
|
2251
|
+
const host = hostOf2(value);
|
|
1888
2252
|
if (host === void 0 || STYLE_HOSTS.includes(host)) return;
|
|
1889
2253
|
add(scan, file, line, "error", `stylesheet host not allowed: ${value} (allowed: ${STYLE_HOSTS.join(", ")})`);
|
|
1890
2254
|
}
|
|
@@ -1940,9 +2304,9 @@ function scanHtml(scan, file, text, facts) {
|
|
|
1940
2304
|
scanConnect(scan, file, masked, at);
|
|
1941
2305
|
}
|
|
1942
2306
|
async function scanFile(scan, entry, facts) {
|
|
1943
|
-
const ext =
|
|
2307
|
+
const ext = path6.extname(entry.rel).toLowerCase();
|
|
1944
2308
|
if (!SCAN_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES3) return;
|
|
1945
|
-
const text = await
|
|
2309
|
+
const text = await readFile6(entry.abs, "utf8");
|
|
1946
2310
|
if (ext === ".html" || ext === ".htm") {
|
|
1947
2311
|
scanHtml(scan, entry.rel, text, facts);
|
|
1948
2312
|
return;
|
|
@@ -1957,7 +2321,7 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1957
2321
|
for (const item of items) {
|
|
1958
2322
|
if (item.name.startsWith(".") || SKIPPED_NAMES.has(item.name)) continue;
|
|
1959
2323
|
const rel = prefix === "" ? item.name : `${prefix}/${item.name}`;
|
|
1960
|
-
const abs =
|
|
2324
|
+
const abs = path6.join(dir, item.name);
|
|
1961
2325
|
const problem = pathProblem(rel);
|
|
1962
2326
|
if (problem !== void 0) add(scan, rel, 1, "error", problem);
|
|
1963
2327
|
if (item.isDirectory()) {
|
|
@@ -1965,7 +2329,7 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1965
2329
|
continue;
|
|
1966
2330
|
}
|
|
1967
2331
|
if (!item.isFile()) continue;
|
|
1968
|
-
const info = await
|
|
2332
|
+
const info = await stat3(abs).catch(() => void 0);
|
|
1969
2333
|
if (info === void 0) continue;
|
|
1970
2334
|
entries.push({ rel, abs, size: info.size });
|
|
1971
2335
|
}
|
|
@@ -1973,11 +2337,12 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1973
2337
|
async function checkThemeFile(scan, key2, rel, where) {
|
|
1974
2338
|
const url = `${scan.art}/_themes/${key2}/${rel}`;
|
|
1975
2339
|
let status2;
|
|
2340
|
+
await readyNetwork();
|
|
1976
2341
|
try {
|
|
1977
2342
|
const reply = await scan.fetchImpl(url, { method: "HEAD" });
|
|
1978
2343
|
status2 = reply.status;
|
|
1979
2344
|
} catch (err) {
|
|
1980
|
-
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${
|
|
2345
|
+
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: ${networkDetail(err, url)}`);
|
|
1981
2346
|
return;
|
|
1982
2347
|
}
|
|
1983
2348
|
if (status2 === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
|
|
@@ -1989,11 +2354,12 @@ async function checkTheme(scan, key2, where) {
|
|
|
1989
2354
|
if (scan.offline || scan.art === "") return;
|
|
1990
2355
|
const url = `${scan.art}/_themes/${key2}/tokens.css`;
|
|
1991
2356
|
let status2;
|
|
2357
|
+
await readyNetwork();
|
|
1992
2358
|
try {
|
|
1993
2359
|
const reply = await scan.fetchImpl(url, { method: "HEAD" });
|
|
1994
2360
|
status2 = reply.status;
|
|
1995
2361
|
} catch (err) {
|
|
1996
|
-
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${
|
|
2362
|
+
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: ${networkDetail(err, url)}`);
|
|
1997
2363
|
return;
|
|
1998
2364
|
}
|
|
1999
2365
|
if (status2 === 404) {
|
|
@@ -2011,8 +2377,8 @@ async function checkTheme(scan, key2, where) {
|
|
|
2011
2377
|
}
|
|
2012
2378
|
}
|
|
2013
2379
|
async function checkFolder(input, opts = {}) {
|
|
2014
|
-
const root2 =
|
|
2015
|
-
const info = await
|
|
2380
|
+
const root2 = path6.resolve(input);
|
|
2381
|
+
const info = await stat3(root2).catch(() => void 0);
|
|
2016
2382
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
2017
2383
|
const scan = {
|
|
2018
2384
|
files: /* @__PURE__ */ new Set(),
|
|
@@ -2104,7 +2470,7 @@ function reportCheck(out, result) {
|
|
|
2104
2470
|
out.line(summaryLine(result));
|
|
2105
2471
|
}
|
|
2106
2472
|
async function enforceCheck(out, target, opts = {}) {
|
|
2107
|
-
const info = await
|
|
2473
|
+
const info = await stat3(target).catch(() => void 0);
|
|
2108
2474
|
if (info === void 0 || !info.isDirectory()) return;
|
|
2109
2475
|
const result = await checkFolder(target, opts);
|
|
2110
2476
|
if (result.findings.length > 0) reportCheck(out, result);
|
|
@@ -2112,12 +2478,12 @@ async function enforceCheck(out, target, opts = {}) {
|
|
|
2112
2478
|
}
|
|
2113
2479
|
|
|
2114
2480
|
// src/publish.ts
|
|
2115
|
-
import { readFile as
|
|
2481
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
2116
2482
|
|
|
2117
2483
|
// src/files.ts
|
|
2118
2484
|
import { createHash } from "node:crypto";
|
|
2119
|
-
import { readdir as readdir2, readFile as
|
|
2120
|
-
import
|
|
2485
|
+
import { readdir as readdir2, readFile as readFile7, stat as stat4 } from "node:fs/promises";
|
|
2486
|
+
import path7 from "node:path";
|
|
2121
2487
|
var CONTENT_TYPES = {
|
|
2122
2488
|
html: "text/html; charset=utf-8",
|
|
2123
2489
|
css: "text/css; charset=utf-8",
|
|
@@ -2149,14 +2515,14 @@ var CONTENT_TYPES = {
|
|
|
2149
2515
|
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
2150
2516
|
var SKIPPED_NAMES2 = /* @__PURE__ */ new Set(["node_modules"]);
|
|
2151
2517
|
function contentTypeFor(filePath) {
|
|
2152
|
-
const ext =
|
|
2518
|
+
const ext = path7.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
2153
2519
|
return CONTENT_TYPES[ext] ?? DEFAULT_CONTENT_TYPE;
|
|
2154
2520
|
}
|
|
2155
2521
|
function sha256Hex(bytes) {
|
|
2156
2522
|
return createHash("sha256").update(bytes).digest("hex");
|
|
2157
2523
|
}
|
|
2158
2524
|
async function readEntry(abs, rel) {
|
|
2159
|
-
const bytes = await
|
|
2525
|
+
const bytes = await readFile7(abs);
|
|
2160
2526
|
return {
|
|
2161
2527
|
path: rel,
|
|
2162
2528
|
file: abs,
|
|
@@ -2174,7 +2540,7 @@ async function collectPaths(root2) {
|
|
|
2174
2540
|
if (entry.name.startsWith(".")) continue;
|
|
2175
2541
|
if (SKIPPED_NAMES2.has(entry.name)) continue;
|
|
2176
2542
|
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
2177
|
-
const abs =
|
|
2543
|
+
const abs = path7.join(dir, entry.name);
|
|
2178
2544
|
if (entry.isDirectory()) {
|
|
2179
2545
|
await walk2(abs, rel);
|
|
2180
2546
|
continue;
|
|
@@ -2187,8 +2553,8 @@ async function collectPaths(root2) {
|
|
|
2187
2553
|
return found;
|
|
2188
2554
|
}
|
|
2189
2555
|
async function buildManifest(input, opts = {}) {
|
|
2190
|
-
const root2 =
|
|
2191
|
-
const info = await
|
|
2556
|
+
const root2 = path7.resolve(input);
|
|
2557
|
+
const info = await stat4(root2).catch(() => void 0);
|
|
2192
2558
|
if (info === void 0) throw new UsageError(`no such file or folder: ${input}`);
|
|
2193
2559
|
const entries = [];
|
|
2194
2560
|
if (info.isFile()) {
|
|
@@ -2198,7 +2564,7 @@ async function buildManifest(input, opts = {}) {
|
|
|
2198
2564
|
if (opts.requireIndex !== false && !paths.includes("index.html")) {
|
|
2199
2565
|
throw new UsageError(`${input} has no index.html`);
|
|
2200
2566
|
}
|
|
2201
|
-
for (const rel of paths) entries.push(await readEntry(
|
|
2567
|
+
for (const rel of paths) entries.push(await readEntry(path7.join(root2, rel), rel));
|
|
2202
2568
|
} else {
|
|
2203
2569
|
throw new UsageError(`not a file or folder: ${input}`);
|
|
2204
2570
|
}
|
|
@@ -2316,7 +2682,7 @@ async function publishTarget(api, target, opts) {
|
|
|
2316
2682
|
let uploaded = 0;
|
|
2317
2683
|
await runPool(missing, UPLOAD_CONCURRENCY, async (hash) => {
|
|
2318
2684
|
const entry = byHash.get(hash);
|
|
2319
|
-
await api.putBlob(hash, await
|
|
2685
|
+
await api.putBlob(hash, await readFile8(entry.file));
|
|
2320
2686
|
uploaded += 1;
|
|
2321
2687
|
});
|
|
2322
2688
|
const version = await api.createVersion(opts.slug, { label: opts.label, files: manifest.files });
|
|
@@ -2344,7 +2710,7 @@ function sameFiles(local, remote) {
|
|
|
2344
2710
|
const paths = Object.keys(local);
|
|
2345
2711
|
const other = remote;
|
|
2346
2712
|
if (paths.length !== Object.keys(other).length) return false;
|
|
2347
|
-
return paths.every((
|
|
2713
|
+
return paths.every((path26) => other[path26]?.hash === local[path26]?.hash);
|
|
2348
2714
|
}
|
|
2349
2715
|
function reportUnchanged(out, result) {
|
|
2350
2716
|
out.data({ unchanged: true, slug: result.slug, version: result.version.n });
|
|
@@ -2372,26 +2738,19 @@ function validateSlug(slug) {
|
|
|
2372
2738
|
return slug;
|
|
2373
2739
|
}
|
|
2374
2740
|
|
|
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
2741
|
// src/commands/publish.ts
|
|
2383
2742
|
async function directoryOf(input) {
|
|
2384
|
-
const dir =
|
|
2385
|
-
const info = await
|
|
2743
|
+
const dir = path8.resolve(input);
|
|
2744
|
+
const info = await stat5(dir).catch(() => void 0);
|
|
2386
2745
|
return info !== void 0 && info.isDirectory() ? dir : void 0;
|
|
2387
2746
|
}
|
|
2388
2747
|
async function targetOf(input, dir, built, out) {
|
|
2389
|
-
if (dir === void 0) return
|
|
2390
|
-
if (built) return
|
|
2748
|
+
if (dir === void 0) return path8.resolve(input);
|
|
2749
|
+
if (built) return path8.resolve(dir, out);
|
|
2391
2750
|
const local = await readConfigFile(localConfigPath(dir));
|
|
2392
|
-
return local.out === void 0 ? dir :
|
|
2751
|
+
return local.out === void 0 ? dir : path8.resolve(dir, out);
|
|
2393
2752
|
}
|
|
2394
|
-
async function
|
|
2753
|
+
async function run(argv, ctx) {
|
|
2395
2754
|
const args = parseCommandArgs(
|
|
2396
2755
|
argv,
|
|
2397
2756
|
{
|
|
@@ -2418,7 +2777,7 @@ async function run2(argv, ctx) {
|
|
|
2418
2777
|
}
|
|
2419
2778
|
const target = await targetOf(input, dir, build !== void 0, config.out);
|
|
2420
2779
|
const slug = validateSlug(
|
|
2421
|
-
stringValue(args, "slug") ?? config.slug ?? deriveSlug(
|
|
2780
|
+
stringValue(args, "slug") ?? config.slug ?? deriveSlug(path8.basename(path8.resolve(input)))
|
|
2422
2781
|
);
|
|
2423
2782
|
const title = stringValue(args, "title") ?? config.title;
|
|
2424
2783
|
const label = stringValue(args, "label");
|
|
@@ -2429,7 +2788,7 @@ async function run2(argv, ctx) {
|
|
|
2429
2788
|
const opts = { slug, title, label, project, tags, force: boolValue(args, "force") };
|
|
2430
2789
|
if (!boolValue(args, "no-check")) {
|
|
2431
2790
|
opts.beforeUpload = () => {
|
|
2432
|
-
if (config.art === void 0)
|
|
2791
|
+
if (config.art === void 0) warn("no art host configured; skipping theme checks");
|
|
2433
2792
|
return enforceCheck(ctx.out, target, { art: config.art, offline: config.art === void 0 });
|
|
2434
2793
|
};
|
|
2435
2794
|
}
|
|
@@ -2453,9 +2812,9 @@ async function run2(argv, ctx) {
|
|
|
2453
2812
|
}
|
|
2454
2813
|
|
|
2455
2814
|
// src/commands/check.ts
|
|
2456
|
-
import { stat as
|
|
2457
|
-
import
|
|
2458
|
-
async function
|
|
2815
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
2816
|
+
import path9 from "node:path";
|
|
2817
|
+
async function run2(argv, ctx) {
|
|
2459
2818
|
const args = parseCommandArgs(
|
|
2460
2819
|
argv,
|
|
2461
2820
|
{ offline: { type: "boolean" }, "no-style": { type: "boolean" } },
|
|
@@ -2464,13 +2823,13 @@ async function run3(argv, ctx) {
|
|
|
2464
2823
|
CHECK_USAGE
|
|
2465
2824
|
);
|
|
2466
2825
|
const input = args.positionals[0];
|
|
2467
|
-
const dir =
|
|
2468
|
-
const info = await
|
|
2826
|
+
const dir = path9.resolve(input);
|
|
2827
|
+
const info = await stat6(dir).catch(() => void 0);
|
|
2469
2828
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
2470
2829
|
const config = await ctx.config(dir);
|
|
2471
2830
|
const offline = boolValue(args, "offline");
|
|
2472
2831
|
const art = config.art;
|
|
2473
|
-
if (art === void 0 && !offline)
|
|
2832
|
+
if (art === void 0 && !offline) warn("no art host configured; skipping theme checks");
|
|
2474
2833
|
const result = await checkFolder(dir, {
|
|
2475
2834
|
art,
|
|
2476
2835
|
offline: offline || art === void 0,
|
|
@@ -2488,21 +2847,21 @@ async function run3(argv, ctx) {
|
|
|
2488
2847
|
}
|
|
2489
2848
|
|
|
2490
2849
|
// src/commands/preview.ts
|
|
2491
|
-
import { stat as
|
|
2492
|
-
import
|
|
2850
|
+
import { stat as stat9 } from "node:fs/promises";
|
|
2851
|
+
import path12 from "node:path";
|
|
2493
2852
|
|
|
2494
2853
|
// src/serve.ts
|
|
2495
2854
|
import { watch } from "node:fs";
|
|
2496
|
-
import { mkdtemp as mkdtemp2, readFile as
|
|
2855
|
+
import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm3, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
|
|
2497
2856
|
import { createHash as createHash2 } from "node:crypto";
|
|
2498
2857
|
import { createServer } from "node:http";
|
|
2499
2858
|
import { tmpdir } from "node:os";
|
|
2500
|
-
import
|
|
2501
|
-
import
|
|
2859
|
+
import path11 from "node:path";
|
|
2860
|
+
import process11 from "node:process";
|
|
2502
2861
|
|
|
2503
2862
|
// src/themes.ts
|
|
2504
|
-
import { cp, mkdir as
|
|
2505
|
-
import
|
|
2863
|
+
import { cp, mkdir as mkdir3, readFile as readFile9, rm as rm2, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
|
|
2864
|
+
import path10 from "node:path";
|
|
2506
2865
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2507
2866
|
|
|
2508
2867
|
// src/color.ts
|
|
@@ -2837,7 +3196,7 @@ function flattenDtcg(tree) {
|
|
|
2837
3196
|
function sourceDir() {
|
|
2838
3197
|
try {
|
|
2839
3198
|
const url = import.meta.url;
|
|
2840
|
-
return typeof url === "string" && url !== "" ?
|
|
3199
|
+
return typeof url === "string" && url !== "" ? path10.dirname(fileURLToPath2(url)) : void 0;
|
|
2841
3200
|
} catch {
|
|
2842
3201
|
return void 0;
|
|
2843
3202
|
}
|
|
@@ -2845,7 +3204,7 @@ function sourceDir() {
|
|
|
2845
3204
|
var HERE = sourceDir();
|
|
2846
3205
|
function nearSource(...segments) {
|
|
2847
3206
|
if (HERE === void 0) return [];
|
|
2848
|
-
return [
|
|
3207
|
+
return [path10.resolve(HERE, "..", "..", "..", ...segments), path10.resolve(HERE, "..", ...segments)];
|
|
2849
3208
|
}
|
|
2850
3209
|
var BUILTIN_BASES = nearSource("themes");
|
|
2851
3210
|
var BUILTIN_SKILL_BASES = nearSource("skills");
|
|
@@ -2863,7 +3222,7 @@ var FONT_FORMATS = {
|
|
|
2863
3222
|
".ttf": "truetype",
|
|
2864
3223
|
".otf": "opentype"
|
|
2865
3224
|
};
|
|
2866
|
-
function
|
|
3225
|
+
function isRecord3(value) {
|
|
2867
3226
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2868
3227
|
}
|
|
2869
3228
|
function stringField(value) {
|
|
@@ -2873,16 +3232,16 @@ function messageOf6(err) {
|
|
|
2873
3232
|
return err instanceof Error ? err.message : String(err);
|
|
2874
3233
|
}
|
|
2875
3234
|
async function isFile(file) {
|
|
2876
|
-
const info = await
|
|
3235
|
+
const info = await stat7(file).catch(() => void 0);
|
|
2877
3236
|
return info !== void 0 && info.isFile();
|
|
2878
3237
|
}
|
|
2879
3238
|
async function isDir(dir) {
|
|
2880
|
-
const info = await
|
|
3239
|
+
const info = await stat7(dir).catch(() => void 0);
|
|
2881
3240
|
return info !== void 0 && info.isDirectory();
|
|
2882
3241
|
}
|
|
2883
3242
|
function tokenMap(value) {
|
|
2884
3243
|
const out = {};
|
|
2885
|
-
if (!
|
|
3244
|
+
if (!isRecord3(value)) return out;
|
|
2886
3245
|
for (const [name, raw] of Object.entries(value)) {
|
|
2887
3246
|
if (typeof raw === "string" && raw.trim() !== "") out[name] = raw.trim();
|
|
2888
3247
|
}
|
|
@@ -2892,7 +3251,7 @@ function fontList(value) {
|
|
|
2892
3251
|
if (!Array.isArray(value)) return [];
|
|
2893
3252
|
const out = [];
|
|
2894
3253
|
for (const item of value) {
|
|
2895
|
-
if (!
|
|
3254
|
+
if (!isRecord3(item)) continue;
|
|
2896
3255
|
const family = stringField(item.family);
|
|
2897
3256
|
const file = stringField(item.file);
|
|
2898
3257
|
if (family === void 0 || file === void 0) continue;
|
|
@@ -2903,8 +3262,8 @@ function fontList(value) {
|
|
|
2903
3262
|
return out;
|
|
2904
3263
|
}
|
|
2905
3264
|
function themeValues(json) {
|
|
2906
|
-
const doc =
|
|
2907
|
-
const modes =
|
|
3265
|
+
const doc = isRecord3(json) ? json : {};
|
|
3266
|
+
const modes = isRecord3(doc.modeTokens) ? doc.modeTokens : {};
|
|
2908
3267
|
return {
|
|
2909
3268
|
tokens: tokenMap(doc.tokens),
|
|
2910
3269
|
light: tokenMap(modes.light),
|
|
@@ -2912,10 +3271,10 @@ function themeValues(json) {
|
|
|
2912
3271
|
};
|
|
2913
3272
|
}
|
|
2914
3273
|
async function readThemeJson(dir) {
|
|
2915
|
-
const file =
|
|
3274
|
+
const file = path10.join(dir, "theme.json");
|
|
2916
3275
|
let text;
|
|
2917
3276
|
try {
|
|
2918
|
-
text = await
|
|
3277
|
+
text = await readFile9(file, "utf8");
|
|
2919
3278
|
} catch (err) {
|
|
2920
3279
|
if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
|
|
2921
3280
|
throw new UsageError(`cannot read ${file}: ${messageOf6(err)}`);
|
|
@@ -2926,13 +3285,13 @@ async function readThemeJson(dir) {
|
|
|
2926
3285
|
} catch {
|
|
2927
3286
|
throw new UsageError(`invalid JSON in ${file}`);
|
|
2928
3287
|
}
|
|
2929
|
-
if (!
|
|
3288
|
+
if (!isRecord3(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
2930
3289
|
return { dir, text, json: parsed };
|
|
2931
3290
|
}
|
|
2932
3291
|
async function builtinThemeDir(name, bases = BUILTIN_BASES) {
|
|
2933
3292
|
if (!THEME_NAME_PATTERN.test(name)) return void 0;
|
|
2934
3293
|
for (const base of [...await binaryThemeBases(), ...bases]) {
|
|
2935
|
-
const dir =
|
|
3294
|
+
const dir = path10.join(base, name);
|
|
2936
3295
|
if (await isDir(dir)) return dir;
|
|
2937
3296
|
}
|
|
2938
3297
|
return void 0;
|
|
@@ -2968,8 +3327,8 @@ function stripPrint(css) {
|
|
|
2968
3327
|
return out;
|
|
2969
3328
|
}
|
|
2970
3329
|
async function themeTokenSets(dir) {
|
|
2971
|
-
const file =
|
|
2972
|
-
const text = await
|
|
3330
|
+
const file = path10.join(dir, "tokens.css");
|
|
3331
|
+
const text = await readFile9(file, "utf8").catch(() => void 0);
|
|
2973
3332
|
return extractTokens(text === void 0 ? [] : [stripPrint(text)]);
|
|
2974
3333
|
}
|
|
2975
3334
|
function declMap(list3) {
|
|
@@ -3022,7 +3381,7 @@ function presetFontCss(fonts) {
|
|
|
3022
3381
|
lines.push(` font-weight: ${font.weight ?? "400"};`);
|
|
3023
3382
|
lines.push(" font-display: swap;");
|
|
3024
3383
|
const url = fontUrl(font.file);
|
|
3025
|
-
const format = FONT_FORMATS[
|
|
3384
|
+
const format = FONT_FORMATS[path10.extname(url).toLowerCase()];
|
|
3026
3385
|
const source = format === void 0 ? `url("${url}")` : `url("${url}") format("${format}")`;
|
|
3027
3386
|
lines.push(` src: ${source};`);
|
|
3028
3387
|
return `@font-face {
|
|
@@ -3047,15 +3406,15 @@ function mergeTheme(base, preset, baseName) {
|
|
|
3047
3406
|
}
|
|
3048
3407
|
async function appendFile(file, addition) {
|
|
3049
3408
|
if (addition === "") return;
|
|
3050
|
-
const before = await
|
|
3409
|
+
const before = await readFile9(file, "utf8").catch(() => "");
|
|
3051
3410
|
const glue = before === "" || before.endsWith("\n") ? "" : "\n";
|
|
3052
|
-
await
|
|
3411
|
+
await writeFile3(file, `${before}${glue}${addition}`);
|
|
3053
3412
|
}
|
|
3054
3413
|
async function buildPreset(presetDir, outDir) {
|
|
3055
3414
|
const preset = await readThemeJson(presetDir);
|
|
3056
3415
|
const baseName = stringField(preset.json.extends);
|
|
3057
3416
|
await rm2(outDir, { recursive: true, force: true });
|
|
3058
|
-
await
|
|
3417
|
+
await mkdir3(outDir, { recursive: true });
|
|
3059
3418
|
if (baseName === void 0) {
|
|
3060
3419
|
await cp(presetDir, outDir, { recursive: true });
|
|
3061
3420
|
return;
|
|
@@ -3066,15 +3425,15 @@ async function buildPreset(presetDir, outDir) {
|
|
|
3066
3425
|
await cp(baseDir, outDir, { recursive: true });
|
|
3067
3426
|
const values = themeValues(preset.json);
|
|
3068
3427
|
const name = stringField(preset.json.name) ?? baseName;
|
|
3069
|
-
await appendFile(
|
|
3428
|
+
await appendFile(path10.join(outDir, "tokens.css"), presetTokenCss(name, values));
|
|
3070
3429
|
const fonts = fontList(preset.json.fonts);
|
|
3071
3430
|
if (fonts.length > 0) {
|
|
3072
|
-
const from =
|
|
3073
|
-
if (await isDir(from)) await cp(from,
|
|
3074
|
-
await appendFile(
|
|
3431
|
+
const from = path10.join(presetDir, "fonts");
|
|
3432
|
+
if (await isDir(from)) await cp(from, path10.join(outDir, "fonts"), { recursive: true });
|
|
3433
|
+
await appendFile(path10.join(outDir, "fonts.css"), presetFontCss(fonts));
|
|
3075
3434
|
}
|
|
3076
3435
|
const merged = mergeTheme(base.json, preset.json, baseName);
|
|
3077
|
-
await
|
|
3436
|
+
await writeFile3(path10.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
|
|
3078
3437
|
`);
|
|
3079
3438
|
}
|
|
3080
3439
|
function keyLine(text, key2, mode) {
|
|
@@ -3089,8 +3448,8 @@ function keyLine(text, key2, mode) {
|
|
|
3089
3448
|
}
|
|
3090
3449
|
return 0;
|
|
3091
3450
|
}
|
|
3092
|
-
function finding(
|
|
3093
|
-
return { path:
|
|
3451
|
+
function finding(path26, line, severity, message) {
|
|
3452
|
+
return { path: path26, line: Math.max(1, line), severity, message };
|
|
3094
3453
|
}
|
|
3095
3454
|
function resolveValue(name, map, fallback) {
|
|
3096
3455
|
let current = map[name] ?? fallback[name];
|
|
@@ -3188,13 +3547,13 @@ async function checkPreset(presetDir) {
|
|
|
3188
3547
|
}
|
|
3189
3548
|
const fonts = fontList(preset.json.fonts);
|
|
3190
3549
|
for (const font of fonts) {
|
|
3191
|
-
const file =
|
|
3550
|
+
const file = path10.join(presetDir, font.file);
|
|
3192
3551
|
if (!await isFile(file)) {
|
|
3193
3552
|
findings.push(finding("theme.json", keyLine(text, "file"), "error", `missing font file: ${font.file}`));
|
|
3194
3553
|
}
|
|
3195
3554
|
}
|
|
3196
3555
|
if (fonts.length > 0) {
|
|
3197
|
-
const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(
|
|
3556
|
+
const found = await Promise.all(LICENCE_FILES.map((rel) => isFile(path10.join(presetDir, "fonts", rel))));
|
|
3198
3557
|
if (!found.some(Boolean)) {
|
|
3199
3558
|
findings.push(
|
|
3200
3559
|
finding(
|
|
@@ -3292,7 +3651,7 @@ var SCRIPT_TYPE = "text/javascript; charset=utf-8";
|
|
|
3292
3651
|
function messageOf7(err) {
|
|
3293
3652
|
return err instanceof Error ? err.message : String(err);
|
|
3294
3653
|
}
|
|
3295
|
-
function
|
|
3654
|
+
function isRecord4(value) {
|
|
3296
3655
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3297
3656
|
}
|
|
3298
3657
|
function etagOf(text) {
|
|
@@ -3327,25 +3686,26 @@ async function resolveFile(root2, pathname) {
|
|
|
3327
3686
|
return void 0;
|
|
3328
3687
|
}
|
|
3329
3688
|
if (decoded.includes("\0")) return void 0;
|
|
3330
|
-
const target =
|
|
3331
|
-
const rel =
|
|
3332
|
-
if (rel.startsWith("..") ||
|
|
3333
|
-
const info = await
|
|
3689
|
+
const target = path11.resolve(root2, decoded.replace(/^\/+/, ""));
|
|
3690
|
+
const rel = path11.relative(root2, target);
|
|
3691
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) return void 0;
|
|
3692
|
+
const info = await stat8(target).catch(() => void 0);
|
|
3334
3693
|
if (info === void 0) return void 0;
|
|
3335
3694
|
if (info.isFile()) return target;
|
|
3336
3695
|
if (!info.isDirectory()) return void 0;
|
|
3337
|
-
const index =
|
|
3338
|
-
const indexInfo = await
|
|
3696
|
+
const index = path11.join(target, "index.html");
|
|
3697
|
+
const indexInfo = await stat8(index).catch(() => void 0);
|
|
3339
3698
|
if (indexInfo === void 0 || !indexInfo.isFile()) return void 0;
|
|
3340
3699
|
return index;
|
|
3341
3700
|
}
|
|
3342
3701
|
async function proxy(art, pathname, method, res) {
|
|
3343
3702
|
let reply;
|
|
3703
|
+
await readyNetwork();
|
|
3344
3704
|
try {
|
|
3345
3705
|
reply = await fetch(art + pathname, { method: method === "HEAD" ? "HEAD" : "GET" });
|
|
3346
3706
|
} catch (err) {
|
|
3347
3707
|
res.writeHead(502, { "content-type": TEXT_TYPE, "x-rshow-source": "proxy" });
|
|
3348
|
-
res.end(`proxy error: ${
|
|
3708
|
+
res.end(`proxy error: ${networkText(err, art + pathname)}
|
|
3349
3709
|
`);
|
|
3350
3710
|
return;
|
|
3351
3711
|
}
|
|
@@ -3361,10 +3721,10 @@ async function proxy(art, pathname, method, res) {
|
|
|
3361
3721
|
async function prepareTheme(dir) {
|
|
3362
3722
|
const doc = await readThemeJson(dir);
|
|
3363
3723
|
const declared = typeof doc.json.name === "string" ? doc.json.name.trim() : "";
|
|
3364
|
-
const name = declared === "" ?
|
|
3724
|
+
const name = declared === "" ? path11.basename(dir) : declared;
|
|
3365
3725
|
const base = typeof doc.json.extends === "string" ? doc.json.extends.trim() : "";
|
|
3366
3726
|
if (base === "") return { name, source: dir, built: dir, label: "local" };
|
|
3367
|
-
const built = await mkdtemp2(
|
|
3727
|
+
const built = await mkdtemp2(path11.join(tmpdir(), "rshow-theme-"));
|
|
3368
3728
|
await buildPreset(dir, built);
|
|
3369
3729
|
return { name, source: dir, built, label: "preset" };
|
|
3370
3730
|
}
|
|
@@ -3435,9 +3795,9 @@ function createDataStore() {
|
|
|
3435
3795
|
const values = /* @__PURE__ */ new Map();
|
|
3436
3796
|
let dir;
|
|
3437
3797
|
const persist = async () => {
|
|
3438
|
-
if (dir === void 0) dir = await mkdtemp2(
|
|
3798
|
+
if (dir === void 0) dir = await mkdtemp2(path11.join(tmpdir(), "rshow-data-"));
|
|
3439
3799
|
const doc = Object.fromEntries(values);
|
|
3440
|
-
await
|
|
3800
|
+
await writeFile4(path11.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
|
|
3441
3801
|
`);
|
|
3442
3802
|
};
|
|
3443
3803
|
return {
|
|
@@ -3483,9 +3843,9 @@ async function readBody(req, limit) {
|
|
|
3483
3843
|
function valueText(value) {
|
|
3484
3844
|
return JSON.stringify(value === void 0 ? null : value);
|
|
3485
3845
|
}
|
|
3486
|
-
async function handleData(req, res,
|
|
3846
|
+
async function handleData(req, res, store2, key2, method) {
|
|
3487
3847
|
if (method === "GET" || method === "HEAD") {
|
|
3488
|
-
const entry =
|
|
3848
|
+
const entry = store2.get(key2);
|
|
3489
3849
|
if (entry === void 0) {
|
|
3490
3850
|
jsonError(res, "not found", 404);
|
|
3491
3851
|
return;
|
|
@@ -3526,12 +3886,12 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3526
3886
|
jsonError(res, "expected a JSON body", 400);
|
|
3527
3887
|
return;
|
|
3528
3888
|
}
|
|
3529
|
-
if (!
|
|
3889
|
+
if (!isRecord4(parsed) || !("value" in parsed)) {
|
|
3530
3890
|
jsonError(res, "expected {value}", 400);
|
|
3531
3891
|
return;
|
|
3532
3892
|
}
|
|
3533
3893
|
const text = valueText(parsed.value);
|
|
3534
|
-
const existing =
|
|
3894
|
+
const existing = store2.get(key2);
|
|
3535
3895
|
const ifMatch = req.headers["if-match"] ?? null;
|
|
3536
3896
|
if (ifMatch !== null && ifMatch.trim() !== "") {
|
|
3537
3897
|
if (existing === void 0 || !etagMatches(ifMatch, etagOf(existing.text))) {
|
|
@@ -3539,7 +3899,7 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3539
3899
|
return;
|
|
3540
3900
|
}
|
|
3541
3901
|
}
|
|
3542
|
-
if (existing === void 0 &&
|
|
3902
|
+
if (existing === void 0 && store2.count() >= MAX_DATA_KEYS) {
|
|
3543
3903
|
jsonError(res, "too many keys for this artifact", 409);
|
|
3544
3904
|
return;
|
|
3545
3905
|
}
|
|
@@ -3547,7 +3907,7 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3547
3907
|
jsonError(res, "value too large", 413);
|
|
3548
3908
|
return;
|
|
3549
3909
|
}
|
|
3550
|
-
const written = await
|
|
3910
|
+
const written = await store2.write(key2, text);
|
|
3551
3911
|
jsonReply(res, 200, { ok: true, key: key2, updated_at: written.updated_at }, { etag: written.etag });
|
|
3552
3912
|
}
|
|
3553
3913
|
function subscribe(res, session) {
|
|
@@ -3578,7 +3938,7 @@ function injectReload(html) {
|
|
|
3578
3938
|
${html.slice(at)}`;
|
|
3579
3939
|
}
|
|
3580
3940
|
async function sendFile(res, method, session, file, headers = {}) {
|
|
3581
|
-
const bytes = await
|
|
3941
|
+
const bytes = await readFile10(file);
|
|
3582
3942
|
const type = contentTypeFor(file);
|
|
3583
3943
|
const all = { "content-type": type, "cache-control": "no-store", ...headers };
|
|
3584
3944
|
if (method === "HEAD") {
|
|
@@ -3603,7 +3963,7 @@ function startWatchers(session, targets) {
|
|
|
3603
3963
|
const done = rebuild ? buildPreset(theme.source, theme.built) : Promise.resolve();
|
|
3604
3964
|
done.then(
|
|
3605
3965
|
() => broadcast(session),
|
|
3606
|
-
(err) =>
|
|
3966
|
+
(err) => warn(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
|
|
3607
3967
|
);
|
|
3608
3968
|
}, DEBOUNCE_MS);
|
|
3609
3969
|
timer.unref();
|
|
@@ -3613,7 +3973,7 @@ function startWatchers(session, targets) {
|
|
|
3613
3973
|
watcher.on("error", () => void 0);
|
|
3614
3974
|
watchers.push(watcher);
|
|
3615
3975
|
} catch (err) {
|
|
3616
|
-
|
|
3976
|
+
warn(`cannot watch ${target.dir}: ${messageOf7(err)}`);
|
|
3617
3977
|
}
|
|
3618
3978
|
}
|
|
3619
3979
|
return watchers;
|
|
@@ -3656,16 +4016,16 @@ async function handle(req, res, session) {
|
|
|
3656
4016
|
}
|
|
3657
4017
|
function closeOnSignals(preview2) {
|
|
3658
4018
|
const stop = () => {
|
|
3659
|
-
preview2.close().catch(() => void 0).finally(() =>
|
|
4019
|
+
preview2.close().catch(() => void 0).finally(() => process11.exit(0));
|
|
3660
4020
|
};
|
|
3661
|
-
|
|
3662
|
-
|
|
4021
|
+
process11.once("SIGINT", stop);
|
|
4022
|
+
process11.once("SIGTERM", stop);
|
|
3663
4023
|
}
|
|
3664
4024
|
async function startPreview(opts) {
|
|
3665
|
-
const dir =
|
|
4025
|
+
const dir = path11.resolve(opts.dir);
|
|
3666
4026
|
const art = (opts.art ?? "").replace(/\/+$/, "");
|
|
3667
4027
|
const host = opts.host ?? "127.0.0.1";
|
|
3668
|
-
const theme = opts.theme === void 0 ? void 0 : await prepareTheme(
|
|
4028
|
+
const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path11.resolve(opts.theme));
|
|
3669
4029
|
const root2 = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
|
|
3670
4030
|
const session = {
|
|
3671
4031
|
root: root2,
|
|
@@ -3733,12 +4093,12 @@ function portOf(value) {
|
|
|
3733
4093
|
return port;
|
|
3734
4094
|
}
|
|
3735
4095
|
async function requireFolder(input, what) {
|
|
3736
|
-
const dir =
|
|
3737
|
-
const info = await
|
|
4096
|
+
const dir = path12.resolve(input);
|
|
4097
|
+
const info = await stat9(dir).catch(() => void 0);
|
|
3738
4098
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such ${what}: ${input}`);
|
|
3739
4099
|
return dir;
|
|
3740
4100
|
}
|
|
3741
|
-
async function
|
|
4101
|
+
async function run3(argv, ctx) {
|
|
3742
4102
|
const args = parseCommandArgs(
|
|
3743
4103
|
argv,
|
|
3744
4104
|
{
|
|
@@ -3757,7 +4117,7 @@ async function run4(argv, ctx) {
|
|
|
3757
4117
|
const offline = boolValue(args, "offline");
|
|
3758
4118
|
const port = portOf(stringValue(args, "port"));
|
|
3759
4119
|
const config = await ctx.config(dir);
|
|
3760
|
-
if (config.art === void 0 && !offline)
|
|
4120
|
+
if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
|
|
3761
4121
|
const preview2 = await startPreview({
|
|
3762
4122
|
dir,
|
|
3763
4123
|
art: offline ? "" : config.art ?? "",
|
|
@@ -3781,7 +4141,14 @@ function themeRefOf(theme) {
|
|
|
3781
4141
|
return { name: theme.slice(0, at), version: theme.slice(at + 1) };
|
|
3782
4142
|
}
|
|
3783
4143
|
async function fetchVersion(art, name) {
|
|
3784
|
-
const
|
|
4144
|
+
const url = `${art}/_themes/${name}.json`;
|
|
4145
|
+
await readyNetwork();
|
|
4146
|
+
let reply;
|
|
4147
|
+
try {
|
|
4148
|
+
reply = await fetch(url, { signal: AbortSignal.timeout(THEME_TIMEOUT_MS) });
|
|
4149
|
+
} catch (err) {
|
|
4150
|
+
throw new Error(networkDetail(err, url));
|
|
4151
|
+
}
|
|
3785
4152
|
if (reply.status !== 200) throw new Error(`HTTP ${reply.status}`);
|
|
3786
4153
|
const body = await reply.json();
|
|
3787
4154
|
const version = body?.version;
|
|
@@ -3800,17 +4167,17 @@ async function resolveTheme2(config) {
|
|
|
3800
4167
|
const ref = themeRefOf(theme);
|
|
3801
4168
|
if (ref.version !== void 0 && ref.version !== "latest") return config;
|
|
3802
4169
|
if (config.art === void 0) {
|
|
3803
|
-
|
|
4170
|
+
warn("no art host configured; skipping theme version lookup");
|
|
3804
4171
|
return config;
|
|
3805
4172
|
}
|
|
3806
4173
|
try {
|
|
3807
4174
|
return { ...config, theme: await resolvedTheme(config.art, theme) };
|
|
3808
4175
|
} catch (err) {
|
|
3809
|
-
|
|
4176
|
+
warn(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
|
|
3810
4177
|
return config.theme === void 0 ? config : { ...config, theme: ref.name };
|
|
3811
4178
|
}
|
|
3812
4179
|
}
|
|
3813
|
-
async function
|
|
4180
|
+
async function run4(argv, ctx) {
|
|
3814
4181
|
const args = parseCommandArgs(argv, { offline: { type: "boolean" } }, 0, 1, CONFIG_USAGE);
|
|
3815
4182
|
const dir = args.positionals[0];
|
|
3816
4183
|
const loaded = await ctx.config(dir);
|
|
@@ -3820,14 +4187,14 @@ async function run5(argv, ctx) {
|
|
|
3820
4187
|
}
|
|
3821
4188
|
|
|
3822
4189
|
// src/commands/init.ts
|
|
3823
|
-
import { mkdir as
|
|
3824
|
-
import
|
|
3825
|
-
import
|
|
4190
|
+
import { mkdir as mkdir4, readFile as readFile11, writeFile as writeFile5 } from "node:fs/promises";
|
|
4191
|
+
import path13 from "node:path";
|
|
4192
|
+
import process12 from "node:process";
|
|
3826
4193
|
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
|
|
3827
4194
|
function messageOf9(err) {
|
|
3828
4195
|
return err instanceof Error ? err.message : String(err);
|
|
3829
4196
|
}
|
|
3830
|
-
function
|
|
4197
|
+
function isRecord5(value) {
|
|
3831
4198
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3832
4199
|
}
|
|
3833
4200
|
function serverUrl(value, flag) {
|
|
@@ -3849,16 +4216,16 @@ function serverUrl(value, flag) {
|
|
|
3849
4216
|
async function rawConfig(file) {
|
|
3850
4217
|
let text;
|
|
3851
4218
|
try {
|
|
3852
|
-
text = await
|
|
4219
|
+
text = await readFile11(file, "utf8");
|
|
3853
4220
|
} catch (err) {
|
|
3854
4221
|
if (err.code === "ENOENT") return {};
|
|
3855
4222
|
throw new UsageError(`cannot read ${file}: ${messageOf9(err)}`);
|
|
3856
4223
|
}
|
|
3857
4224
|
const parsed = JSON.parse(text);
|
|
3858
|
-
if (!
|
|
4225
|
+
if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
3859
4226
|
return parsed;
|
|
3860
4227
|
}
|
|
3861
|
-
async function
|
|
4228
|
+
async function run5(argv, ctx) {
|
|
3862
4229
|
const args = parseCommandArgs(
|
|
3863
4230
|
argv,
|
|
3864
4231
|
{
|
|
@@ -3874,7 +4241,7 @@ async function run6(argv, ctx) {
|
|
|
3874
4241
|
const url = serverUrl(stringValue(args, "url"), "url");
|
|
3875
4242
|
const art = serverUrl(stringValue(args, "art"), "art");
|
|
3876
4243
|
const theme = (stringValue(args, "theme") ?? "").trim();
|
|
3877
|
-
const file = globalConfigPath(
|
|
4244
|
+
const file = globalConfigPath(process12.env);
|
|
3878
4245
|
const current = await readConfigFile(file);
|
|
3879
4246
|
const same = (have, want) => have === void 0 || have.replace(/\/+$/, "") === want;
|
|
3880
4247
|
if (!boolValue(args, "force")) {
|
|
@@ -3889,8 +4256,8 @@ async function run6(argv, ctx) {
|
|
|
3889
4256
|
raw.url = url;
|
|
3890
4257
|
raw.art = art;
|
|
3891
4258
|
if (theme !== "") raw.theme = theme;
|
|
3892
|
-
await
|
|
3893
|
-
await
|
|
4259
|
+
await mkdir4(path13.dirname(file), { recursive: true });
|
|
4260
|
+
await writeFile5(file, `${JSON.stringify(raw, null, 2)}
|
|
3894
4261
|
`, "utf8");
|
|
3895
4262
|
ctx.out.data({ path: file, url, art });
|
|
3896
4263
|
ctx.out.line(`wrote ${file}`);
|
|
@@ -3898,15 +4265,15 @@ async function run6(argv, ctx) {
|
|
|
3898
4265
|
}
|
|
3899
4266
|
|
|
3900
4267
|
// src/commands/doctor.ts
|
|
3901
|
-
import { readFile as
|
|
3902
|
-
import
|
|
3903
|
-
import
|
|
4268
|
+
import { readFile as readFile13, stat as stat11 } from "node:fs/promises";
|
|
4269
|
+
import path15 from "node:path";
|
|
4270
|
+
import process13 from "node:process";
|
|
3904
4271
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3905
4272
|
|
|
3906
4273
|
// src/skills.ts
|
|
3907
|
-
import { cp as cp2, lstat, mkdir as
|
|
3908
|
-
import { homedir as
|
|
3909
|
-
import
|
|
4274
|
+
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";
|
|
4275
|
+
import { homedir as homedir4 } from "node:os";
|
|
4276
|
+
import path14 from "node:path";
|
|
3910
4277
|
var SKILL_TARGETS = ["claude", "pi", "codex"];
|
|
3911
4278
|
var SKILL_MARKER = ".rshow-skill";
|
|
3912
4279
|
var SKILL_SOURCE = "rshow";
|
|
@@ -3916,26 +4283,26 @@ var TARGET_SEGMENTS = {
|
|
|
3916
4283
|
codex: [".codex"]
|
|
3917
4284
|
};
|
|
3918
4285
|
async function isDir2(dir) {
|
|
3919
|
-
const info = await
|
|
4286
|
+
const info = await stat10(dir).catch(() => void 0);
|
|
3920
4287
|
return info !== void 0 && info.isDirectory();
|
|
3921
4288
|
}
|
|
3922
4289
|
async function isFile2(file) {
|
|
3923
|
-
const info = await
|
|
4290
|
+
const info = await stat10(file).catch(() => void 0);
|
|
3924
4291
|
return info !== void 0 && info.isFile();
|
|
3925
4292
|
}
|
|
3926
4293
|
function isUnder(file, dir) {
|
|
3927
|
-
const rel =
|
|
3928
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
4294
|
+
const rel = path14.relative(dir, file);
|
|
4295
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
|
|
3929
4296
|
}
|
|
3930
4297
|
function homeDir(env) {
|
|
3931
4298
|
const home = (env.HOME ?? "").trim();
|
|
3932
|
-
return home !== "" ? home :
|
|
4299
|
+
return home !== "" ? home : homedir4();
|
|
3933
4300
|
}
|
|
3934
4301
|
function targetParent(home, target) {
|
|
3935
|
-
return
|
|
4302
|
+
return path14.join(home, ...TARGET_SEGMENTS[target]);
|
|
3936
4303
|
}
|
|
3937
4304
|
function targetSkillsDir(home, target) {
|
|
3938
|
-
return
|
|
4305
|
+
return path14.join(targetParent(home, target), "skills");
|
|
3939
4306
|
}
|
|
3940
4307
|
async function detectedTargets(home) {
|
|
3941
4308
|
const out = [];
|
|
@@ -3959,13 +4326,13 @@ function targetHint() {
|
|
|
3959
4326
|
return SKILL_TARGETS.join(", ");
|
|
3960
4327
|
}
|
|
3961
4328
|
function defaultModeOf(dir) {
|
|
3962
|
-
return dir.split(
|
|
4329
|
+
return dir.split(path14.sep).join("/").includes("/_npx/") ? "copy" : "link";
|
|
3963
4330
|
}
|
|
3964
4331
|
async function skillsSource(env) {
|
|
3965
4332
|
const override = (env.RSHOW_SKILLS_DIR ?? "").trim();
|
|
3966
4333
|
let dir;
|
|
3967
4334
|
if (override !== "") {
|
|
3968
|
-
dir =
|
|
4335
|
+
dir = path14.resolve(override);
|
|
3969
4336
|
if (!await isDir2(dir)) throw new UsageError(`RSHOW_SKILLS_DIR is not a folder: ${dir}`);
|
|
3970
4337
|
} else {
|
|
3971
4338
|
dir = await builtinSkillsDir();
|
|
@@ -3973,22 +4340,22 @@ async function skillsSource(env) {
|
|
|
3973
4340
|
if (dir === void 0) return void 0;
|
|
3974
4341
|
const skills = [];
|
|
3975
4342
|
for (const entry of await readdir3(dir, { withFileTypes: true }).catch(() => [])) {
|
|
3976
|
-
if (entry.isDirectory() && await isFile2(
|
|
4343
|
+
if (entry.isDirectory() && await isFile2(path14.join(dir, entry.name, "SKILL.md"))) skills.push(entry.name);
|
|
3977
4344
|
}
|
|
3978
4345
|
skills.sort();
|
|
3979
4346
|
return { dir, mode: await isBinary() ? "copy" : defaultModeOf(dir), skills };
|
|
3980
4347
|
}
|
|
3981
4348
|
function isOurLink(target, source) {
|
|
3982
|
-
const resolved =
|
|
3983
|
-
if (isUnder(resolved,
|
|
3984
|
-
return /(^|\/)rshow\/skills\//.test(resolved.split(
|
|
4349
|
+
const resolved = path14.resolve(target);
|
|
4350
|
+
if (isUnder(resolved, path14.resolve(source))) return true;
|
|
4351
|
+
return /(^|\/)rshow\/skills\//.test(resolved.split(path14.sep).join("/"));
|
|
3985
4352
|
}
|
|
3986
4353
|
async function linkTargetOf(dst) {
|
|
3987
4354
|
const value = await readlink(dst);
|
|
3988
|
-
return
|
|
4355
|
+
return path14.isAbsolute(value) ? value : path14.resolve(path14.dirname(dst), value);
|
|
3989
4356
|
}
|
|
3990
4357
|
async function readMarker(dir) {
|
|
3991
|
-
const text = await
|
|
4358
|
+
const text = await readFile12(path14.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
|
|
3992
4359
|
if (text === void 0) return void 0;
|
|
3993
4360
|
let parsed;
|
|
3994
4361
|
try {
|
|
@@ -4007,7 +4374,7 @@ async function inspectSkill(dst, source, skill) {
|
|
|
4007
4374
|
if (info.isSymbolicLink()) {
|
|
4008
4375
|
const link = await linkTargetOf(dst);
|
|
4009
4376
|
if (!isOurLink(link, source)) return { kind: "foreign" };
|
|
4010
|
-
return { kind: "link", path: link, current:
|
|
4377
|
+
return { kind: "link", path: link, current: path14.resolve(link) === path14.join(path14.resolve(source), skill) };
|
|
4011
4378
|
}
|
|
4012
4379
|
if (info.isDirectory()) {
|
|
4013
4380
|
const marker = await readMarker(dst);
|
|
@@ -4045,17 +4412,17 @@ async function writeSkill(dst, src, mode, version) {
|
|
|
4045
4412
|
}
|
|
4046
4413
|
await cp2(src, dst, { recursive: true });
|
|
4047
4414
|
const marker = { version, source: SKILL_SOURCE };
|
|
4048
|
-
await
|
|
4415
|
+
await writeFile6(path14.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
|
|
4049
4416
|
`, "utf8");
|
|
4050
4417
|
}
|
|
4051
4418
|
async function installSkills(options) {
|
|
4052
4419
|
const entries = [];
|
|
4053
4420
|
for (const target of options.targets) {
|
|
4054
4421
|
const dir = targetSkillsDir(options.home, target);
|
|
4055
|
-
if (!options.dryRun) await
|
|
4422
|
+
if (!options.dryRun) await mkdir5(dir, { recursive: true });
|
|
4056
4423
|
for (const skill of options.source.skills) {
|
|
4057
|
-
const dst =
|
|
4058
|
-
const src =
|
|
4424
|
+
const dst = path14.join(dir, skill);
|
|
4425
|
+
const src = path14.join(options.source.dir, skill);
|
|
4059
4426
|
const state = await inspectSkill(dst, options.source.dir, skill);
|
|
4060
4427
|
const action = actionFor(state, options.version);
|
|
4061
4428
|
if (!options.dryRun && (action === "installed" || action === "updated")) {
|
|
@@ -4072,7 +4439,7 @@ async function uninstallSkills(options) {
|
|
|
4072
4439
|
for (const target of options.targets) {
|
|
4073
4440
|
const dir = targetSkillsDir(options.home, target);
|
|
4074
4441
|
for (const skill of (await readdir3(dir).catch(() => [])).sort()) {
|
|
4075
|
-
const dst =
|
|
4442
|
+
const dst = path14.join(dir, skill);
|
|
4076
4443
|
const state = await inspectSkill(dst, options.source.dir, skill);
|
|
4077
4444
|
if (state.kind !== "link" && state.kind !== "copy") continue;
|
|
4078
4445
|
if (!options.dryRun) await rm4(dst, { recursive: true, force: true });
|
|
@@ -4086,7 +4453,7 @@ async function skillsStatus(options) {
|
|
|
4086
4453
|
for (const target of options.targets) {
|
|
4087
4454
|
const dir = targetSkillsDir(options.home, target);
|
|
4088
4455
|
for (const skill of options.source.skills) {
|
|
4089
|
-
const dst =
|
|
4456
|
+
const dst = path14.join(dir, skill);
|
|
4090
4457
|
out.push({ target, skill, state: await inspectSkill(dst, options.source.dir, skill), path: dst });
|
|
4091
4458
|
}
|
|
4092
4459
|
}
|
|
@@ -4099,13 +4466,13 @@ function messageOf10(err) {
|
|
|
4099
4466
|
return err instanceof Error ? err.message : String(err);
|
|
4100
4467
|
}
|
|
4101
4468
|
async function isFile3(file) {
|
|
4102
|
-
const info = await
|
|
4469
|
+
const info = await stat11(file).catch(() => void 0);
|
|
4103
4470
|
return info !== void 0 && info.isFile();
|
|
4104
4471
|
}
|
|
4105
4472
|
function sourceDir2() {
|
|
4106
4473
|
try {
|
|
4107
4474
|
const url = import.meta.url;
|
|
4108
|
-
return typeof url === "string" && url !== "" ?
|
|
4475
|
+
return typeof url === "string" && url !== "" ? path15.dirname(fileURLToPath3(url)) : void 0;
|
|
4109
4476
|
} catch {
|
|
4110
4477
|
return void 0;
|
|
4111
4478
|
}
|
|
@@ -4115,9 +4482,9 @@ async function packageJsonPath() {
|
|
|
4115
4482
|
if (start === void 0) return void 0;
|
|
4116
4483
|
let dir = start;
|
|
4117
4484
|
for (let i = 0; i < 6; i += 1) {
|
|
4118
|
-
const file =
|
|
4485
|
+
const file = path15.join(dir, "package.json");
|
|
4119
4486
|
if (await isFile3(file)) return file;
|
|
4120
|
-
const up =
|
|
4487
|
+
const up = path15.dirname(dir);
|
|
4121
4488
|
if (up === dir) break;
|
|
4122
4489
|
dir = up;
|
|
4123
4490
|
}
|
|
@@ -4141,7 +4508,7 @@ function minimumOf(range) {
|
|
|
4141
4508
|
}
|
|
4142
4509
|
async function nodeCheck() {
|
|
4143
4510
|
const name = "node";
|
|
4144
|
-
const current =
|
|
4511
|
+
const current = process13.versions.node;
|
|
4145
4512
|
const file = await packageJsonPath();
|
|
4146
4513
|
if (file === void 0) {
|
|
4147
4514
|
if (await isBinary()) return { name, status: "ok", detail: `v${current} (inside the binary)` };
|
|
@@ -4149,7 +4516,7 @@ async function nodeCheck() {
|
|
|
4149
4516
|
}
|
|
4150
4517
|
let range;
|
|
4151
4518
|
try {
|
|
4152
|
-
const body = JSON.parse(await
|
|
4519
|
+
const body = JSON.parse(await readFile13(file, "utf8"));
|
|
4153
4520
|
range = typeof body.engines?.node === "string" ? body.engines.node : "";
|
|
4154
4521
|
} catch (err) {
|
|
4155
4522
|
return { name, status: "warn", detail: `v${current} (${messageOf10(err)})` };
|
|
@@ -4160,20 +4527,22 @@ async function nodeCheck() {
|
|
|
4160
4527
|
return { name, status: status2, detail: `v${current} (needs ${range})` };
|
|
4161
4528
|
}
|
|
4162
4529
|
async function tokenOf(url) {
|
|
4163
|
-
const env = (
|
|
4530
|
+
const env = (process13.env.RSHOW_TOKEN ?? "").trim();
|
|
4164
4531
|
if (env !== "") return { token: env, source: "env" };
|
|
4165
|
-
if (url === void 0) return {
|
|
4532
|
+
if (url === void 0) return {};
|
|
4166
4533
|
try {
|
|
4167
|
-
const
|
|
4168
|
-
|
|
4169
|
-
return { source:
|
|
4534
|
+
const store2 = await choose(process13.env);
|
|
4535
|
+
const saved = await store2.read(url);
|
|
4536
|
+
if (saved !== void 0) return { token: saved, source: store2.detail };
|
|
4537
|
+
return {};
|
|
4170
4538
|
} catch (err) {
|
|
4171
|
-
return {
|
|
4539
|
+
return { error: messageOf10(err) };
|
|
4172
4540
|
}
|
|
4173
4541
|
}
|
|
4174
4542
|
async function getJson(url, token) {
|
|
4175
4543
|
const headers = {};
|
|
4176
4544
|
if (token !== void 0) headers.authorization = `Bearer ${token}`;
|
|
4545
|
+
await readyNetwork();
|
|
4177
4546
|
const reply = await fetch(url, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
4178
4547
|
if (reply.status !== 200) return { status: reply.status };
|
|
4179
4548
|
const body = await reply.json().catch(() => void 0);
|
|
@@ -4199,7 +4568,7 @@ function tokenCheck(token) {
|
|
|
4199
4568
|
const reason = token.error === void 0 ? "" : ` (${token.error})`;
|
|
4200
4569
|
return { name, status: "fail", detail: `none${reason}: run rshow login, or set RSHOW_TOKEN` };
|
|
4201
4570
|
}
|
|
4202
|
-
return { name, status: "ok", detail: `source: ${token.source}` };
|
|
4571
|
+
return { name, status: "ok", detail: `source: ${token.source ?? "none"}` };
|
|
4203
4572
|
}
|
|
4204
4573
|
async function apiCheck(url, token) {
|
|
4205
4574
|
const name = "api";
|
|
@@ -4213,7 +4582,7 @@ async function apiCheck(url, token) {
|
|
|
4213
4582
|
const scope = typeof body.scope === "string" ? body.scope : "";
|
|
4214
4583
|
return { name, status: "ok", detail: `${email} (scope ${scope})` };
|
|
4215
4584
|
} catch (err) {
|
|
4216
|
-
return { name, status: "fail", detail:
|
|
4585
|
+
return { name, status: "fail", detail: networkDetail(err, `${url}/api/me`) };
|
|
4217
4586
|
}
|
|
4218
4587
|
}
|
|
4219
4588
|
async function artCheck(art) {
|
|
@@ -4228,7 +4597,7 @@ async function artCheck(art) {
|
|
|
4228
4597
|
}
|
|
4229
4598
|
return { name, status: "ok", detail: `version ${version}` };
|
|
4230
4599
|
} catch (err) {
|
|
4231
|
-
return { name, status: "warn", detail:
|
|
4600
|
+
return { name, status: "warn", detail: networkDetail(err, `${art}/_themes/default.json`) };
|
|
4232
4601
|
}
|
|
4233
4602
|
}
|
|
4234
4603
|
async function themeCheck(config) {
|
|
@@ -4247,10 +4616,10 @@ async function themeCheck(config) {
|
|
|
4247
4616
|
}
|
|
4248
4617
|
async function skillsCheck() {
|
|
4249
4618
|
const name = "skills";
|
|
4250
|
-
const home = homeDir(
|
|
4619
|
+
const home = homeDir(process13.env);
|
|
4251
4620
|
const targets = await detectedTargets(home);
|
|
4252
4621
|
if (targets.length === 0) return { name, status: "ok", detail: "no target detected" };
|
|
4253
|
-
const source = await skillsSource(
|
|
4622
|
+
const source = await skillsSource(process13.env).catch(() => void 0);
|
|
4254
4623
|
if (source === void 0) return { name, status: "warn", detail: "no skills folder next to rshow" };
|
|
4255
4624
|
const list3 = await skillsStatus({ home, source, targets });
|
|
4256
4625
|
const problems = [];
|
|
@@ -4261,11 +4630,11 @@ async function skillsCheck() {
|
|
|
4261
4630
|
if (problems.length === 0) return { name, status: "ok", detail: targets.join(", ") };
|
|
4262
4631
|
return { name, status: "warn", detail: `${problems.join("; ")} (run rshow skills install)` };
|
|
4263
4632
|
}
|
|
4264
|
-
async function
|
|
4633
|
+
async function run6(argv, ctx) {
|
|
4265
4634
|
const args = parseCommandArgs(argv, {}, 0, 1, DOCTOR_USAGE);
|
|
4266
4635
|
const dir = args.positionals[0];
|
|
4267
4636
|
const found = [];
|
|
4268
|
-
for (const file of [globalConfigPath(
|
|
4637
|
+
for (const file of [globalConfigPath(process13.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
|
|
4269
4638
|
if (file !== void 0 && await isFile3(file)) found.push(file);
|
|
4270
4639
|
}
|
|
4271
4640
|
let config;
|
|
@@ -4293,13 +4662,13 @@ async function run7(argv, ctx) {
|
|
|
4293
4662
|
}
|
|
4294
4663
|
|
|
4295
4664
|
// src/commands/theme.ts
|
|
4296
|
-
import { mkdir as
|
|
4665
|
+
import { mkdir as mkdir6, mkdtemp as mkdtemp3, rm as rm5, stat as stat13, writeFile as writeFile7 } from "node:fs/promises";
|
|
4297
4666
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4298
|
-
import
|
|
4667
|
+
import path17 from "node:path";
|
|
4299
4668
|
|
|
4300
4669
|
// src/theme.ts
|
|
4301
|
-
import { readFile as
|
|
4302
|
-
import
|
|
4670
|
+
import { readFile as readFile14, stat as stat12 } from "node:fs/promises";
|
|
4671
|
+
import path16 from "node:path";
|
|
4303
4672
|
function themeSlug(name) {
|
|
4304
4673
|
return `theme-${name}`;
|
|
4305
4674
|
}
|
|
@@ -4307,10 +4676,10 @@ function themeUrl(name, n) {
|
|
|
4307
4676
|
return `/_themes/${name}@${n}/`;
|
|
4308
4677
|
}
|
|
4309
4678
|
async function readThemeMeta(dir) {
|
|
4310
|
-
const file =
|
|
4679
|
+
const file = path16.join(dir, "theme.json");
|
|
4311
4680
|
let text;
|
|
4312
4681
|
try {
|
|
4313
|
-
text = await
|
|
4682
|
+
text = await readFile14(file, "utf8");
|
|
4314
4683
|
} catch (err) {
|
|
4315
4684
|
if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
|
|
4316
4685
|
throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4331,8 +4700,8 @@ async function readThemeMeta(dir) {
|
|
|
4331
4700
|
return { name };
|
|
4332
4701
|
}
|
|
4333
4702
|
async function requireThemeDir(input) {
|
|
4334
|
-
const dir =
|
|
4335
|
-
const info = await
|
|
4703
|
+
const dir = path16.resolve(input);
|
|
4704
|
+
const info = await stat12(dir).catch(() => void 0);
|
|
4336
4705
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
4337
4706
|
return dir;
|
|
4338
4707
|
}
|
|
@@ -4340,7 +4709,7 @@ async function requireThemeDir(input) {
|
|
|
4340
4709
|
// src/commands/theme.ts
|
|
4341
4710
|
async function presetCheck(dir) {
|
|
4342
4711
|
const findings = await checkPreset(dir);
|
|
4343
|
-
const info = await
|
|
4712
|
+
const info = await stat13(path17.join(dir, "theme.json")).catch(() => void 0);
|
|
4344
4713
|
const errors = findings.filter((finding2) => finding2.severity === "error").length;
|
|
4345
4714
|
return { findings, errors, warnings: findings.length - errors, files: 1, bytes: info?.size ?? 0 };
|
|
4346
4715
|
}
|
|
@@ -4369,14 +4738,14 @@ async function publish(argv, ctx) {
|
|
|
4369
4738
|
if (!boolValue(args, "no-check")) {
|
|
4370
4739
|
if (preset) await enforcePresetCheck(ctx.out, dir);
|
|
4371
4740
|
else {
|
|
4372
|
-
if (config.art === void 0)
|
|
4741
|
+
if (config.art === void 0) warn("no art host configured; skipping theme checks");
|
|
4373
4742
|
await enforceCheck(ctx.out, dir, { art: config.art, offline: config.art === void 0 });
|
|
4374
4743
|
}
|
|
4375
4744
|
}
|
|
4376
4745
|
let target = dir;
|
|
4377
4746
|
let temp;
|
|
4378
4747
|
if (preset) {
|
|
4379
|
-
temp = await mkdtemp3(
|
|
4748
|
+
temp = await mkdtemp3(path17.join(tmpdir2(), "rshow-theme-"));
|
|
4380
4749
|
await buildPreset(dir, temp);
|
|
4381
4750
|
target = temp;
|
|
4382
4751
|
}
|
|
@@ -4464,14 +4833,14 @@ async function create(argv, ctx) {
|
|
|
4464
4833
|
const from = stringValue(args, "from") ?? "default";
|
|
4465
4834
|
const baseDir = await builtinThemeDir(from);
|
|
4466
4835
|
if (baseDir === void 0) throw new UsageError(`no built-in theme: ${from}`);
|
|
4467
|
-
const parent =
|
|
4468
|
-
const target =
|
|
4469
|
-
const exists2 = await
|
|
4836
|
+
const parent = path17.resolve(stringValue(args, "dir") ?? ".");
|
|
4837
|
+
const target = path17.join(parent, name);
|
|
4838
|
+
const exists2 = await stat13(target).catch(() => void 0);
|
|
4470
4839
|
if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
|
|
4471
4840
|
const values = await baseRoleValues(baseDir, ROLE_TOKENS);
|
|
4472
4841
|
const doc = { name, extends: from, tokens: {}, modeTokens: values };
|
|
4473
|
-
await
|
|
4474
|
-
await
|
|
4842
|
+
await mkdir6(target, { recursive: true });
|
|
4843
|
+
await writeFile7(path17.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
|
|
4475
4844
|
`);
|
|
4476
4845
|
ctx.out.data({ path: target, name, extends: from });
|
|
4477
4846
|
ctx.out.line(target);
|
|
@@ -4506,7 +4875,7 @@ async function preview(argv, ctx) {
|
|
|
4506
4875
|
const port = portOf(stringValue(args, "port"));
|
|
4507
4876
|
const offline = boolValue(args, "offline");
|
|
4508
4877
|
const config = await ctx.config(dir);
|
|
4509
|
-
if (config.art === void 0 && !offline)
|
|
4878
|
+
if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
|
|
4510
4879
|
const server = await startPreview({
|
|
4511
4880
|
dir,
|
|
4512
4881
|
theme: dir,
|
|
@@ -4520,7 +4889,7 @@ async function preview(argv, ctx) {
|
|
|
4520
4889
|
ctx.out.raw(`${server.url}/index.html`);
|
|
4521
4890
|
return EXIT.ok;
|
|
4522
4891
|
}
|
|
4523
|
-
async function
|
|
4892
|
+
async function run7(argv, ctx) {
|
|
4524
4893
|
const [sub, ...rest] = argv;
|
|
4525
4894
|
if (sub === void 0) throw new UsageError(`expected a theme command (try: ${usageLine(THEME_USAGE)})`);
|
|
4526
4895
|
if (sub === "new") return create(rest, ctx);
|
|
@@ -4538,11 +4907,11 @@ async function run8(argv, ctx) {
|
|
|
4538
4907
|
}
|
|
4539
4908
|
|
|
4540
4909
|
// src/commands/skills.ts
|
|
4541
|
-
import
|
|
4542
|
-
import
|
|
4910
|
+
import path18 from "node:path";
|
|
4911
|
+
import process14 from "node:process";
|
|
4543
4912
|
var CHANGE_ACTIONS = /* @__PURE__ */ new Set(["installed", "updated", "removed"]);
|
|
4544
4913
|
async function sourceOf() {
|
|
4545
|
-
const source = await skillsSource(
|
|
4914
|
+
const source = await skillsSource(process14.env);
|
|
4546
4915
|
if (source === void 0) {
|
|
4547
4916
|
throw new UsageError(`no skills folder next to rshow (set RSHOW_SKILLS_DIR to one)`);
|
|
4548
4917
|
}
|
|
@@ -4555,7 +4924,7 @@ async function targetsWithDetection(names, home) {
|
|
|
4555
4924
|
}
|
|
4556
4925
|
function noTargetsError(home) {
|
|
4557
4926
|
return new UsageError(
|
|
4558
|
-
`no target detected (looked for ${
|
|
4927
|
+
`no target detected (looked for ${path18.join(home, ".claude")}, ${path18.join(home, ".pi/agent")} and ${path18.join(home, ".codex")}); name one of ${targetHint()}`
|
|
4559
4928
|
);
|
|
4560
4929
|
}
|
|
4561
4930
|
function actionLine(entry, dryRun) {
|
|
@@ -4581,7 +4950,7 @@ async function install(argv, ctx) {
|
|
|
4581
4950
|
const link = boolValue(args, "link");
|
|
4582
4951
|
const copy = boolValue(args, "copy");
|
|
4583
4952
|
if (link && copy) throw new UsageError(`--link and --copy cannot be used together (try: ${usageLine(SKILLS_INSTALL_USAGE)})`);
|
|
4584
|
-
const home = homeDir(
|
|
4953
|
+
const home = homeDir(process14.env);
|
|
4585
4954
|
const targets = await targetsWithDetection(args.positionals, home);
|
|
4586
4955
|
if (targets.length === 0) throw noTargetsError(home);
|
|
4587
4956
|
const source = await sourceOf();
|
|
@@ -4595,13 +4964,13 @@ async function install(argv, ctx) {
|
|
|
4595
4964
|
else ctx.out.line(actionLine(entry, dryRun));
|
|
4596
4965
|
}
|
|
4597
4966
|
ctx.out.line(summaryLine2("installed", entries, targets, dryRun));
|
|
4598
|
-
for (const dst of skipped)
|
|
4967
|
+
for (const dst of skipped) process14.stderr.write(`skip ${dst}: exists and is not from rshow
|
|
4599
4968
|
`);
|
|
4600
4969
|
return skipped.length > 0 ? EXIT.check : EXIT.ok;
|
|
4601
4970
|
}
|
|
4602
4971
|
async function uninstall(argv, ctx) {
|
|
4603
4972
|
const args = parseCommandArgs(argv, { "dry-run": { type: "boolean" } }, 0, 3, SKILLS_UNINSTALL_USAGE);
|
|
4604
|
-
const home = homeDir(
|
|
4973
|
+
const home = homeDir(process14.env);
|
|
4605
4974
|
const targets = await targetsWithDetection(args.positionals, home);
|
|
4606
4975
|
if (targets.length === 0) return EXIT.ok;
|
|
4607
4976
|
const source = await sourceOf();
|
|
@@ -4619,7 +4988,7 @@ function actionOf(item) {
|
|
|
4619
4988
|
}
|
|
4620
4989
|
async function status(argv, ctx) {
|
|
4621
4990
|
const args = parseCommandArgs(argv, {}, 0, 3, SKILLS_STATUS_USAGE);
|
|
4622
|
-
const home = homeDir(
|
|
4991
|
+
const home = homeDir(process14.env);
|
|
4623
4992
|
const targets = await targetsWithDetection(args.positionals, home);
|
|
4624
4993
|
if (targets.length === 0) return EXIT.ok;
|
|
4625
4994
|
const source = await sourceOf();
|
|
@@ -4634,12 +5003,12 @@ async function status(argv, ctx) {
|
|
|
4634
5003
|
const name = `${item.target.padEnd(targetWidth)} ${item.skill.padEnd(skillWidth)}`;
|
|
4635
5004
|
ctx.out.line(`${name} ${skillStateLabel(item.state)}`);
|
|
4636
5005
|
if (item.state.kind === "copy" && !skillIsCurrent(item.state, VERSION)) {
|
|
4637
|
-
|
|
5006
|
+
warn(`${item.path} is a copy of v${item.state.version}, this rshow is ${VERSION}: run rshow skills install`);
|
|
4638
5007
|
}
|
|
4639
5008
|
}
|
|
4640
5009
|
return EXIT.ok;
|
|
4641
5010
|
}
|
|
4642
|
-
async function
|
|
5011
|
+
async function run8(argv, ctx) {
|
|
4643
5012
|
const [sub, ...rest] = argv;
|
|
4644
5013
|
switch (sub) {
|
|
4645
5014
|
case "install":
|
|
@@ -4654,7 +5023,7 @@ async function run9(argv, ctx) {
|
|
|
4654
5023
|
}
|
|
4655
5024
|
|
|
4656
5025
|
// src/commands/list.ts
|
|
4657
|
-
async function
|
|
5026
|
+
async function run9(argv, ctx) {
|
|
4658
5027
|
const args = parseCommandArgs(
|
|
4659
5028
|
argv,
|
|
4660
5029
|
{ project: { type: "string" }, tag: { type: "string" }, q: { type: "string" } },
|
|
@@ -4687,7 +5056,7 @@ async function run10(argv, ctx) {
|
|
|
4687
5056
|
}
|
|
4688
5057
|
|
|
4689
5058
|
// src/commands/show.ts
|
|
4690
|
-
async function
|
|
5059
|
+
async function run10(argv, ctx) {
|
|
4691
5060
|
const args = parseCommandArgs(argv, {}, 1, 1, SHOW_USAGE);
|
|
4692
5061
|
const slug = validateSlug(args.positionals[0]);
|
|
4693
5062
|
const api = await ctx.api();
|
|
@@ -4720,7 +5089,7 @@ async function run11(argv, ctx) {
|
|
|
4720
5089
|
|
|
4721
5090
|
// src/commands/share.ts
|
|
4722
5091
|
var MODES = /* @__PURE__ */ new Set(["private", "people", "public"]);
|
|
4723
|
-
async function
|
|
5092
|
+
async function run11(argv, ctx) {
|
|
4724
5093
|
const args = parseCommandArgs(
|
|
4725
5094
|
argv,
|
|
4726
5095
|
{
|
|
@@ -4760,7 +5129,7 @@ async function run12(argv, ctx) {
|
|
|
4760
5129
|
}
|
|
4761
5130
|
|
|
4762
5131
|
// src/commands/pin.ts
|
|
4763
|
-
async function
|
|
5132
|
+
async function run12(argv, ctx) {
|
|
4764
5133
|
const args = parseCommandArgs(argv, {}, 2, 2, PIN_USAGE);
|
|
4765
5134
|
const slug = validateSlug(args.positionals[0]);
|
|
4766
5135
|
const target = args.positionals[1];
|
|
@@ -4782,7 +5151,7 @@ async function run13(argv, ctx) {
|
|
|
4782
5151
|
}
|
|
4783
5152
|
|
|
4784
5153
|
// src/commands/delete.ts
|
|
4785
|
-
async function
|
|
5154
|
+
async function run13(argv, ctx) {
|
|
4786
5155
|
const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, DELETE_USAGE);
|
|
4787
5156
|
const slug = validateSlug(args.positionals[0]);
|
|
4788
5157
|
if (!boolValue(args, "yes")) throw new UsageError(`delete needs --yes (try: ${usageLine(DELETE_USAGE)})`);
|
|
@@ -4896,7 +5265,7 @@ async function share(argv, ctx) {
|
|
|
4896
5265
|
for (const member of members) ctx.out.line(`member ${member.email} ${member.role}`);
|
|
4897
5266
|
for (const email of removals) ctx.out.line(`removed ${email}`);
|
|
4898
5267
|
}
|
|
4899
|
-
async function
|
|
5268
|
+
async function remove4(argv, ctx) {
|
|
4900
5269
|
const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, PROJECT_DELETE_USAGE);
|
|
4901
5270
|
const slug = validateSlug(args.positionals[0]);
|
|
4902
5271
|
if (!boolValue(args, "yes")) {
|
|
@@ -4907,7 +5276,7 @@ async function remove2(argv, ctx) {
|
|
|
4907
5276
|
ctx.out.data(null);
|
|
4908
5277
|
ctx.out.line(`deleted: ${slug}`);
|
|
4909
5278
|
}
|
|
4910
|
-
async function
|
|
5279
|
+
async function run14(argv, ctx) {
|
|
4911
5280
|
const [sub, ...rest] = argv;
|
|
4912
5281
|
if (sub === void 0) {
|
|
4913
5282
|
throw new UsageError(`expected a project command (try: ${usageLine(PROJECT_USAGE)})`);
|
|
@@ -4916,12 +5285,12 @@ async function run15(argv, ctx) {
|
|
|
4916
5285
|
if (sub === "list") return list2(rest, ctx);
|
|
4917
5286
|
if (sub === "show") return show(rest, ctx);
|
|
4918
5287
|
if (sub === "share") return share(rest, ctx);
|
|
4919
|
-
if (sub === "delete") return
|
|
5288
|
+
if (sub === "delete") return remove4(rest, ctx);
|
|
4920
5289
|
throw new UsageError(`unknown project command: ${sub} (try: ${usageLine(PROJECT_USAGE)})`);
|
|
4921
5290
|
}
|
|
4922
5291
|
|
|
4923
5292
|
// src/commands/tag.ts
|
|
4924
|
-
async function
|
|
5293
|
+
async function run15(argv, ctx) {
|
|
4925
5294
|
const args = parseCommandArgs(
|
|
4926
5295
|
argv,
|
|
4927
5296
|
{ add: { type: "string", multiple: true }, remove: { type: "string", multiple: true } },
|
|
@@ -4931,13 +5300,13 @@ async function run16(argv, ctx) {
|
|
|
4931
5300
|
);
|
|
4932
5301
|
const slug = validateSlug(args.positionals[0]);
|
|
4933
5302
|
const add2 = normalizeTags(stringList(args, "add"));
|
|
4934
|
-
const
|
|
4935
|
-
if (add2.length === 0 &&
|
|
5303
|
+
const remove5 = normalizeTags(stringList(args, "remove"));
|
|
5304
|
+
if (add2.length === 0 && remove5.length === 0) {
|
|
4936
5305
|
throw new UsageError(`nothing to change (try: ${usageLine(TAG_USAGE)})`);
|
|
4937
5306
|
}
|
|
4938
5307
|
const api = await ctx.api();
|
|
4939
5308
|
const current = await api.artifact(slug);
|
|
4940
|
-
const tags = withTags(tagsOf(current?.tags), add2,
|
|
5309
|
+
const tags = withTags(tagsOf(current?.tags), add2, remove5);
|
|
4941
5310
|
const artifact = await api.patchArtifact(slug, { tags });
|
|
4942
5311
|
ctx.out.data({ artifact, tags });
|
|
4943
5312
|
ctx.out.line(`slug: ${slug}`);
|
|
@@ -4945,20 +5314,20 @@ async function run16(argv, ctx) {
|
|
|
4945
5314
|
}
|
|
4946
5315
|
|
|
4947
5316
|
// src/commands/handoff.ts
|
|
4948
|
-
import
|
|
5317
|
+
import process18 from "node:process";
|
|
4949
5318
|
|
|
4950
5319
|
// src/handoff.ts
|
|
4951
|
-
import { mkdir as
|
|
4952
|
-
import
|
|
4953
|
-
import
|
|
5320
|
+
import { mkdir as mkdir7, stat as stat14, writeFile as writeFile9 } from "node:fs/promises";
|
|
5321
|
+
import path21 from "node:path";
|
|
5322
|
+
import process17 from "node:process";
|
|
4954
5323
|
|
|
4955
5324
|
// src/cdp.ts
|
|
4956
5325
|
import { spawn as spawn3 } from "node:child_process";
|
|
4957
5326
|
import { existsSync, rmSync } from "node:fs";
|
|
4958
5327
|
import { mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
|
|
4959
5328
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
4960
|
-
import
|
|
4961
|
-
import
|
|
5329
|
+
import path19 from "node:path";
|
|
5330
|
+
import process15 from "node:process";
|
|
4962
5331
|
var CHROME_CANDIDATES = [
|
|
4963
5332
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
4964
5333
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
@@ -4978,9 +5347,9 @@ var LOAD_TIMEOUT_MS = 2e4;
|
|
|
4978
5347
|
var liveBrowsers = /* @__PURE__ */ new Map();
|
|
4979
5348
|
var signalHandlersInstalled = false;
|
|
4980
5349
|
function killBrowser(child, signal) {
|
|
4981
|
-
if (
|
|
5350
|
+
if (process15.platform !== "win32" && child.pid !== void 0) {
|
|
4982
5351
|
try {
|
|
4983
|
-
|
|
5352
|
+
process15.kill(-child.pid, signal);
|
|
4984
5353
|
return;
|
|
4985
5354
|
} catch {
|
|
4986
5355
|
}
|
|
@@ -5005,24 +5374,24 @@ function reapBrowsers() {
|
|
|
5005
5374
|
}
|
|
5006
5375
|
function onInterrupt() {
|
|
5007
5376
|
reapBrowsers();
|
|
5008
|
-
|
|
5377
|
+
process15.exit(130);
|
|
5009
5378
|
}
|
|
5010
5379
|
function onTerminate() {
|
|
5011
5380
|
reapBrowsers();
|
|
5012
|
-
|
|
5381
|
+
process15.exit(143);
|
|
5013
5382
|
}
|
|
5014
5383
|
function dropSignalHandlers() {
|
|
5015
5384
|
if (!signalHandlersInstalled) return;
|
|
5016
5385
|
signalHandlersInstalled = false;
|
|
5017
|
-
|
|
5018
|
-
|
|
5386
|
+
process15.off("SIGINT", onInterrupt);
|
|
5387
|
+
process15.off("SIGTERM", onTerminate);
|
|
5019
5388
|
}
|
|
5020
5389
|
function trackBrowser(child, profile) {
|
|
5021
5390
|
liveBrowsers.set(child, profile);
|
|
5022
5391
|
if (!signalHandlersInstalled) {
|
|
5023
5392
|
signalHandlersInstalled = true;
|
|
5024
|
-
|
|
5025
|
-
|
|
5393
|
+
process15.on("SIGINT", onInterrupt);
|
|
5394
|
+
process15.on("SIGTERM", onTerminate);
|
|
5026
5395
|
}
|
|
5027
5396
|
}
|
|
5028
5397
|
function forgetBrowser(child) {
|
|
@@ -5033,20 +5402,20 @@ var exitHookInstalled = false;
|
|
|
5033
5402
|
function installExitHook() {
|
|
5034
5403
|
if (exitHookInstalled) return;
|
|
5035
5404
|
exitHookInstalled = true;
|
|
5036
|
-
|
|
5405
|
+
process15.once("exit", reapBrowsers);
|
|
5037
5406
|
}
|
|
5038
5407
|
function messageOf11(err) {
|
|
5039
5408
|
return err instanceof Error ? err.message : String(err);
|
|
5040
5409
|
}
|
|
5041
5410
|
function whichInPath(name, pathValue) {
|
|
5042
|
-
for (const dir of String(pathValue ?? "").split(
|
|
5411
|
+
for (const dir of String(pathValue ?? "").split(path19.delimiter)) {
|
|
5043
5412
|
if (dir === "") continue;
|
|
5044
|
-
const file =
|
|
5413
|
+
const file = path19.join(dir, name);
|
|
5045
5414
|
if (existsSync(file)) return file;
|
|
5046
5415
|
}
|
|
5047
5416
|
return void 0;
|
|
5048
5417
|
}
|
|
5049
|
-
function findChrome(env =
|
|
5418
|
+
function findChrome(env = process15.env) {
|
|
5050
5419
|
const explicit = (env.CHROME ?? "").trim();
|
|
5051
5420
|
if (explicit !== "") {
|
|
5052
5421
|
if (existsSync(explicit)) return explicit;
|
|
@@ -5217,7 +5586,7 @@ var Chrome = class _Chrome {
|
|
|
5217
5586
|
static async launch(opts = {}) {
|
|
5218
5587
|
const binary = opts.binary ?? findChrome();
|
|
5219
5588
|
if (binary === void 0) throw new UsageError("no Chrome or Chromium found, set CHROME");
|
|
5220
|
-
const profile = await mkdtemp4(
|
|
5589
|
+
const profile = await mkdtemp4(path19.join(tmpdir3(), "rshow-chrome-"));
|
|
5221
5590
|
const child = spawn3(
|
|
5222
5591
|
binary,
|
|
5223
5592
|
[
|
|
@@ -5237,7 +5606,7 @@ var Chrome = class _Chrome {
|
|
|
5237
5606
|
"--window-size=1440,900",
|
|
5238
5607
|
"about:blank"
|
|
5239
5608
|
],
|
|
5240
|
-
{ stdio: ["ignore", "pipe", "pipe"], detached:
|
|
5609
|
+
{ stdio: ["ignore", "pipe", "pipe"], detached: process15.platform !== "win32" }
|
|
5241
5610
|
);
|
|
5242
5611
|
installExitHook();
|
|
5243
5612
|
trackBrowser(child, profile);
|
|
@@ -5369,16 +5738,16 @@ var Page = class _Page {
|
|
|
5369
5738
|
};
|
|
5370
5739
|
|
|
5371
5740
|
// src/map.ts
|
|
5372
|
-
import { readFile as
|
|
5373
|
-
import
|
|
5374
|
-
import
|
|
5741
|
+
import { readFile as readFile15, writeFile as writeFile8 } from "node:fs/promises";
|
|
5742
|
+
import path20 from "node:path";
|
|
5743
|
+
import process16 from "node:process";
|
|
5375
5744
|
var MAP_FILE = "rshow.map.json";
|
|
5376
|
-
function
|
|
5745
|
+
function isRecord6(value) {
|
|
5377
5746
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5378
5747
|
}
|
|
5379
5748
|
function textMap(file, key2, value) {
|
|
5380
5749
|
if (value === void 0 || value === null) return {};
|
|
5381
|
-
if (!
|
|
5750
|
+
if (!isRecord6(value)) throw new UsageError(`${file}: ${key2} must be an object`);
|
|
5382
5751
|
const out = {};
|
|
5383
5752
|
for (const [name, target] of Object.entries(value)) {
|
|
5384
5753
|
if (typeof target !== "string") throw new UsageError(`${file}: ${key2}.${name} must be a string`);
|
|
@@ -5387,12 +5756,12 @@ function textMap(file, key2, value) {
|
|
|
5387
5756
|
return out;
|
|
5388
5757
|
}
|
|
5389
5758
|
function mapPath(dir) {
|
|
5390
|
-
return
|
|
5759
|
+
return path20.resolve(dir ?? process16.cwd(), MAP_FILE);
|
|
5391
5760
|
}
|
|
5392
5761
|
async function readMapFile(file) {
|
|
5393
5762
|
let text;
|
|
5394
5763
|
try {
|
|
5395
|
-
text = await
|
|
5764
|
+
text = await readFile15(file, "utf8");
|
|
5396
5765
|
} catch (err) {
|
|
5397
5766
|
if (err.code === "ENOENT") return void 0;
|
|
5398
5767
|
throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -5403,7 +5772,7 @@ async function readMapFile(file) {
|
|
|
5403
5772
|
} catch {
|
|
5404
5773
|
throw new UsageError(`invalid JSON in ${file}`);
|
|
5405
5774
|
}
|
|
5406
|
-
if (!
|
|
5775
|
+
if (!isRecord6(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
5407
5776
|
return {
|
|
5408
5777
|
components: textMap(file, "components", parsed.components),
|
|
5409
5778
|
tokens: textMap(file, "tokens", parsed.tokens),
|
|
@@ -5411,7 +5780,7 @@ async function readMapFile(file) {
|
|
|
5411
5780
|
};
|
|
5412
5781
|
}
|
|
5413
5782
|
async function writeMapFile(file, map) {
|
|
5414
|
-
await
|
|
5783
|
+
await writeFile8(file, `${JSON.stringify(sortMap(map), null, 2)}
|
|
5415
5784
|
`);
|
|
5416
5785
|
}
|
|
5417
5786
|
function sortRecord(value) {
|
|
@@ -5855,14 +6224,14 @@ function parseTarget(input) {
|
|
|
5855
6224
|
if (remote !== null) {
|
|
5856
6225
|
return { kind: "remote", slug: remote[1], version: Number(remote[2]) };
|
|
5857
6226
|
}
|
|
5858
|
-
const dir =
|
|
5859
|
-
return { kind: "local", dir, slug: deriveSlug(
|
|
6227
|
+
const dir = path21.resolve(input);
|
|
6228
|
+
return { kind: "local", dir, slug: deriveSlug(path21.basename(dir)) || "handoff" };
|
|
5860
6229
|
}
|
|
5861
6230
|
async function requireLocalDir(target) {
|
|
5862
6231
|
if (target.kind !== "local") throw new UsageError("expected a local folder");
|
|
5863
|
-
const info = await
|
|
6232
|
+
const info = await stat14(target.dir).catch(() => void 0);
|
|
5864
6233
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${target.dir}`);
|
|
5865
|
-
const index = await
|
|
6234
|
+
const index = await stat14(path21.join(target.dir, "index.html")).catch(() => void 0);
|
|
5866
6235
|
if (index === void 0 || !index.isFile()) throw new UsageError(`${target.dir} has no index.html`);
|
|
5867
6236
|
return target.dir;
|
|
5868
6237
|
}
|
|
@@ -5877,10 +6246,10 @@ function safeName(text) {
|
|
|
5877
6246
|
return cleaned === "" ? "screen" : cleaned;
|
|
5878
6247
|
}
|
|
5879
6248
|
function displayPath(target) {
|
|
5880
|
-
const resolved =
|
|
5881
|
-
const relative =
|
|
6249
|
+
const resolved = path21.resolve(target);
|
|
6250
|
+
const relative = path21.relative(process17.cwd(), resolved);
|
|
5882
6251
|
if (relative === "") return ".";
|
|
5883
|
-
if (relative.startsWith("..") ||
|
|
6252
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative)) return resolved;
|
|
5884
6253
|
return relative;
|
|
5885
6254
|
}
|
|
5886
6255
|
function baselineName(screen, state, width, mode) {
|
|
@@ -5915,10 +6284,11 @@ async function collectStyleTexts(url, fetchImpl = fetch) {
|
|
|
5915
6284
|
}
|
|
5916
6285
|
async function assertPublic(url, art, fetchImpl = fetch) {
|
|
5917
6286
|
let reply;
|
|
6287
|
+
await readyNetwork();
|
|
5918
6288
|
try {
|
|
5919
6289
|
reply = await fetchImpl(url, { headers: { accept: "text/html" }, redirect: "follow" });
|
|
5920
6290
|
} catch (err) {
|
|
5921
|
-
throw new UsageError(`cannot reach ${url}: ${err
|
|
6291
|
+
throw new UsageError(`cannot reach ${url}: ${networkDetail(err, url)}`);
|
|
5922
6292
|
}
|
|
5923
6293
|
const host = new URL(art).host;
|
|
5924
6294
|
const wandered = new URL(reply.url === "" ? url : reply.url).host !== host;
|
|
@@ -6041,7 +6411,7 @@ async function runHandoff(target, opts) {
|
|
|
6041
6411
|
const version = target.kind === "remote" ? target.version : null;
|
|
6042
6412
|
const slug = target.slug;
|
|
6043
6413
|
const url = target.kind === "remote" ? `${art}/${slug}/v${version}/` : "";
|
|
6044
|
-
const out =
|
|
6414
|
+
const out = path21.resolve(opts.out);
|
|
6045
6415
|
let closePreview;
|
|
6046
6416
|
let pageUrl = url;
|
|
6047
6417
|
if (target.kind === "remote") {
|
|
@@ -6056,8 +6426,8 @@ async function runHandoff(target, opts) {
|
|
|
6056
6426
|
const styleTexts = await collectStyleTexts(pageUrl, fetchImpl);
|
|
6057
6427
|
const tokens = extractTokens(styleTexts);
|
|
6058
6428
|
say(`tokens: ${tokens.light.length} light, ${tokens.dark.length} dark`);
|
|
6059
|
-
await
|
|
6060
|
-
await
|
|
6429
|
+
await mkdir7(path21.join(out, "baseline"), { recursive: true });
|
|
6430
|
+
await writeFile9(path21.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
|
|
6061
6431
|
`);
|
|
6062
6432
|
const screens = [];
|
|
6063
6433
|
const baselines = [];
|
|
@@ -6108,7 +6478,7 @@ async function runHandoff(target, opts) {
|
|
|
6108
6478
|
}
|
|
6109
6479
|
const png = await capture(page, screen);
|
|
6110
6480
|
const name = baselineName(screen.id, state, width, mode);
|
|
6111
|
-
await
|
|
6481
|
+
await writeFile9(path21.join(out, "baseline", name), png);
|
|
6112
6482
|
baselines.push({ screen: screen.id, state, width, mode, file: `baseline/${name}` });
|
|
6113
6483
|
}
|
|
6114
6484
|
}
|
|
@@ -6137,7 +6507,7 @@ async function runHandoff(target, opts) {
|
|
|
6137
6507
|
screens,
|
|
6138
6508
|
baselines
|
|
6139
6509
|
};
|
|
6140
|
-
await
|
|
6510
|
+
await writeFile9(path21.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
|
|
6141
6511
|
`);
|
|
6142
6512
|
const mapFile = MAP_FILE;
|
|
6143
6513
|
let mapWritten = false;
|
|
@@ -6156,7 +6526,7 @@ async function runHandoff(target, opts) {
|
|
|
6156
6526
|
createdAt,
|
|
6157
6527
|
mapFile
|
|
6158
6528
|
});
|
|
6159
|
-
await
|
|
6529
|
+
await writeFile9(path21.join(out, "HANDOFF.md"), markdown);
|
|
6160
6530
|
return {
|
|
6161
6531
|
out,
|
|
6162
6532
|
url: pageUrl,
|
|
@@ -6197,7 +6567,7 @@ function modeList(text) {
|
|
|
6197
6567
|
}
|
|
6198
6568
|
return out;
|
|
6199
6569
|
}
|
|
6200
|
-
async function
|
|
6570
|
+
async function run16(argv, ctx) {
|
|
6201
6571
|
const args = parseCommandArgs(
|
|
6202
6572
|
argv,
|
|
6203
6573
|
{ out: { type: "string" }, widths: { type: "string" }, modes: { type: "string" } },
|
|
@@ -6213,14 +6583,14 @@ async function run17(argv, ctx) {
|
|
|
6213
6583
|
requireUrl(config);
|
|
6214
6584
|
requireArt(config);
|
|
6215
6585
|
} else if (config.art === void 0) {
|
|
6216
|
-
|
|
6586
|
+
warn("no art host configured; skipping the /_themes proxy");
|
|
6217
6587
|
}
|
|
6218
6588
|
const result = await runHandoff(target, {
|
|
6219
6589
|
out,
|
|
6220
6590
|
widths: widthList(stringValue(args, "widths")),
|
|
6221
6591
|
modes: modeList(stringValue(args, "modes")),
|
|
6222
6592
|
art: config.art ?? "",
|
|
6223
|
-
chrome:
|
|
6593
|
+
chrome: process18.env.CHROME,
|
|
6224
6594
|
onProgress: (line) => ctx.out.line(line)
|
|
6225
6595
|
});
|
|
6226
6596
|
const display = displayPath(result.out);
|
|
@@ -6246,11 +6616,11 @@ async function run17(argv, ctx) {
|
|
|
6246
6616
|
}
|
|
6247
6617
|
|
|
6248
6618
|
// src/commands/compare.ts
|
|
6249
|
-
import
|
|
6619
|
+
import process20 from "node:process";
|
|
6250
6620
|
|
|
6251
6621
|
// src/compare.ts
|
|
6252
|
-
import { copyFile, mkdir as
|
|
6253
|
-
import
|
|
6622
|
+
import { copyFile, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile10 } from "node:fs/promises";
|
|
6623
|
+
import path23 from "node:path";
|
|
6254
6624
|
|
|
6255
6625
|
// src/diff.ts
|
|
6256
6626
|
var MAX_Y = 35215;
|
|
@@ -6412,10 +6782,10 @@ function diffImages(before, after, opts = {}) {
|
|
|
6412
6782
|
|
|
6413
6783
|
// src/native.ts
|
|
6414
6784
|
import { spawn as spawn4 } from "node:child_process";
|
|
6415
|
-
import { readFile as
|
|
6785
|
+
import { readFile as readFile16, unlink as unlink2 } from "node:fs/promises";
|
|
6416
6786
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
6417
|
-
import
|
|
6418
|
-
import
|
|
6787
|
+
import path22 from "node:path";
|
|
6788
|
+
import process19 from "node:process";
|
|
6419
6789
|
var FILE_PREFIX = "file:";
|
|
6420
6790
|
var WINDOW_PREFIX = "window:";
|
|
6421
6791
|
var TMUX_PREFIX = "tmux:";
|
|
@@ -6444,9 +6814,9 @@ function isNative(source) {
|
|
|
6444
6814
|
}
|
|
6445
6815
|
async function captureNative(source, mapDir, deps = {}) {
|
|
6446
6816
|
if (source.kind === "file") {
|
|
6447
|
-
const file =
|
|
6817
|
+
const file = path22.resolve(mapDir, source.file);
|
|
6448
6818
|
try {
|
|
6449
|
-
return new Uint8Array(await
|
|
6819
|
+
return new Uint8Array(await readFile16(file));
|
|
6450
6820
|
} catch {
|
|
6451
6821
|
throw new UsageError(`no screenshot at ${source.file}, which is relative to rshow.map.json`);
|
|
6452
6822
|
}
|
|
@@ -6494,8 +6864,8 @@ function noWindow(title, seen) {
|
|
|
6494
6864
|
return new UsageError(`no window matches ${JSON.stringify(title)} (${list3}); use ${WINDOW_PREFIX} with an owner name or a title, or ${NATIVE_HINT}`);
|
|
6495
6865
|
}
|
|
6496
6866
|
var systemRunner = {
|
|
6497
|
-
run: (
|
|
6498
|
-
const child = spawn4(
|
|
6867
|
+
run: (bin3, args) => new Promise((resolve2, reject) => {
|
|
6868
|
+
const child = spawn4(bin3, [...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
6499
6869
|
let stdout = "";
|
|
6500
6870
|
let stderr = "";
|
|
6501
6871
|
child.stdout?.setEncoding("utf8");
|
|
@@ -6506,10 +6876,10 @@ var systemRunner = {
|
|
|
6506
6876
|
child.stderr?.on("data", (chunk2) => {
|
|
6507
6877
|
stderr += chunk2;
|
|
6508
6878
|
});
|
|
6509
|
-
child.on("error", (err) => reject(new UsageError(`cannot run ${
|
|
6879
|
+
child.on("error", (err) => reject(new UsageError(`cannot run ${bin3}: ${err.message}`)));
|
|
6510
6880
|
child.on("close", (code) => {
|
|
6511
6881
|
if (code !== 0) {
|
|
6512
|
-
reject(new UsageError(stderr.trim() === "" ? `${
|
|
6882
|
+
reject(new UsageError(stderr.trim() === "" ? `${bin3} exited ${code}` : `${bin3}: ${stderr.trim()}`));
|
|
6513
6883
|
return;
|
|
6514
6884
|
}
|
|
6515
6885
|
resolve2(stdout);
|
|
@@ -6517,9 +6887,9 @@ var systemRunner = {
|
|
|
6517
6887
|
})
|
|
6518
6888
|
};
|
|
6519
6889
|
async function captureWindow(title, deps = {}) {
|
|
6520
|
-
const platform = deps.platform ??
|
|
6890
|
+
const platform = deps.platform ?? process19.platform;
|
|
6521
6891
|
const runner = deps.runner ?? systemRunner;
|
|
6522
|
-
const scratch =
|
|
6892
|
+
const scratch = path22.join(tmpdir4(), `rshow-window-${process19.pid}-${Date.now()}.png`);
|
|
6523
6893
|
const wrap = (err) => err instanceof UsageError ? err : new UsageError(`window: ${title}: ${err instanceof Error ? err.message : String(err)}; ${NATIVE_HINT}`);
|
|
6524
6894
|
try {
|
|
6525
6895
|
if (platform === "darwin") {
|
|
@@ -6535,11 +6905,11 @@ async function captureWindow(title, deps = {}) {
|
|
|
6535
6905
|
} else {
|
|
6536
6906
|
throw new UsageError(`window: capture is not supported on ${platform}; ${NATIVE_HINT}`);
|
|
6537
6907
|
}
|
|
6538
|
-
return new Uint8Array(await
|
|
6908
|
+
return new Uint8Array(await readFile16(scratch));
|
|
6539
6909
|
} catch (err) {
|
|
6540
6910
|
throw wrap(err);
|
|
6541
6911
|
} finally {
|
|
6542
|
-
await
|
|
6912
|
+
await unlink2(scratch).catch(() => {
|
|
6543
6913
|
});
|
|
6544
6914
|
}
|
|
6545
6915
|
}
|
|
@@ -6576,23 +6946,23 @@ function extractPalette(image, k = 8) {
|
|
|
6576
6946
|
const g = image.data[pos + 1];
|
|
6577
6947
|
const b = image.data[pos + 2];
|
|
6578
6948
|
const key2 = r >> BIN_SHIFT << BITS * 2 | g >> BIN_SHIFT << BITS | b >> BIN_SHIFT;
|
|
6579
|
-
const
|
|
6580
|
-
if (
|
|
6949
|
+
const bin3 = bins.get(key2);
|
|
6950
|
+
if (bin3 === void 0) bins.set(key2, { count: 1, r, g, b });
|
|
6581
6951
|
else {
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6952
|
+
bin3.count += 1;
|
|
6953
|
+
bin3.r += r;
|
|
6954
|
+
bin3.g += g;
|
|
6955
|
+
bin3.b += b;
|
|
6586
6956
|
}
|
|
6587
6957
|
used += 1;
|
|
6588
6958
|
}
|
|
6589
6959
|
}
|
|
6590
6960
|
if (used === 0) return [];
|
|
6591
6961
|
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:
|
|
6962
|
+
let centers = seeds.map((bin3) => ({ r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count }));
|
|
6963
|
+
const points = [...bins.values()].map((bin3) => ({
|
|
6964
|
+
color: { r: bin3.r / bin3.count, g: bin3.g / bin3.count, b: bin3.b / bin3.count },
|
|
6965
|
+
weight: bin3.count
|
|
6596
6966
|
}));
|
|
6597
6967
|
for (let round2 = 0; round2 < ROUNDS && centers.length > 1; round2 += 1) {
|
|
6598
6968
|
const sums = centers.map(() => ({ r: 0, g: 0, b: 0, weight: 0 }));
|
|
@@ -7307,9 +7677,9 @@ function compareGrid(expected, actual) {
|
|
|
7307
7677
|
}
|
|
7308
7678
|
return { rows, differing, pass: differing.length === 0 };
|
|
7309
7679
|
}
|
|
7310
|
-
function captureTmux(target,
|
|
7680
|
+
function captureTmux(target, bin3 = "tmux") {
|
|
7311
7681
|
return new Promise((resolve2, reject) => {
|
|
7312
|
-
const child = spawn5(
|
|
7682
|
+
const child = spawn5(bin3, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
|
|
7313
7683
|
let stdout = "";
|
|
7314
7684
|
let stderr = "";
|
|
7315
7685
|
child.stdout?.setEncoding("utf8");
|
|
@@ -7321,11 +7691,11 @@ function captureTmux(target, bin2 = "tmux") {
|
|
|
7321
7691
|
stderr += chunk2;
|
|
7322
7692
|
});
|
|
7323
7693
|
child.on("error", (err) => {
|
|
7324
|
-
reject(new ApiError(`cannot run ${
|
|
7694
|
+
reject(new ApiError(`cannot run ${bin3}: ${err.message}`));
|
|
7325
7695
|
});
|
|
7326
7696
|
child.on("close", (code) => {
|
|
7327
7697
|
if (code !== 0) {
|
|
7328
|
-
reject(new ApiError(stderr.trim() === "" ? `${
|
|
7698
|
+
reject(new ApiError(stderr.trim() === "" ? `${bin3} exited ${code}` : stderr.trim()));
|
|
7329
7699
|
return;
|
|
7330
7700
|
}
|
|
7331
7701
|
resolve2(stdout);
|
|
@@ -7349,9 +7719,9 @@ var EMPTY_MAP = { components: {}, tokens: {}, screens: {} };
|
|
|
7349
7719
|
async function readJson(file, what) {
|
|
7350
7720
|
let text;
|
|
7351
7721
|
try {
|
|
7352
|
-
text = await
|
|
7722
|
+
text = await readFile17(file, "utf8");
|
|
7353
7723
|
} catch {
|
|
7354
|
-
throw new UsageError(`${what} is missing ${
|
|
7724
|
+
throw new UsageError(`${what} is missing ${path23.basename(file)} (run rshow handoff first)`);
|
|
7355
7725
|
}
|
|
7356
7726
|
try {
|
|
7357
7727
|
return JSON.parse(text);
|
|
@@ -7360,13 +7730,13 @@ async function readJson(file, what) {
|
|
|
7360
7730
|
}
|
|
7361
7731
|
}
|
|
7362
7732
|
async function readHandoff(dir) {
|
|
7363
|
-
const spec = await readJson(
|
|
7733
|
+
const spec = await readJson(path23.join(dir, "spec.json"), dir);
|
|
7364
7734
|
if (spec === null || typeof spec !== "object" || !Array.isArray(spec.screens)) {
|
|
7365
7735
|
throw new UsageError(`${dir}/spec.json does not hold a handoff spec`);
|
|
7366
7736
|
}
|
|
7367
7737
|
let tokens = { light: [], dark: [] };
|
|
7368
7738
|
try {
|
|
7369
|
-
tokens = flattenDtcg(JSON.parse(await
|
|
7739
|
+
tokens = flattenDtcg(JSON.parse(await readFile17(path23.join(dir, "tokens.json"), "utf8")));
|
|
7370
7740
|
} catch {
|
|
7371
7741
|
tokens = { light: [], dark: [] };
|
|
7372
7742
|
}
|
|
@@ -7389,15 +7759,15 @@ function clipOf(info, screenId) {
|
|
|
7389
7759
|
async function runCompare(opts) {
|
|
7390
7760
|
const say = opts.onProgress ?? (() => {
|
|
7391
7761
|
});
|
|
7392
|
-
const dir =
|
|
7762
|
+
const dir = path23.resolve(opts.against);
|
|
7393
7763
|
const { spec, tokens } = await readHandoff(dir);
|
|
7394
|
-
const mapFile = opts.map === void 0 ? mapPath() :
|
|
7764
|
+
const mapFile = opts.map === void 0 ? mapPath() : path23.resolve(opts.map);
|
|
7395
7765
|
const map = await readMapFile(mapFile) ?? EMPTY_MAP;
|
|
7396
|
-
const mapDir =
|
|
7397
|
-
const reportDir =
|
|
7398
|
-
const imagesDir =
|
|
7399
|
-
await
|
|
7400
|
-
await
|
|
7766
|
+
const mapDir = path23.dirname(mapFile);
|
|
7767
|
+
const reportDir = path23.join(dir, "compare-report");
|
|
7768
|
+
const imagesDir = path23.join(reportDir, "images");
|
|
7769
|
+
await mkdir8(imagesDir, { recursive: true });
|
|
7770
|
+
await mkdir8(path23.join(dir, "diff"), { recursive: true });
|
|
7401
7771
|
const results = [];
|
|
7402
7772
|
const collected = [];
|
|
7403
7773
|
let skipped = 0;
|
|
@@ -7432,16 +7802,16 @@ async function runCompare(opts) {
|
|
|
7432
7802
|
throw new UsageError(`tmux capture failed for ${target}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7433
7803
|
});
|
|
7434
7804
|
const text = opts.tuiColors ? captured : stripAnsi(captured);
|
|
7435
|
-
const baselineFile =
|
|
7805
|
+
const baselineFile = path23.join(dir, "baseline", `${safeName(screen)}.txt`);
|
|
7436
7806
|
let expected;
|
|
7437
7807
|
try {
|
|
7438
|
-
expected = await
|
|
7808
|
+
expected = await readFile17(baselineFile, "utf8");
|
|
7439
7809
|
} catch {
|
|
7440
7810
|
expected = void 0;
|
|
7441
7811
|
}
|
|
7442
7812
|
if (expected === void 0) {
|
|
7443
|
-
await
|
|
7444
|
-
await
|
|
7813
|
+
await mkdir8(path23.dirname(baselineFile), { recursive: true });
|
|
7814
|
+
await writeFile10(baselineFile, text);
|
|
7445
7815
|
results.push({
|
|
7446
7816
|
screen,
|
|
7447
7817
|
state: "default",
|
|
@@ -7486,12 +7856,12 @@ async function runCompare(opts) {
|
|
|
7486
7856
|
const raw = (map.screens[screen] ?? "").trim();
|
|
7487
7857
|
say(`capture: ${raw}`);
|
|
7488
7858
|
let capture2;
|
|
7489
|
-
let
|
|
7859
|
+
let failure;
|
|
7490
7860
|
try {
|
|
7491
7861
|
capture2 = decodePng(await captureNative(group.source, mapDir));
|
|
7492
7862
|
} catch (err) {
|
|
7493
7863
|
if (err instanceof UsageError) throw err;
|
|
7494
|
-
|
|
7864
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
7495
7865
|
}
|
|
7496
7866
|
const screenSpec = spec.screens.find((item) => item.id === screen);
|
|
7497
7867
|
const rubricFile = nativeName(screen);
|
|
@@ -7510,13 +7880,13 @@ async function runCompare(opts) {
|
|
|
7510
7880
|
rubric: rubricFile,
|
|
7511
7881
|
mismatches: []
|
|
7512
7882
|
};
|
|
7513
|
-
if (
|
|
7514
|
-
results.push({ ...base, note: `capture failed: ${
|
|
7883
|
+
if (failure !== void 0 || capture2 === void 0) {
|
|
7884
|
+
results.push({ ...base, note: `capture failed: ${failure ?? "no pixels"}` });
|
|
7515
7885
|
continue;
|
|
7516
7886
|
}
|
|
7517
7887
|
let expectedImage;
|
|
7518
7888
|
try {
|
|
7519
|
-
expectedImage = decodePng(new Uint8Array(await
|
|
7889
|
+
expectedImage = decodePng(new Uint8Array(await readFile17(path23.join(dir, baseline.file))));
|
|
7520
7890
|
} catch {
|
|
7521
7891
|
results.push({ ...base, note: `missing baseline ${baseline.file}` });
|
|
7522
7892
|
continue;
|
|
@@ -7528,10 +7898,10 @@ async function runCompare(opts) {
|
|
|
7528
7898
|
threshold: opts.threshold
|
|
7529
7899
|
});
|
|
7530
7900
|
const diffPng = encodePng(diff.image);
|
|
7531
|
-
await
|
|
7532
|
-
await
|
|
7533
|
-
await
|
|
7534
|
-
await copyFile(
|
|
7901
|
+
await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
|
|
7902
|
+
await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
|
|
7903
|
+
await writeFile10(path23.join(imagesDir, `${stem}-capture.png`), encodePng(scaled));
|
|
7904
|
+
await copyFile(path23.join(dir, baseline.file), path23.join(imagesDir, `${stem}-baseline.png`));
|
|
7535
7905
|
const expected = screenSpec === void 0 ? [] : normativeColors(screenSpec, tokens, baseline.state, baseline.mode);
|
|
7536
7906
|
const palette = checkPalette(extractPalette(scaled), expected);
|
|
7537
7907
|
const pixelOk = diff.ratio <= opts.nativeMaxDiffRatio;
|
|
@@ -7576,7 +7946,7 @@ async function runCompare(opts) {
|
|
|
7576
7946
|
});
|
|
7577
7947
|
}
|
|
7578
7948
|
if (screenSpec !== void 0) {
|
|
7579
|
-
await
|
|
7949
|
+
await writeFile10(path23.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
|
|
7580
7950
|
`);
|
|
7581
7951
|
}
|
|
7582
7952
|
}
|
|
@@ -7633,12 +8003,12 @@ async function runCompare(opts) {
|
|
|
7633
8003
|
}
|
|
7634
8004
|
const clip = clipOf(info, screenId) ? await screenClip(page, screenId) : null;
|
|
7635
8005
|
const actual = await page.screenshot(clip ?? void 0);
|
|
7636
|
-
const actualFile =
|
|
7637
|
-
await
|
|
7638
|
-
const baselineFile =
|
|
8006
|
+
const actualFile = path23.join(imagesDir, `${stem}-actual.png`);
|
|
8007
|
+
await writeFile10(actualFile, actual);
|
|
8008
|
+
const baselineFile = path23.join(dir, baseline.file);
|
|
7639
8009
|
let expected;
|
|
7640
8010
|
try {
|
|
7641
|
-
expected = new Uint8Array(await
|
|
8011
|
+
expected = new Uint8Array(await readFile17(baselineFile));
|
|
7642
8012
|
} catch {
|
|
7643
8013
|
results.push({
|
|
7644
8014
|
screen: baseline.screen,
|
|
@@ -7653,14 +8023,14 @@ async function runCompare(opts) {
|
|
|
7653
8023
|
});
|
|
7654
8024
|
continue;
|
|
7655
8025
|
}
|
|
7656
|
-
const baselineCopy =
|
|
8026
|
+
const baselineCopy = path23.join(imagesDir, `${stem}-baseline.png`);
|
|
7657
8027
|
await copyFile(baselineFile, baselineCopy);
|
|
7658
8028
|
const left = decodePng(expected);
|
|
7659
8029
|
const right = decodePng(actual);
|
|
7660
8030
|
const diff = diffImages(left, right, { threshold: opts.threshold });
|
|
7661
8031
|
const diffPng = encodePng(diff.image);
|
|
7662
|
-
await
|
|
7663
|
-
await
|
|
8032
|
+
await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
|
|
8033
|
+
await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
|
|
7664
8034
|
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
8035
|
results.push({
|
|
7666
8036
|
screen: baseline.screen,
|
|
@@ -7729,8 +8099,8 @@ async function runCompare(opts) {
|
|
|
7729
8099
|
const checked = results.filter((item) => item.source !== "").length;
|
|
7730
8100
|
const review = results.filter((item) => item.status === "review").length;
|
|
7731
8101
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7732
|
-
await
|
|
7733
|
-
|
|
8102
|
+
await writeFile10(
|
|
8103
|
+
path23.join(reportDir, "report.json"),
|
|
7734
8104
|
`${JSON.stringify(
|
|
7735
8105
|
{
|
|
7736
8106
|
schema: 1,
|
|
@@ -7766,8 +8136,8 @@ async function runCompare(opts) {
|
|
|
7766
8136
|
)}
|
|
7767
8137
|
`
|
|
7768
8138
|
);
|
|
7769
|
-
await
|
|
7770
|
-
|
|
8139
|
+
await writeFile10(
|
|
8140
|
+
path23.join(reportDir, "index.html"),
|
|
7771
8141
|
reportHtml({
|
|
7772
8142
|
spec,
|
|
7773
8143
|
results,
|
|
@@ -7888,7 +8258,7 @@ function ratioValue(text, name, fallback) {
|
|
|
7888
8258
|
}
|
|
7889
8259
|
return value;
|
|
7890
8260
|
}
|
|
7891
|
-
async function
|
|
8261
|
+
async function run17(argv, ctx) {
|
|
7892
8262
|
const args = parseCommandArgs(
|
|
7893
8263
|
argv,
|
|
7894
8264
|
{
|
|
@@ -7918,7 +8288,7 @@ async function run18(argv, ctx) {
|
|
|
7918
8288
|
DEFAULT_NATIVE_MAX_DIFF_RATIO
|
|
7919
8289
|
),
|
|
7920
8290
|
tuiColors: boolValue(args, "tui-colors"),
|
|
7921
|
-
chrome:
|
|
8291
|
+
chrome: process20.env.CHROME,
|
|
7922
8292
|
onProgress: (line) => ctx.out.line(line)
|
|
7923
8293
|
});
|
|
7924
8294
|
const failing = result.results.filter((item) => !item.pass);
|
|
@@ -7966,8 +8336,8 @@ async function run18(argv, ctx) {
|
|
|
7966
8336
|
}
|
|
7967
8337
|
|
|
7968
8338
|
// src/commands/docs.ts
|
|
7969
|
-
import { mkdir as
|
|
7970
|
-
import
|
|
8339
|
+
import { mkdir as mkdir9, readFile as readFile18, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
|
|
8340
|
+
import path24 from "node:path";
|
|
7971
8341
|
var USAGE = {
|
|
7972
8342
|
publish: PUBLISH_USAGE,
|
|
7973
8343
|
check: CHECK_USAGE,
|
|
@@ -8246,7 +8616,7 @@ function heading(label) {
|
|
|
8246
8616
|
async function themeTokensPage() {
|
|
8247
8617
|
const dir = await builtinThemeDir("default");
|
|
8248
8618
|
if (dir === void 0) throw new Error("no built-in theme: default");
|
|
8249
|
-
const css = await
|
|
8619
|
+
const css = await readFile18(path24.join(dir, "tokens.css"), "utf8");
|
|
8250
8620
|
const end = css.indexOf("*/");
|
|
8251
8621
|
const roles = parseRoles(end < 0 ? css : css.slice(0, end));
|
|
8252
8622
|
const sections = parseSections(rootBlock(css));
|
|
@@ -8294,45 +8664,45 @@ function exitCodesPage() {
|
|
|
8294
8664
|
`;
|
|
8295
8665
|
}
|
|
8296
8666
|
async function generate(out) {
|
|
8297
|
-
const dir =
|
|
8667
|
+
const dir = path24.join(out, "commands");
|
|
8298
8668
|
await rm7(dir, { recursive: true, force: true });
|
|
8299
|
-
await
|
|
8669
|
+
await mkdir9(dir, { recursive: true });
|
|
8300
8670
|
const rows = commandRows();
|
|
8301
8671
|
const files = [];
|
|
8302
8672
|
for (const row of rows) {
|
|
8303
|
-
const file =
|
|
8304
|
-
await
|
|
8673
|
+
const file = path24.join(dir, row.file);
|
|
8674
|
+
await writeFile11(file, commandPage(row, rows));
|
|
8305
8675
|
files.push(file);
|
|
8306
8676
|
}
|
|
8307
|
-
const index =
|
|
8308
|
-
await
|
|
8677
|
+
const index = path24.join(dir, "index.md");
|
|
8678
|
+
await writeFile11(index, indexPage(rows));
|
|
8309
8679
|
files.push(index);
|
|
8310
8680
|
for (const [name, text] of [
|
|
8311
8681
|
["theme-tokens.md", await themeTokensPage()],
|
|
8312
8682
|
["exit-codes.md", exitCodesPage()]
|
|
8313
8683
|
]) {
|
|
8314
|
-
const file =
|
|
8315
|
-
await
|
|
8684
|
+
const file = path24.join(out, name);
|
|
8685
|
+
await writeFile11(file, text);
|
|
8316
8686
|
files.push(file);
|
|
8317
8687
|
}
|
|
8318
|
-
return { commands: rows.length, files: files.map((file) =>
|
|
8688
|
+
return { commands: rows.length, files: files.map((file) => path24.relative(out, file)).sort() };
|
|
8319
8689
|
}
|
|
8320
|
-
async function
|
|
8690
|
+
async function run18(argv, ctx) {
|
|
8321
8691
|
const args = parseCommandArgs(argv, { out: { type: "string" } }, 0, 0, DOCS_USAGE);
|
|
8322
8692
|
const out = stringValue(args, "out");
|
|
8323
8693
|
if (out === void 0) throw new UsageError(`docs needs --out <dir> (try: ${usageLine(DOCS_USAGE)})`);
|
|
8324
8694
|
const result = await generate(out);
|
|
8325
8695
|
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 ${
|
|
8696
|
+
ctx.out.line(`wrote ${result.commands} command pages and the index under ${path24.join(out, "commands")}`);
|
|
8697
|
+
ctx.out.line(`wrote ${path24.join(out, "theme-tokens.md")}`);
|
|
8698
|
+
ctx.out.line(`wrote ${path24.join(out, "exit-codes.md")}`);
|
|
8329
8699
|
}
|
|
8330
8700
|
|
|
8331
8701
|
// src/commands/login.ts
|
|
8332
|
-
import
|
|
8702
|
+
import process21 from "node:process";
|
|
8333
8703
|
async function hiddenLine() {
|
|
8334
|
-
|
|
8335
|
-
const stdin =
|
|
8704
|
+
process21.stderr.write("token: ");
|
|
8705
|
+
const stdin = process21.stdin;
|
|
8336
8706
|
stdin.setRawMode(true);
|
|
8337
8707
|
stdin.resume();
|
|
8338
8708
|
stdin.setEncoding("utf8");
|
|
@@ -8342,7 +8712,7 @@ async function hiddenLine() {
|
|
|
8342
8712
|
stdin.off("data", onData);
|
|
8343
8713
|
stdin.setRawMode(false);
|
|
8344
8714
|
stdin.pause();
|
|
8345
|
-
|
|
8715
|
+
process21.stderr.write("\n");
|
|
8346
8716
|
if (err === void 0) resolve2(text);
|
|
8347
8717
|
else reject(err);
|
|
8348
8718
|
};
|
|
@@ -8365,16 +8735,16 @@ async function hiddenLine() {
|
|
|
8365
8735
|
}
|
|
8366
8736
|
async function pipedText() {
|
|
8367
8737
|
const chunks = [];
|
|
8368
|
-
for await (const chunk2 of
|
|
8738
|
+
for await (const chunk2 of process21.stdin) chunks.push(chunk2);
|
|
8369
8739
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
8370
8740
|
}
|
|
8371
8741
|
async function tokenFromStdin() {
|
|
8372
|
-
const token =
|
|
8742
|
+
const token = process21.stdin.isTTY === true ? await hiddenLine() : await pipedText();
|
|
8373
8743
|
if (token === "") throw new UsageError("expected a token on stdin");
|
|
8374
8744
|
if (!token.startsWith(TOKEN_PREFIX)) throw new UsageError(`that is not an rshow token: it must start with ${TOKEN_PREFIX}`);
|
|
8375
8745
|
return token;
|
|
8376
8746
|
}
|
|
8377
|
-
async function
|
|
8747
|
+
async function run19(argv, ctx, platform = process21.platform) {
|
|
8378
8748
|
const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGIN_USAGE);
|
|
8379
8749
|
const settings = await ctx.config();
|
|
8380
8750
|
const given = (stringValue(args, "url") ?? "").trim();
|
|
@@ -8383,48 +8753,60 @@ async function run20(argv, ctx) {
|
|
|
8383
8753
|
const me = (await new Client(url, token).send("GET", "/api/me")).body;
|
|
8384
8754
|
const email = typeof me?.email === "string" ? me.email : "";
|
|
8385
8755
|
const scope = typeof me?.scope === "string" ? me.scope : "";
|
|
8386
|
-
await
|
|
8756
|
+
const store2 = await choose(process21.env, platform);
|
|
8757
|
+
await store2.save(url, token);
|
|
8758
|
+
if (await store2.read(url) !== token) {
|
|
8759
|
+
throw new ApiError(`cannot save the token in ${store2.description}`);
|
|
8760
|
+
}
|
|
8387
8761
|
ctx.out.data({ email, scope, url });
|
|
8388
|
-
ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}`);
|
|
8762
|
+
ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}, saved in ${store2.description}`);
|
|
8389
8763
|
}
|
|
8390
8764
|
|
|
8391
8765
|
// src/commands/logout.ts
|
|
8392
|
-
|
|
8766
|
+
import process22 from "node:process";
|
|
8767
|
+
async function run20(argv, ctx, platform = process22.platform) {
|
|
8393
8768
|
const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
|
|
8394
8769
|
const settings = await ctx.config();
|
|
8395
8770
|
const given = (stringValue(args, "url") ?? "").trim();
|
|
8396
8771
|
const url = (given === "" ? requireUrl(settings) : given).replace(/\/+$/, "");
|
|
8397
|
-
const removed =
|
|
8398
|
-
|
|
8772
|
+
const removed = [];
|
|
8773
|
+
for (const store2 of await every(process22.env, platform)) {
|
|
8774
|
+
if (await store2.remove(url)) removed.push(store2.description);
|
|
8775
|
+
}
|
|
8776
|
+
if (removed.length === 0) {
|
|
8777
|
+
ctx.out.line(`no saved token for ${url}`);
|
|
8778
|
+
return;
|
|
8779
|
+
}
|
|
8780
|
+
ctx.out.line(`logged out of ${url}, removed from ${removed.join(" and ")}`);
|
|
8399
8781
|
}
|
|
8400
8782
|
|
|
8401
8783
|
// src/main.ts
|
|
8402
8784
|
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:
|
|
8785
|
+
publish: { usage: PUBLISH_USAGE, run },
|
|
8786
|
+
check: { usage: CHECK_USAGE, run: run2 },
|
|
8787
|
+
handoff: { usage: HANDOFF_USAGE, run: run16 },
|
|
8788
|
+
compare: { usage: COMPARE_USAGE, run: run17 },
|
|
8789
|
+
preview: { usage: PREVIEW_USAGE, run: run3 },
|
|
8790
|
+
config: { usage: CONFIG_USAGE, run: run4 },
|
|
8791
|
+
init: { usage: INIT_USAGE, run: run5 },
|
|
8792
|
+
doctor: { usage: DOCTOR_USAGE, run: run6 },
|
|
8793
|
+
theme: { usage: THEME_USAGE, run: run7 },
|
|
8794
|
+
skills: { usage: SKILLS_USAGE, run: run8 },
|
|
8795
|
+
project: { usage: PROJECT_USAGE, run: run14 },
|
|
8796
|
+
tag: { usage: TAG_USAGE, run: run15 },
|
|
8797
|
+
list: { usage: LIST_USAGE, run: run9 },
|
|
8798
|
+
show: { usage: SHOW_USAGE, run: run10 },
|
|
8799
|
+
share: { usage: SHARE_USAGE, run: run11 },
|
|
8800
|
+
pin: { usage: PIN_USAGE, run: run12 },
|
|
8801
|
+
delete: { usage: DELETE_USAGE, run: run13 },
|
|
8802
|
+
login: { usage: LOGIN_USAGE, run: run19 },
|
|
8803
|
+
logout: { usage: LOGOUT_USAGE, run: run20 },
|
|
8422
8804
|
// Kept out of the general help: the docs site runs it, a person rarely does.
|
|
8423
|
-
docs: { usage: DOCS_USAGE, run:
|
|
8805
|
+
docs: { usage: DOCS_USAGE, run: run18 }
|
|
8424
8806
|
};
|
|
8425
8807
|
function fail(err) {
|
|
8426
8808
|
const message = err instanceof Error ? err.message : String(err);
|
|
8427
|
-
|
|
8809
|
+
process23.stderr.write(`rshow: ${message}
|
|
8428
8810
|
`);
|
|
8429
8811
|
if (err instanceof UsageError) return EXIT.usage;
|
|
8430
8812
|
if (err instanceof CheckError) return EXIT.check;
|
|
@@ -8442,17 +8824,17 @@ async function main(argv) {
|
|
|
8442
8824
|
const spec = name === void 0 ? void 0 : COMMANDS[name];
|
|
8443
8825
|
if (wantsVersion) {
|
|
8444
8826
|
const suffix = await isBinary() ? " (binary)" : "";
|
|
8445
|
-
|
|
8827
|
+
process23.stdout.write(`rshow ${VERSION}${suffix}
|
|
8446
8828
|
`);
|
|
8447
8829
|
return EXIT.ok;
|
|
8448
8830
|
}
|
|
8449
8831
|
if (wantsHelp) {
|
|
8450
|
-
|
|
8832
|
+
process23.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
|
|
8451
8833
|
`);
|
|
8452
8834
|
return EXIT.ok;
|
|
8453
8835
|
}
|
|
8454
8836
|
if (name === void 0) {
|
|
8455
|
-
|
|
8837
|
+
process23.stderr.write(`${GENERAL_USAGE}
|
|
8456
8838
|
`);
|
|
8457
8839
|
return EXIT.usage;
|
|
8458
8840
|
}
|
|
@@ -8463,12 +8845,12 @@ async function main(argv) {
|
|
|
8463
8845
|
const rest = [...args.slice(0, index), ...args.slice(index + 1)];
|
|
8464
8846
|
const configs = /* @__PURE__ */ new Map();
|
|
8465
8847
|
const clients = /* @__PURE__ */ new Map();
|
|
8466
|
-
const keyOf = (dir) => dir === void 0 ? "" :
|
|
8848
|
+
const keyOf = (dir) => dir === void 0 ? "" : path25.resolve(dir);
|
|
8467
8849
|
const configFor = (dir) => {
|
|
8468
8850
|
const key2 = keyOf(dir);
|
|
8469
8851
|
let pending = configs.get(key2);
|
|
8470
8852
|
if (pending === void 0) {
|
|
8471
|
-
pending = loadConfig(
|
|
8853
|
+
pending = loadConfig(process23.env, dir);
|
|
8472
8854
|
configs.set(key2, pending);
|
|
8473
8855
|
}
|
|
8474
8856
|
return pending;
|
|
@@ -8481,7 +8863,7 @@ async function main(argv) {
|
|
|
8481
8863
|
if (cached !== void 0) return cached;
|
|
8482
8864
|
const settings = await configFor(dir);
|
|
8483
8865
|
const url = requireUrl(settings);
|
|
8484
|
-
const client = new Client(url, await readToken(
|
|
8866
|
+
const client = new Client(url, await readToken(process23.env, url));
|
|
8485
8867
|
clients.set(key2, client);
|
|
8486
8868
|
return client;
|
|
8487
8869
|
},
|
|
@@ -8490,11 +8872,11 @@ async function main(argv) {
|
|
|
8490
8872
|
const code = await spec.run(rest, ctx);
|
|
8491
8873
|
return code ?? EXIT.ok;
|
|
8492
8874
|
}
|
|
8493
|
-
main(
|
|
8875
|
+
main(process23.argv.slice(2)).then(
|
|
8494
8876
|
(code) => {
|
|
8495
|
-
|
|
8877
|
+
process23.exitCode = code;
|
|
8496
8878
|
},
|
|
8497
8879
|
(err) => {
|
|
8498
|
-
|
|
8880
|
+
process23.exitCode = fail(err);
|
|
8499
8881
|
}
|
|
8500
8882
|
);
|