rshow 0.0.1 → 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 +1423 -522
- package/package.json +4 -3
- package/skills/rshow/SKILL.md +1 -1
- package/skills/rshow/reference/cli.md +28 -5
package/dist/main.js
CHANGED
|
@@ -1,8 +1,115 @@
|
|
|
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
|
+
|
|
7
|
+
// src/binary.ts
|
|
8
|
+
import { mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
|
|
13
|
+
// src/version.ts
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
var VERSION = true ? "0.0.3" : versionFromPackage();
|
|
17
|
+
|
|
18
|
+
// src/binary.ts
|
|
19
|
+
var ASSET_MANIFEST = "assets.json";
|
|
20
|
+
var COMPLETE_MARKER = ".complete";
|
|
21
|
+
var sea;
|
|
22
|
+
function seaModule() {
|
|
23
|
+
if (sea === void 0) {
|
|
24
|
+
sea = import("node:sea").then(
|
|
25
|
+
(mod) => mod,
|
|
26
|
+
() => void 0
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return sea;
|
|
30
|
+
}
|
|
31
|
+
async function isBinary() {
|
|
32
|
+
const mod = await seaModule();
|
|
33
|
+
if (mod === void 0 || typeof mod.isSea !== "function") return false;
|
|
34
|
+
try {
|
|
35
|
+
return mod.isSea();
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function cacheRoot(env = process.env) {
|
|
41
|
+
const xdg = (env.XDG_CACHE_HOME ?? "").trim();
|
|
42
|
+
if (xdg !== "") return path.resolve(xdg);
|
|
43
|
+
const home = (env.HOME ?? "").trim();
|
|
44
|
+
return path.join(home !== "" ? home : homedir(), ".cache");
|
|
45
|
+
}
|
|
46
|
+
function binaryCacheDir(env = process.env, version = VERSION) {
|
|
47
|
+
return path.join(cacheRoot(env), "rshow", version);
|
|
48
|
+
}
|
|
49
|
+
async function exists(file) {
|
|
50
|
+
return await stat(file).catch(() => void 0) !== void 0;
|
|
51
|
+
}
|
|
52
|
+
async function extractAssets(bundle, dir) {
|
|
53
|
+
if (await exists(path.join(dir, COMPLETE_MARKER))) return;
|
|
54
|
+
const parent = path.dirname(dir);
|
|
55
|
+
await mkdir(parent, { recursive: true });
|
|
56
|
+
const tmp = await mkdtemp(path.join(parent, `${path.basename(dir)}.tmp-`));
|
|
57
|
+
try {
|
|
58
|
+
for (const key2 of bundle.keys) {
|
|
59
|
+
const file = path.join(tmp, ...key2.split("/"));
|
|
60
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
61
|
+
await writeFile(file, bundle.read(key2));
|
|
62
|
+
}
|
|
63
|
+
await writeFile(path.join(tmp, COMPLETE_MARKER), `${VERSION}
|
|
64
|
+
`);
|
|
65
|
+
await rm(dir, { recursive: true, force: true });
|
|
66
|
+
await rename(tmp, dir);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
await rm(tmp, { recursive: true, force: true });
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function safeKey(key2) {
|
|
73
|
+
return key2 !== "" && !path.isAbsolute(key2) && !key2.split("/").includes("..");
|
|
74
|
+
}
|
|
75
|
+
function manifestKeys(read4) {
|
|
76
|
+
try {
|
|
77
|
+
const doc = JSON.parse(new TextDecoder().decode(read4(ASSET_MANIFEST)));
|
|
78
|
+
if (doc !== null && typeof doc === "object" && Array.isArray(doc.keys)) {
|
|
79
|
+
const keys = doc.keys.filter((key2) => typeof key2 === "string");
|
|
80
|
+
if (keys.length > 0) return keys.filter(safeKey);
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
function bundleOf(mod) {
|
|
87
|
+
const raw = mod.getRawAsset;
|
|
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
|
+
}
|
|
92
|
+
async function unpack(env) {
|
|
93
|
+
if (!await isBinary()) return void 0;
|
|
94
|
+
const mod = await seaModule();
|
|
95
|
+
if (mod?.getRawAsset === void 0) throw new Error("this rshow binary carries no assets");
|
|
96
|
+
const dir = binaryCacheDir(env);
|
|
97
|
+
await extractAssets(bundleOf(mod), dir);
|
|
98
|
+
return dir;
|
|
99
|
+
}
|
|
100
|
+
var root;
|
|
101
|
+
function binaryRoot(env = process.env) {
|
|
102
|
+
if (root === void 0) root = unpack(env);
|
|
103
|
+
return root;
|
|
104
|
+
}
|
|
105
|
+
async function binaryThemeBases(env = process.env) {
|
|
106
|
+
const dir = await binaryRoot(env);
|
|
107
|
+
return dir === void 0 ? [] : [path.join(dir, "themes")];
|
|
108
|
+
}
|
|
109
|
+
async function binarySkillBases(env = process.env) {
|
|
110
|
+
const dir = await binaryRoot(env);
|
|
111
|
+
return dir === void 0 ? [] : [path.join(dir, "skills")];
|
|
112
|
+
}
|
|
6
113
|
|
|
7
114
|
// src/exit.ts
|
|
8
115
|
var EXIT = { ok: 0, usage: 2, auth: 4, api: 5, build: 5, check: 6, compare: 7 };
|
|
@@ -14,18 +121,18 @@ var UsageError = class extends Error {
|
|
|
14
121
|
};
|
|
15
122
|
var AuthError = class extends Error {
|
|
16
123
|
status;
|
|
17
|
-
constructor(message,
|
|
124
|
+
constructor(message, status2) {
|
|
18
125
|
super(message);
|
|
19
126
|
this.name = "AuthError";
|
|
20
|
-
this.status =
|
|
127
|
+
this.status = status2;
|
|
21
128
|
}
|
|
22
129
|
};
|
|
23
130
|
var ApiError = class extends Error {
|
|
24
131
|
status;
|
|
25
|
-
constructor(message,
|
|
132
|
+
constructor(message, status2 = 0) {
|
|
26
133
|
super(message);
|
|
27
134
|
this.name = "ApiError";
|
|
28
|
-
this.status =
|
|
135
|
+
this.status = status2;
|
|
29
136
|
}
|
|
30
137
|
};
|
|
31
138
|
var BuildError = class extends Error {
|
|
@@ -41,20 +148,150 @@ var CheckError = class extends Error {
|
|
|
41
148
|
}
|
|
42
149
|
};
|
|
43
150
|
|
|
44
|
-
// src/
|
|
45
|
-
import {
|
|
46
|
-
import
|
|
47
|
-
|
|
151
|
+
// src/network.ts
|
|
152
|
+
import { readFile } from "node:fs/promises";
|
|
153
|
+
import process3 from "node:process";
|
|
154
|
+
import tls from "node:tls";
|
|
48
155
|
|
|
49
|
-
// src/
|
|
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
|
+
};
|
|
50
183
|
function messageOf(err) {
|
|
51
184
|
return err instanceof Error ? err.message : String(err);
|
|
52
185
|
}
|
|
53
|
-
function
|
|
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
|
|
290
|
+
function errorText(body, status2) {
|
|
54
291
|
if (body !== null && typeof body === "object" && typeof body.error === "string") {
|
|
55
292
|
return body.error;
|
|
56
293
|
}
|
|
57
|
-
return `HTTP ${
|
|
294
|
+
return `HTTP ${status2}`;
|
|
58
295
|
}
|
|
59
296
|
function parseBody(text) {
|
|
60
297
|
if (text.trim() === "") return void 0;
|
|
@@ -87,12 +324,7 @@ var Client = class {
|
|
|
87
324
|
headers["content-type"] = opts.contentType ?? "application/octet-stream";
|
|
88
325
|
body = opts.bytes;
|
|
89
326
|
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
res = await this.fetchImpl(this.baseUrl + pathname, { method, headers, body });
|
|
93
|
-
} catch (err) {
|
|
94
|
-
throw new ApiError(`network error: ${messageOf(err)}`);
|
|
95
|
-
}
|
|
327
|
+
const res = await sendRequest(this.fetchImpl, this.baseUrl + pathname, { method, headers, body });
|
|
96
328
|
const text = await res.text();
|
|
97
329
|
const parsed = parseBody(text);
|
|
98
330
|
if (opts.allow404 === true && res.status === 404) return { status: 404, body: void 0 };
|
|
@@ -178,73 +410,9 @@ var Client = class {
|
|
|
178
410
|
};
|
|
179
411
|
|
|
180
412
|
// src/config.ts
|
|
181
|
-
import { readFile } from "node:fs/promises";
|
|
182
|
-
import { homedir } from "node:os";
|
|
183
|
-
import
|
|
184
|
-
|
|
185
|
-
// src/keychain.ts
|
|
186
|
-
import { spawn } from "node:child_process";
|
|
187
|
-
import process from "node:process";
|
|
188
|
-
var SERVICE = "rshow";
|
|
189
|
-
var MISSING = 44;
|
|
190
|
-
var NO_MACOS = "the keychain needs macOS; set RSHOW_TOKEN instead";
|
|
191
|
-
function bin() {
|
|
192
|
-
return process.env.RSHOW_SECURITY_BIN ?? "security";
|
|
193
|
-
}
|
|
194
|
-
function usable() {
|
|
195
|
-
return process.platform === "darwin" || process.env.RSHOW_SECURITY_BIN !== void 0;
|
|
196
|
-
}
|
|
197
|
-
function run(args, input) {
|
|
198
|
-
return new Promise((resolve2, reject) => {
|
|
199
|
-
const child = spawn(bin(), args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
200
|
-
let stdout = "";
|
|
201
|
-
let stderr = "";
|
|
202
|
-
child.stdout.setEncoding("utf8");
|
|
203
|
-
child.stdout.on("data", (chunk2) => {
|
|
204
|
-
stdout += chunk2;
|
|
205
|
-
});
|
|
206
|
-
child.stderr.setEncoding("utf8");
|
|
207
|
-
child.stderr.on("data", (chunk2) => {
|
|
208
|
-
stderr += chunk2;
|
|
209
|
-
});
|
|
210
|
-
child.stdin.on("error", () => {
|
|
211
|
-
});
|
|
212
|
-
child.on("error", (err) => {
|
|
213
|
-
if (err.code === "ENOENT") resolve2({ code: -1, stdout, stderr, absent: true });
|
|
214
|
-
else reject(new ApiError(`keychain: ${err.message}`));
|
|
215
|
-
});
|
|
216
|
-
child.on("close", (code) => resolve2({ code: code ?? -1, stdout, stderr, absent: false }));
|
|
217
|
-
child.stdin.end(input ?? "");
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
function failure(doing, result, secret) {
|
|
221
|
-
const text = secret === void 0 ? result.stderr : result.stderr.split(secret).join("[redacted]");
|
|
222
|
-
const detail = text.trim();
|
|
223
|
-
return new ApiError(`${doing}: ${detail === "" ? `exit ${result.code}` : detail}`);
|
|
224
|
-
}
|
|
225
|
-
async function save(url, token) {
|
|
226
|
-
if (!usable()) throw new UsageError(NO_MACOS);
|
|
227
|
-
const line = `add-generic-password -U -s ${SERVICE} -a ${url} -w ${token}
|
|
228
|
-
`;
|
|
229
|
-
const result = await run(["-i"], line);
|
|
230
|
-
if (result.code !== 0) throw failure("cannot save the token in the keychain", result, token);
|
|
231
|
-
if (await read(url) !== token) throw failure("cannot save the token in the keychain", result, token);
|
|
232
|
-
}
|
|
233
|
-
async function read(url) {
|
|
234
|
-
if (!usable()) return void 0;
|
|
235
|
-
const result = await run(["find-generic-password", "-s", SERVICE, "-a", url, "-w"]);
|
|
236
|
-
if (result.absent || result.code === MISSING) return void 0;
|
|
237
|
-
if (result.code !== 0) throw failure("cannot read the token from the keychain", result);
|
|
238
|
-
const token = result.stdout.trim();
|
|
239
|
-
return token === "" ? void 0 : token;
|
|
240
|
-
}
|
|
241
|
-
async function remove(url) {
|
|
242
|
-
if (!usable()) throw new UsageError(NO_MACOS);
|
|
243
|
-
const result = await run(["delete-generic-password", "-s", SERVICE, "-a", url]);
|
|
244
|
-
if (result.absent || result.code === MISSING) return false;
|
|
245
|
-
if (result.code !== 0) throw failure("cannot remove the token from the keychain", result);
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
413
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
414
|
+
import { homedir as homedir3 } from "node:os";
|
|
415
|
+
import path3 from "node:path";
|
|
248
416
|
|
|
249
417
|
// ../shared/src/index.ts
|
|
250
418
|
var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
@@ -291,8 +459,8 @@ function tagsOf(value) {
|
|
|
291
459
|
if (!Array.isArray(value)) return [];
|
|
292
460
|
return value.filter((tag) => typeof tag === "string").map((tag) => tag.toLowerCase());
|
|
293
461
|
}
|
|
294
|
-
function withTags(current, add2,
|
|
295
|
-
const next = unique(tagsOf(current)).filter((tag) => !
|
|
462
|
+
function withTags(current, add2, remove5) {
|
|
463
|
+
const next = unique(tagsOf(current)).filter((tag) => !remove5.includes(tag));
|
|
296
464
|
for (const tag of add2) {
|
|
297
465
|
if (!next.includes(tag)) next.push(tag);
|
|
298
466
|
}
|
|
@@ -304,16 +472,306 @@ function sameTags(left, right) {
|
|
|
304
472
|
return a.length === b.length && a.every((tag, index) => tag === b[index]);
|
|
305
473
|
}
|
|
306
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
|
+
|
|
307
765
|
// src/config.ts
|
|
308
766
|
var DEFAULT_OUT = "dist";
|
|
309
767
|
var GLOBAL_DIR = "rshow";
|
|
310
768
|
var GLOBAL_FILE = "config.json";
|
|
311
769
|
var LOCAL_FILE = "rshow.json";
|
|
312
770
|
var NO_SERVER = "no server configured: run rshow init --url <admin-url> --art <viewer-url>";
|
|
313
|
-
function
|
|
771
|
+
function messageOf3(err) {
|
|
314
772
|
return err instanceof Error ? err.message : String(err);
|
|
315
773
|
}
|
|
316
|
-
function
|
|
774
|
+
function isRecord2(value) {
|
|
317
775
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
318
776
|
}
|
|
319
777
|
function withoutSlash(value) {
|
|
@@ -322,14 +780,14 @@ function withoutSlash(value) {
|
|
|
322
780
|
function globalConfigPath(env) {
|
|
323
781
|
const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
|
|
324
782
|
const home = (env.HOME ?? "").trim();
|
|
325
|
-
const base = xdg !== "" ? xdg :
|
|
326
|
-
return
|
|
783
|
+
const base = xdg !== "" ? xdg : path3.join(home !== "" ? home : homedir3(), ".config");
|
|
784
|
+
return path3.join(base, GLOBAL_DIR, GLOBAL_FILE);
|
|
327
785
|
}
|
|
328
786
|
function localConfigPath(dir) {
|
|
329
|
-
return
|
|
787
|
+
return path3.join(path3.resolve(dir), LOCAL_FILE);
|
|
330
788
|
}
|
|
331
789
|
function layerOf(file, value) {
|
|
332
|
-
if (!
|
|
790
|
+
if (!isRecord2(value)) throw new UsageError(`${file} must hold a JSON object`);
|
|
333
791
|
const fieldOf = (key2) => {
|
|
334
792
|
const raw = value[key2];
|
|
335
793
|
if (raw === void 0 || raw === null) return void 0;
|
|
@@ -349,7 +807,7 @@ function layerOf(file, value) {
|
|
|
349
807
|
};
|
|
350
808
|
const rawTokens = value.tokens;
|
|
351
809
|
if (rawTokens !== void 0 && rawTokens !== null) {
|
|
352
|
-
if (!
|
|
810
|
+
if (!isRecord2(rawTokens)) throw new UsageError(`${file}: tokens must be an object`);
|
|
353
811
|
const tokens = {};
|
|
354
812
|
for (const [key2, raw] of Object.entries(rawTokens)) {
|
|
355
813
|
if (typeof raw !== "string") throw new UsageError(`${file}: tokens.${key2} must be a string`);
|
|
@@ -371,10 +829,10 @@ function layerOf(file, value) {
|
|
|
371
829
|
async function readConfigFile(file) {
|
|
372
830
|
let text;
|
|
373
831
|
try {
|
|
374
|
-
text = await
|
|
832
|
+
text = await readFile3(file, "utf8");
|
|
375
833
|
} catch (err) {
|
|
376
834
|
if (err.code === "ENOENT") return {};
|
|
377
|
-
throw new UsageError(`cannot read ${file}: ${
|
|
835
|
+
throw new UsageError(`cannot read ${file}: ${messageOf3(err)}`);
|
|
378
836
|
}
|
|
379
837
|
let parsed;
|
|
380
838
|
try {
|
|
@@ -436,13 +894,14 @@ function requireArt(config) {
|
|
|
436
894
|
async function readToken(env, url) {
|
|
437
895
|
const token = (env.RSHOW_TOKEN ?? "").trim();
|
|
438
896
|
if (token !== "") return token;
|
|
439
|
-
const
|
|
897
|
+
const store2 = await choose(env);
|
|
898
|
+
const saved = await store2.read(url);
|
|
440
899
|
if (saved !== void 0) return saved;
|
|
441
900
|
throw new UsageError("no token: run rshow login, or set RSHOW_TOKEN");
|
|
442
901
|
}
|
|
443
902
|
|
|
444
903
|
// src/output.ts
|
|
445
|
-
import
|
|
904
|
+
import process9 from "node:process";
|
|
446
905
|
var Output = class {
|
|
447
906
|
json;
|
|
448
907
|
constructor(json) {
|
|
@@ -450,16 +909,16 @@ var Output = class {
|
|
|
450
909
|
}
|
|
451
910
|
data(value) {
|
|
452
911
|
if (!this.json) return;
|
|
453
|
-
|
|
912
|
+
process9.stdout.write(`${JSON.stringify(value ?? null, null, 2)}
|
|
454
913
|
`);
|
|
455
914
|
}
|
|
456
915
|
line(text) {
|
|
457
916
|
if (this.json) return;
|
|
458
|
-
|
|
917
|
+
process9.stdout.write(`${text}
|
|
459
918
|
`);
|
|
460
919
|
}
|
|
461
920
|
raw(text) {
|
|
462
|
-
|
|
921
|
+
process9.stdout.write(`${text}
|
|
463
922
|
`);
|
|
464
923
|
}
|
|
465
924
|
};
|
|
@@ -475,12 +934,13 @@ commands:
|
|
|
475
934
|
preview <dir> serve a folder on 127.0.0.1 for a look, offline
|
|
476
935
|
config [dir] print the merged config as JSON
|
|
477
936
|
init --url u --art a write the global config file
|
|
478
|
-
doctor [dir] check node, config, token, api, art
|
|
937
|
+
doctor [dir] check node, config, token, api, art, theme, skills
|
|
479
938
|
theme new <name> write a preset folder from a built-in theme
|
|
480
939
|
theme check <dir> check a theme folder, preset or full
|
|
481
940
|
theme preview <dir> build a theme, serve it on 127.0.0.1 for a look
|
|
482
941
|
theme publish <dir> publish a theme folder as theme-<name>
|
|
483
942
|
theme list list theme artifacts
|
|
943
|
+
skills <cmd> install, remove or show the agent skills, per target
|
|
484
944
|
project <cmd> create, list, show, share or delete a project
|
|
485
945
|
tag <slug> add or remove tags on an artifact
|
|
486
946
|
list [filters] list artifacts you own or share
|
|
@@ -488,7 +948,7 @@ commands:
|
|
|
488
948
|
share <slug> set mode or change shares
|
|
489
949
|
pin <slug> <n|live> serve version n, or live for the newest
|
|
490
950
|
delete <slug> --yes delete an artifact
|
|
491
|
-
login [--url u] save the API token
|
|
951
|
+
login [--url u] save the API token for this account
|
|
492
952
|
logout [--url u] remove the saved token for a server
|
|
493
953
|
|
|
494
954
|
global:
|
|
@@ -499,9 +959,13 @@ global:
|
|
|
499
959
|
environment:
|
|
500
960
|
RSHOW_URL API base URL, overrides the config file
|
|
501
961
|
RSHOW_ART art base URL, overrides the config file
|
|
502
|
-
RSHOW_TOKEN the API token, else the token
|
|
503
|
-
|
|
504
|
-
|
|
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+)
|
|
505
969
|
|
|
506
970
|
Start here: rshow init --url <admin-url> --art <viewer-url>, then rshow login.
|
|
507
971
|
|
|
@@ -599,8 +1063,8 @@ files win. Run rshow init to write url and art.
|
|
|
599
1063
|
A theme with no version, or @latest, is looked up on the art host and printed
|
|
600
1064
|
as <name>@<n>; when that fails a warning goes to stderr. --offline skips it, and
|
|
601
1065
|
so does a config with no art host.
|
|
602
|
-
The token is RSHOW_TOKEN, else the token
|
|
603
|
-
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.`;
|
|
604
1068
|
var INIT_USAGE = `usage: rshow init --url <admin-url> --art <viewer-url> [--theme <name>] [--force]
|
|
605
1069
|
|
|
606
1070
|
Writes the global config file that rshow reads first:
|
|
@@ -616,9 +1080,11 @@ Prints the path it wrote and the next step: rshow login. --json prints
|
|
|
616
1080
|
var DOCTOR_USAGE = `usage: rshow doctor [dir] [--json]
|
|
617
1081
|
|
|
618
1082
|
Checks the rshow version, the node version against the engines.node of the
|
|
619
|
-
package, the config files, the token, the api, the art host
|
|
620
|
-
line each: ok|warn|fail <check> <detail>. With [dir] it reads
|
|
1083
|
+
package, the config files, the token, the api, the art host, the theme and the
|
|
1084
|
+
skills, one line each: ok|warn|fail <check> <detail>. With [dir] it reads
|
|
1085
|
+
<dir>/rshow.json too. A skill line warns and never fails.
|
|
621
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.
|
|
622
1088
|
The token is never printed.`;
|
|
623
1089
|
var THEME_USAGE = `usage: rshow theme <new|check|preview|publish|list> [options]
|
|
624
1090
|
|
|
@@ -676,6 +1142,54 @@ Prints /_themes/<name>@<n>/.`;
|
|
|
676
1142
|
var THEME_LIST_USAGE = `usage: rshow theme list
|
|
677
1143
|
|
|
678
1144
|
Lists artifacts whose slug starts with theme- and their served version.`;
|
|
1145
|
+
var SKILLS_USAGE = `usage: rshow skills <install|uninstall|status> [target ...] [options]
|
|
1146
|
+
|
|
1147
|
+
skills install [target ...] link or copy the three skills into an agent
|
|
1148
|
+
skills uninstall [target ...] remove the links and copies rshow made
|
|
1149
|
+
skills status [target ...] say what each target holds, per skill
|
|
1150
|
+
|
|
1151
|
+
A target is where an agent reads its skills from:
|
|
1152
|
+
claude $HOME/.claude/skills
|
|
1153
|
+
pi $HOME/.pi/agent/skills
|
|
1154
|
+
codex $HOME/.codex/skills
|
|
1155
|
+
With no target, every target whose parent folder exists is used.`;
|
|
1156
|
+
var SKILLS_INSTALL_USAGE = `usage: rshow skills install [target ...] [--link|--copy] [--dry-run]
|
|
1157
|
+
|
|
1158
|
+
Puts the three skills rshow ships, rshow, rshow-slides and rshow-implement, in
|
|
1159
|
+
the skills folder of every target, so that claude, pi and codex read them:
|
|
1160
|
+
$HOME/.claude/skills, $HOME/.pi/agent/skills and $HOME/.codex/skills. With no
|
|
1161
|
+
target, every target whose parent folder exists is used; naming one of claude,
|
|
1162
|
+
pi and codex picks it, and a usage error names them when none is detected.
|
|
1163
|
+
Each skill is a symlink to the folder rshow reads it from, so an upgrade of
|
|
1164
|
+
rshow is nothing to do. --copy copies the folder and writes a .rshow-skill
|
|
1165
|
+
marker in it, {"version": <rshow version>, "source": "rshow"}, which is how a
|
|
1166
|
+
newer rshow knows the copy is its own. The default is a link, and a copy when
|
|
1167
|
+
that folder sits in an npx cache, which npm may delete under a link, or when
|
|
1168
|
+
rshow runs as a single executable, whose files live in a per-version cache.
|
|
1169
|
+
A skill that is ours and current prints ok, and one of ours from another path or
|
|
1170
|
+
another version is replaced and prints updated. Anything else, a folder, a file
|
|
1171
|
+
or a link that rshow did not make, is left alone: skip goes to stderr, the rest
|
|
1172
|
+
is still installed, and the run exits 6.
|
|
1173
|
+
--dry-run prints what would happen, with would, and changes nothing.
|
|
1174
|
+
Prints one line per skill, then installed <n> skills for <targets>; --json
|
|
1175
|
+
prints {targets, actions: [{target, skill, action, path}]}.
|
|
1176
|
+
RSHOW_SKILLS_DIR reads the skills from another folder.`;
|
|
1177
|
+
var SKILLS_UNINSTALL_USAGE = `usage: rshow skills uninstall [target ...] [--dry-run]
|
|
1178
|
+
|
|
1179
|
+
Removes the skills rshow put in every target: a symlink into a skills folder
|
|
1180
|
+
that holds rshow skills, and a copy that carries the .rshow-skill marker.
|
|
1181
|
+
It removes nothing else, with no line and no error. With no target, every
|
|
1182
|
+
target whose parent folder exists is used.
|
|
1183
|
+
Prints removed <path> per skill, then removed <n> skills for <targets>, and
|
|
1184
|
+
--dry-run prints what would happen, with would, and changes nothing.`;
|
|
1185
|
+
var SKILLS_STATUS_USAGE = `usage: rshow skills status [target ...] [--json]
|
|
1186
|
+
|
|
1187
|
+
Prints one line per target and skill, <target> <skill> <state>, where the
|
|
1188
|
+
state is link -> <path>, copy v<version>, missing or foreign. It prints nothing
|
|
1189
|
+
for a target whose parent folder does not exist, and --json prints {targets,
|
|
1190
|
+
actions: [{target, skill, action, path}]}.
|
|
1191
|
+
A copy of another version also warns on stderr, because rshow skills install
|
|
1192
|
+
replaces it.`;
|
|
679
1193
|
var LIST_USAGE = `usage: rshow list [--project p] [--tag t] [--q text]
|
|
680
1194
|
|
|
681
1195
|
Lists artifacts you own or that are shared with you.
|
|
@@ -697,14 +1211,19 @@ var DELETE_USAGE = `usage: rshow delete <slug> --yes
|
|
|
697
1211
|
Deletes the artifact and needs --yes.`;
|
|
698
1212
|
var LOGIN_USAGE = `usage: rshow login [--url u]
|
|
699
1213
|
|
|
700
|
-
Reads the token from stdin, checks it
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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.`;
|
|
705
1223
|
var LOGOUT_USAGE = `usage: rshow logout [--url u]
|
|
706
1224
|
|
|
707
|
-
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.`;
|
|
708
1227
|
var PROJECT_USAGE = `usage: rshow project <create|list|show|share|delete> [options]
|
|
709
1228
|
|
|
710
1229
|
project create <slug> [--title t] [--description d]
|
|
@@ -830,14 +1349,14 @@ function usageLine(usage) {
|
|
|
830
1349
|
}
|
|
831
1350
|
|
|
832
1351
|
// src/commands/publish.ts
|
|
833
|
-
import { stat as
|
|
834
|
-
import
|
|
1352
|
+
import { stat as stat5 } from "node:fs/promises";
|
|
1353
|
+
import path8 from "node:path";
|
|
835
1354
|
|
|
836
1355
|
// src/args.ts
|
|
837
1356
|
import { parseArgs } from "node:util";
|
|
838
1357
|
var LIST_FLAGS = /* @__PURE__ */ new Set(["--add", "--editor", "--remove", "--tag"]);
|
|
839
1358
|
var LOWERCASE_TAG = new RegExp(TAG_PATTERN.source, "i");
|
|
840
|
-
function
|
|
1359
|
+
function messageOf4(err) {
|
|
841
1360
|
return err instanceof Error ? err.message : String(err);
|
|
842
1361
|
}
|
|
843
1362
|
function isListValue(token) {
|
|
@@ -873,7 +1392,7 @@ function parseCommandArgs(argv, options, minPositionals, maxPositionals, hint) {
|
|
|
873
1392
|
try {
|
|
874
1393
|
parsed = parseArgs({ args: expanded, options, allowPositionals: true, strict: true });
|
|
875
1394
|
} catch (err) {
|
|
876
|
-
throw new UsageError(`${
|
|
1395
|
+
throw new UsageError(`${messageOf4(err)} (try: ${usageLine(hint)})`);
|
|
877
1396
|
}
|
|
878
1397
|
const count = parsed.positionals.length;
|
|
879
1398
|
if (count < minPositionals || count > maxPositionals) {
|
|
@@ -905,21 +1424,21 @@ function emailList(args, name) {
|
|
|
905
1424
|
|
|
906
1425
|
// src/build.ts
|
|
907
1426
|
import { spawn as spawn2 } from "node:child_process";
|
|
908
|
-
import
|
|
909
|
-
function
|
|
1427
|
+
import process10 from "node:process";
|
|
1428
|
+
function messageOf5(err) {
|
|
910
1429
|
return err instanceof Error ? err.message : String(err);
|
|
911
1430
|
}
|
|
912
1431
|
function shellPath(env) {
|
|
913
1432
|
const shell = (env.SHELL ?? "").trim();
|
|
914
1433
|
return shell === "" ? "/bin/sh" : shell;
|
|
915
1434
|
}
|
|
916
|
-
async function runBuild(dir, command, env =
|
|
1435
|
+
async function runBuild(dir, command, env = process10.env) {
|
|
917
1436
|
const code = await new Promise((resolve2, reject) => {
|
|
918
1437
|
const child = spawn2(shellPath(env), ["-c", command], { cwd: dir, stdio: "inherit" });
|
|
919
1438
|
child.once("error", reject);
|
|
920
1439
|
child.once("close", (value) => resolve2(value));
|
|
921
1440
|
}).catch((err) => {
|
|
922
|
-
throw new BuildError(`build failed: ${
|
|
1441
|
+
throw new BuildError(`build failed: ${messageOf5(err)}`);
|
|
923
1442
|
});
|
|
924
1443
|
if (code !== 0) {
|
|
925
1444
|
throw new BuildError(`build failed: ${code === null ? "killed by a signal" : `exit ${code}`}`);
|
|
@@ -927,12 +1446,12 @@ async function runBuild(dir, command, env = process3.env) {
|
|
|
927
1446
|
}
|
|
928
1447
|
|
|
929
1448
|
// src/check.ts
|
|
930
|
-
import { readFile as
|
|
931
|
-
import
|
|
1449
|
+
import { readFile as readFile6, readdir, stat as stat3 } from "node:fs/promises";
|
|
1450
|
+
import path6 from "node:path";
|
|
932
1451
|
|
|
933
1452
|
// src/mockup.ts
|
|
934
|
-
import { readFile as
|
|
935
|
-
import
|
|
1453
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1454
|
+
import path4 from "node:path";
|
|
936
1455
|
var MAX_SCAN_BYTES = 4 * 1024 * 1024;
|
|
937
1456
|
var HTML_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm"]);
|
|
938
1457
|
var MIN_COLS = 80;
|
|
@@ -1002,20 +1521,20 @@ function idOf(value) {
|
|
|
1002
1521
|
const id = String(value ?? "").trim().replace(/^#/, "");
|
|
1003
1522
|
return id === "" ? void 0 : id;
|
|
1004
1523
|
}
|
|
1005
|
-
function
|
|
1524
|
+
function warn2(scan, line, message) {
|
|
1006
1525
|
scan.out.push({ path: scan.path, line, message });
|
|
1007
1526
|
}
|
|
1008
1527
|
function keyHint(scan, masked, from, to, line) {
|
|
1009
1528
|
const hit = TERMINAL_KEYS.exec(masked.slice(from, to).replace(ANY_TAG, ""));
|
|
1010
1529
|
if (hit === null) return;
|
|
1011
|
-
|
|
1530
|
+
warn2(scan, line, `key hint binds the terminal's key: ${hit[0].trim()}`);
|
|
1012
1531
|
}
|
|
1013
1532
|
function sizeGate(scan, attrs, line) {
|
|
1014
1533
|
if (attrs.has("data-allow-small")) return;
|
|
1015
1534
|
const cols = sizeOf(attrs, "data-cols", MIN_COLS);
|
|
1016
1535
|
const rows = sizeOf(attrs, "data-rows", MIN_ROWS);
|
|
1017
1536
|
if (cols >= MIN_COLS && rows >= MIN_ROWS) return;
|
|
1018
|
-
|
|
1537
|
+
warn2(scan, line, `tui below the 80x24 minimum (${cols}x${rows}), or mark it with data-allow-small`);
|
|
1019
1538
|
}
|
|
1020
1539
|
function walk(scan, masked) {
|
|
1021
1540
|
const stack = [];
|
|
@@ -1095,25 +1614,25 @@ function resolve(scan) {
|
|
|
1095
1614
|
ids.add(item.id);
|
|
1096
1615
|
const count = (seen.get(item.id) ?? 0) + 1;
|
|
1097
1616
|
seen.set(item.id, count);
|
|
1098
|
-
if (count === 2)
|
|
1617
|
+
if (count === 2) warn2(scan, item.line, `duplicate artboard id: ${item.id}`);
|
|
1099
1618
|
}
|
|
1100
1619
|
const unknown = /* @__PURE__ */ new Set();
|
|
1101
1620
|
for (const flow of scan.flows) {
|
|
1102
1621
|
if (ids.has(flow.id) || unknown.has(flow.id)) continue;
|
|
1103
1622
|
unknown.add(flow.id);
|
|
1104
|
-
|
|
1623
|
+
warn2(scan, flow.line, `flow to #${flow.id} is not an artboard id in this file`);
|
|
1105
1624
|
}
|
|
1106
1625
|
for (const item of scan.boards) {
|
|
1107
|
-
if (!item.title)
|
|
1626
|
+
if (!item.title) warn2(scan, item.line, `artboard ${item.label} has no data-title`);
|
|
1108
1627
|
for (const block of item.blocks) {
|
|
1109
1628
|
const missing = block.states.filter((state) => state !== "default" && !item.states.includes(state));
|
|
1110
1629
|
if (missing.length === 0) continue;
|
|
1111
|
-
|
|
1630
|
+
warn2(scan, block.line, `block data-state "${missing.join(",")}" is not in the artboard data-states`);
|
|
1112
1631
|
}
|
|
1113
1632
|
for (const state of item.states) {
|
|
1114
1633
|
if (state === "default") continue;
|
|
1115
1634
|
if (item.blocks.some((block) => block.states.includes(state))) continue;
|
|
1116
|
-
|
|
1635
|
+
warn2(scan, item.line, `artboard ${item.label} lists state "${state}" that no block uses`);
|
|
1117
1636
|
}
|
|
1118
1637
|
}
|
|
1119
1638
|
}
|
|
@@ -1127,9 +1646,9 @@ function htmlWarnings(rel, text) {
|
|
|
1127
1646
|
async function mockupWarnings(entries) {
|
|
1128
1647
|
const out = [];
|
|
1129
1648
|
for (const entry of entries) {
|
|
1130
|
-
const ext =
|
|
1649
|
+
const ext = path4.extname(entry.rel).toLowerCase();
|
|
1131
1650
|
if (!HTML_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES) continue;
|
|
1132
|
-
const text = await
|
|
1651
|
+
const text = await readFile4(entry.abs, "utf8").catch(() => void 0);
|
|
1133
1652
|
if (text === void 0) continue;
|
|
1134
1653
|
out.push(...htmlWarnings(entry.rel, text));
|
|
1135
1654
|
}
|
|
@@ -1137,8 +1656,8 @@ async function mockupWarnings(entries) {
|
|
|
1137
1656
|
}
|
|
1138
1657
|
|
|
1139
1658
|
// src/style.ts
|
|
1140
|
-
import { readFile as
|
|
1141
|
-
import
|
|
1659
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
1660
|
+
import path5 from "node:path";
|
|
1142
1661
|
var MAX_SCAN_BYTES2 = 4 * 1024 * 1024;
|
|
1143
1662
|
var STYLE_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".css"]);
|
|
1144
1663
|
var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
@@ -1535,9 +2054,9 @@ async function styleWarnings(entries) {
|
|
|
1535
2054
|
const loaded = /* @__PURE__ */ new Set();
|
|
1536
2055
|
const used = [];
|
|
1537
2056
|
for (const entry of entries) {
|
|
1538
|
-
const ext =
|
|
2057
|
+
const ext = path5.extname(entry.rel).toLowerCase();
|
|
1539
2058
|
if (!STYLE_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES2) continue;
|
|
1540
|
-
const text = await
|
|
2059
|
+
const text = await readFile5(entry.abs, "utf8").catch(() => void 0);
|
|
1541
2060
|
if (text === void 0) continue;
|
|
1542
2061
|
collectLoaded(text, loaded);
|
|
1543
2062
|
if (stylePathSkipped(entry.rel)) continue;
|
|
@@ -1588,9 +2107,6 @@ var CONNECT_PATTERNS = [
|
|
|
1588
2107
|
pattern: /\.open\s*\(\s*["'`][A-Za-z]+["'`]\s*,\s*["'`](https?:\/\/[^"'`\s)]+)/g
|
|
1589
2108
|
}
|
|
1590
2109
|
];
|
|
1591
|
-
function messageOf5(err) {
|
|
1592
|
-
return err instanceof Error ? err.message : String(err);
|
|
1593
|
-
}
|
|
1594
2110
|
function add(scan, file, line, severity, message) {
|
|
1595
2111
|
scan.findings.push({ path: file, line, severity, message });
|
|
1596
2112
|
}
|
|
@@ -1641,7 +2157,7 @@ function attributes2(raw) {
|
|
|
1641
2157
|
}
|
|
1642
2158
|
return found;
|
|
1643
2159
|
}
|
|
1644
|
-
function
|
|
2160
|
+
function hostOf2(value) {
|
|
1645
2161
|
if (!EXTERNAL.test(value) || IGNORED.test(value)) return void 0;
|
|
1646
2162
|
try {
|
|
1647
2163
|
const host = new URL(value, BASE_URL).hostname;
|
|
@@ -1665,7 +2181,7 @@ function resolvePath(file, ref) {
|
|
|
1665
2181
|
decoded = ref;
|
|
1666
2182
|
}
|
|
1667
2183
|
const wantsIndex = decoded.endsWith("/");
|
|
1668
|
-
const base =
|
|
2184
|
+
const base = path6.posix.dirname(file);
|
|
1669
2185
|
const parts2 = base === "." ? [] : base.split("/");
|
|
1670
2186
|
for (const segment of decoded.split("/")) {
|
|
1671
2187
|
if (segment === "" || segment === ".") continue;
|
|
@@ -1716,7 +2232,7 @@ function resolveRef(scan, file, raw, line) {
|
|
|
1716
2232
|
}
|
|
1717
2233
|
function checkScript(scan, file, line, attrs) {
|
|
1718
2234
|
const value = (attrs.get("src") ?? "").trim();
|
|
1719
|
-
const host =
|
|
2235
|
+
const host = hostOf2(value);
|
|
1720
2236
|
if (host === void 0) return;
|
|
1721
2237
|
if (!SCRIPT_HOSTS.includes(host)) {
|
|
1722
2238
|
add(scan, file, line, "error", `script host not allowed: ${value} (allowed: ${SCRIPT_HOSTS.join(", ")})`);
|
|
@@ -1732,7 +2248,7 @@ function checkLink(scan, file, line, attrs) {
|
|
|
1732
2248
|
const rel = (attrs.get("rel") ?? "").toLowerCase().split(/\s+/);
|
|
1733
2249
|
if (!rel.includes("stylesheet")) return;
|
|
1734
2250
|
const value = (attrs.get("href") ?? "").trim();
|
|
1735
|
-
const host =
|
|
2251
|
+
const host = hostOf2(value);
|
|
1736
2252
|
if (host === void 0 || STYLE_HOSTS.includes(host)) return;
|
|
1737
2253
|
add(scan, file, line, "error", `stylesheet host not allowed: ${value} (allowed: ${STYLE_HOSTS.join(", ")})`);
|
|
1738
2254
|
}
|
|
@@ -1788,9 +2304,9 @@ function scanHtml(scan, file, text, facts) {
|
|
|
1788
2304
|
scanConnect(scan, file, masked, at);
|
|
1789
2305
|
}
|
|
1790
2306
|
async function scanFile(scan, entry, facts) {
|
|
1791
|
-
const ext =
|
|
2307
|
+
const ext = path6.extname(entry.rel).toLowerCase();
|
|
1792
2308
|
if (!SCAN_EXTENSIONS.has(ext) || entry.size > MAX_SCAN_BYTES3) return;
|
|
1793
|
-
const text = await
|
|
2309
|
+
const text = await readFile6(entry.abs, "utf8");
|
|
1794
2310
|
if (ext === ".html" || ext === ".htm") {
|
|
1795
2311
|
scanHtml(scan, entry.rel, text, facts);
|
|
1796
2312
|
return;
|
|
@@ -1805,7 +2321,7 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1805
2321
|
for (const item of items) {
|
|
1806
2322
|
if (item.name.startsWith(".") || SKIPPED_NAMES.has(item.name)) continue;
|
|
1807
2323
|
const rel = prefix === "" ? item.name : `${prefix}/${item.name}`;
|
|
1808
|
-
const abs =
|
|
2324
|
+
const abs = path6.join(dir, item.name);
|
|
1809
2325
|
const problem = pathProblem(rel);
|
|
1810
2326
|
if (problem !== void 0) add(scan, rel, 1, "error", problem);
|
|
1811
2327
|
if (item.isDirectory()) {
|
|
@@ -1813,54 +2329,56 @@ async function collect(scan, dir, prefix, entries) {
|
|
|
1813
2329
|
continue;
|
|
1814
2330
|
}
|
|
1815
2331
|
if (!item.isFile()) continue;
|
|
1816
|
-
const info = await
|
|
2332
|
+
const info = await stat3(abs).catch(() => void 0);
|
|
1817
2333
|
if (info === void 0) continue;
|
|
1818
2334
|
entries.push({ rel, abs, size: info.size });
|
|
1819
2335
|
}
|
|
1820
2336
|
}
|
|
1821
2337
|
async function checkThemeFile(scan, key2, rel, where) {
|
|
1822
2338
|
const url = `${scan.art}/_themes/${key2}/${rel}`;
|
|
1823
|
-
let
|
|
2339
|
+
let status2;
|
|
2340
|
+
await readyNetwork();
|
|
1824
2341
|
try {
|
|
1825
2342
|
const reply = await scan.fetchImpl(url, { method: "HEAD" });
|
|
1826
|
-
|
|
2343
|
+
status2 = reply.status;
|
|
1827
2344
|
} catch (err) {
|
|
1828
|
-
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)}`);
|
|
1829
2346
|
return;
|
|
1830
2347
|
}
|
|
1831
|
-
if (
|
|
1832
|
-
else if (
|
|
1833
|
-
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: HTTP ${
|
|
2348
|
+
if (status2 === 404) add(scan, where.file, where.line, "error", `theme ${key2} has no ${rel} (${url})`);
|
|
2349
|
+
else if (status2 < 200 || status2 > 399) {
|
|
2350
|
+
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}/${rel}: HTTP ${status2}`);
|
|
1834
2351
|
}
|
|
1835
2352
|
}
|
|
1836
2353
|
async function checkTheme(scan, key2, where) {
|
|
1837
2354
|
if (scan.offline || scan.art === "") return;
|
|
1838
2355
|
const url = `${scan.art}/_themes/${key2}/tokens.css`;
|
|
1839
|
-
let
|
|
2356
|
+
let status2;
|
|
2357
|
+
await readyNetwork();
|
|
1840
2358
|
try {
|
|
1841
2359
|
const reply = await scan.fetchImpl(url, { method: "HEAD" });
|
|
1842
|
-
|
|
2360
|
+
status2 = reply.status;
|
|
1843
2361
|
} catch (err) {
|
|
1844
|
-
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)}`);
|
|
1845
2363
|
return;
|
|
1846
2364
|
}
|
|
1847
|
-
if (
|
|
2365
|
+
if (status2 === 404) {
|
|
1848
2366
|
add(scan, where.file, where.line, "error", `theme ${key2} is not published (${url})`);
|
|
1849
2367
|
return;
|
|
1850
2368
|
}
|
|
1851
|
-
if (
|
|
1852
|
-
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: HTTP ${
|
|
2369
|
+
if (status2 < 200 || status2 > 399) {
|
|
2370
|
+
add(scan, where.file, where.line, "warning", `cannot check theme ${key2}: HTTP ${status2}`);
|
|
1853
2371
|
return;
|
|
1854
2372
|
}
|
|
1855
|
-
if (
|
|
2373
|
+
if (status2 > 299) return;
|
|
1856
2374
|
for (const [rel, ref] of where.files) {
|
|
1857
2375
|
if (rel === "tokens.css") continue;
|
|
1858
2376
|
await checkThemeFile(scan, key2, rel, ref);
|
|
1859
2377
|
}
|
|
1860
2378
|
}
|
|
1861
2379
|
async function checkFolder(input, opts = {}) {
|
|
1862
|
-
const
|
|
1863
|
-
const info = await
|
|
2380
|
+
const root2 = path6.resolve(input);
|
|
2381
|
+
const info = await stat3(root2).catch(() => void 0);
|
|
1864
2382
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
1865
2383
|
const scan = {
|
|
1866
2384
|
files: /* @__PURE__ */ new Set(),
|
|
@@ -1871,7 +2389,7 @@ async function checkFolder(input, opts = {}) {
|
|
|
1871
2389
|
themes: /* @__PURE__ */ new Map()
|
|
1872
2390
|
};
|
|
1873
2391
|
const entries = [];
|
|
1874
|
-
await collect(scan,
|
|
2392
|
+
await collect(scan, root2, "", entries);
|
|
1875
2393
|
let bytes = 0;
|
|
1876
2394
|
for (const entry of entries) {
|
|
1877
2395
|
scan.files.add(entry.rel);
|
|
@@ -1952,7 +2470,7 @@ function reportCheck(out, result) {
|
|
|
1952
2470
|
out.line(summaryLine(result));
|
|
1953
2471
|
}
|
|
1954
2472
|
async function enforceCheck(out, target, opts = {}) {
|
|
1955
|
-
const info = await
|
|
2473
|
+
const info = await stat3(target).catch(() => void 0);
|
|
1956
2474
|
if (info === void 0 || !info.isDirectory()) return;
|
|
1957
2475
|
const result = await checkFolder(target, opts);
|
|
1958
2476
|
if (result.findings.length > 0) reportCheck(out, result);
|
|
@@ -1960,12 +2478,12 @@ async function enforceCheck(out, target, opts = {}) {
|
|
|
1960
2478
|
}
|
|
1961
2479
|
|
|
1962
2480
|
// src/publish.ts
|
|
1963
|
-
import { readFile as
|
|
2481
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
1964
2482
|
|
|
1965
2483
|
// src/files.ts
|
|
1966
2484
|
import { createHash } from "node:crypto";
|
|
1967
|
-
import { readdir as readdir2, readFile as
|
|
1968
|
-
import
|
|
2485
|
+
import { readdir as readdir2, readFile as readFile7, stat as stat4 } from "node:fs/promises";
|
|
2486
|
+
import path7 from "node:path";
|
|
1969
2487
|
var CONTENT_TYPES = {
|
|
1970
2488
|
html: "text/html; charset=utf-8",
|
|
1971
2489
|
css: "text/css; charset=utf-8",
|
|
@@ -1997,14 +2515,14 @@ var CONTENT_TYPES = {
|
|
|
1997
2515
|
var DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
1998
2516
|
var SKIPPED_NAMES2 = /* @__PURE__ */ new Set(["node_modules"]);
|
|
1999
2517
|
function contentTypeFor(filePath) {
|
|
2000
|
-
const ext =
|
|
2518
|
+
const ext = path7.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
2001
2519
|
return CONTENT_TYPES[ext] ?? DEFAULT_CONTENT_TYPE;
|
|
2002
2520
|
}
|
|
2003
2521
|
function sha256Hex(bytes) {
|
|
2004
2522
|
return createHash("sha256").update(bytes).digest("hex");
|
|
2005
2523
|
}
|
|
2006
2524
|
async function readEntry(abs, rel) {
|
|
2007
|
-
const bytes = await
|
|
2525
|
+
const bytes = await readFile7(abs);
|
|
2008
2526
|
return {
|
|
2009
2527
|
path: rel,
|
|
2010
2528
|
file: abs,
|
|
@@ -2013,7 +2531,7 @@ async function readEntry(abs, rel) {
|
|
|
2013
2531
|
type: contentTypeFor(rel)
|
|
2014
2532
|
};
|
|
2015
2533
|
}
|
|
2016
|
-
async function collectPaths(
|
|
2534
|
+
async function collectPaths(root2) {
|
|
2017
2535
|
const found = [];
|
|
2018
2536
|
async function walk2(dir, prefix) {
|
|
2019
2537
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
@@ -2022,7 +2540,7 @@ async function collectPaths(root) {
|
|
|
2022
2540
|
if (entry.name.startsWith(".")) continue;
|
|
2023
2541
|
if (SKIPPED_NAMES2.has(entry.name)) continue;
|
|
2024
2542
|
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
2025
|
-
const abs =
|
|
2543
|
+
const abs = path7.join(dir, entry.name);
|
|
2026
2544
|
if (entry.isDirectory()) {
|
|
2027
2545
|
await walk2(abs, rel);
|
|
2028
2546
|
continue;
|
|
@@ -2031,22 +2549,22 @@ async function collectPaths(root) {
|
|
|
2031
2549
|
found.push(rel);
|
|
2032
2550
|
}
|
|
2033
2551
|
}
|
|
2034
|
-
await walk2(
|
|
2552
|
+
await walk2(root2, "");
|
|
2035
2553
|
return found;
|
|
2036
2554
|
}
|
|
2037
2555
|
async function buildManifest(input, opts = {}) {
|
|
2038
|
-
const
|
|
2039
|
-
const info = await
|
|
2556
|
+
const root2 = path7.resolve(input);
|
|
2557
|
+
const info = await stat4(root2).catch(() => void 0);
|
|
2040
2558
|
if (info === void 0) throw new UsageError(`no such file or folder: ${input}`);
|
|
2041
2559
|
const entries = [];
|
|
2042
2560
|
if (info.isFile()) {
|
|
2043
|
-
entries.push(await readEntry(
|
|
2561
|
+
entries.push(await readEntry(root2, "index.html"));
|
|
2044
2562
|
} else if (info.isDirectory()) {
|
|
2045
|
-
const paths = await collectPaths(
|
|
2563
|
+
const paths = await collectPaths(root2);
|
|
2046
2564
|
if (opts.requireIndex !== false && !paths.includes("index.html")) {
|
|
2047
2565
|
throw new UsageError(`${input} has no index.html`);
|
|
2048
2566
|
}
|
|
2049
|
-
for (const rel of paths) entries.push(await readEntry(
|
|
2567
|
+
for (const rel of paths) entries.push(await readEntry(path7.join(root2, rel), rel));
|
|
2050
2568
|
} else {
|
|
2051
2569
|
throw new UsageError(`not a file or folder: ${input}`);
|
|
2052
2570
|
}
|
|
@@ -2164,7 +2682,7 @@ async function publishTarget(api, target, opts) {
|
|
|
2164
2682
|
let uploaded = 0;
|
|
2165
2683
|
await runPool(missing, UPLOAD_CONCURRENCY, async (hash) => {
|
|
2166
2684
|
const entry = byHash.get(hash);
|
|
2167
|
-
await api.putBlob(hash, await
|
|
2685
|
+
await api.putBlob(hash, await readFile8(entry.file));
|
|
2168
2686
|
uploaded += 1;
|
|
2169
2687
|
});
|
|
2170
2688
|
const version = await api.createVersion(opts.slug, { label: opts.label, files: manifest.files });
|
|
@@ -2192,7 +2710,7 @@ function sameFiles(local, remote) {
|
|
|
2192
2710
|
const paths = Object.keys(local);
|
|
2193
2711
|
const other = remote;
|
|
2194
2712
|
if (paths.length !== Object.keys(other).length) return false;
|
|
2195
|
-
return paths.every((
|
|
2713
|
+
return paths.every((path26) => other[path26]?.hash === local[path26]?.hash);
|
|
2196
2714
|
}
|
|
2197
2715
|
function reportUnchanged(out, result) {
|
|
2198
2716
|
out.data({ unchanged: true, slug: result.slug, version: result.version.n });
|
|
@@ -2220,26 +2738,19 @@ function validateSlug(slug) {
|
|
|
2220
2738
|
return slug;
|
|
2221
2739
|
}
|
|
2222
2740
|
|
|
2223
|
-
// src/warn.ts
|
|
2224
|
-
import process4 from "node:process";
|
|
2225
|
-
function warn2(message) {
|
|
2226
|
-
process4.stderr.write(`rshow: warning: ${message}
|
|
2227
|
-
`);
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
2741
|
// src/commands/publish.ts
|
|
2231
2742
|
async function directoryOf(input) {
|
|
2232
|
-
const dir =
|
|
2233
|
-
const info = await
|
|
2743
|
+
const dir = path8.resolve(input);
|
|
2744
|
+
const info = await stat5(dir).catch(() => void 0);
|
|
2234
2745
|
return info !== void 0 && info.isDirectory() ? dir : void 0;
|
|
2235
2746
|
}
|
|
2236
2747
|
async function targetOf(input, dir, built, out) {
|
|
2237
|
-
if (dir === void 0) return
|
|
2238
|
-
if (built) return
|
|
2748
|
+
if (dir === void 0) return path8.resolve(input);
|
|
2749
|
+
if (built) return path8.resolve(dir, out);
|
|
2239
2750
|
const local = await readConfigFile(localConfigPath(dir));
|
|
2240
|
-
return local.out === void 0 ? dir :
|
|
2751
|
+
return local.out === void 0 ? dir : path8.resolve(dir, out);
|
|
2241
2752
|
}
|
|
2242
|
-
async function
|
|
2753
|
+
async function run(argv, ctx) {
|
|
2243
2754
|
const args = parseCommandArgs(
|
|
2244
2755
|
argv,
|
|
2245
2756
|
{
|
|
@@ -2266,7 +2777,7 @@ async function run2(argv, ctx) {
|
|
|
2266
2777
|
}
|
|
2267
2778
|
const target = await targetOf(input, dir, build !== void 0, config.out);
|
|
2268
2779
|
const slug = validateSlug(
|
|
2269
|
-
stringValue(args, "slug") ?? config.slug ?? deriveSlug(
|
|
2780
|
+
stringValue(args, "slug") ?? config.slug ?? deriveSlug(path8.basename(path8.resolve(input)))
|
|
2270
2781
|
);
|
|
2271
2782
|
const title = stringValue(args, "title") ?? config.title;
|
|
2272
2783
|
const label = stringValue(args, "label");
|
|
@@ -2277,7 +2788,7 @@ async function run2(argv, ctx) {
|
|
|
2277
2788
|
const opts = { slug, title, label, project, tags, force: boolValue(args, "force") };
|
|
2278
2789
|
if (!boolValue(args, "no-check")) {
|
|
2279
2790
|
opts.beforeUpload = () => {
|
|
2280
|
-
if (config.art === void 0)
|
|
2791
|
+
if (config.art === void 0) warn("no art host configured; skipping theme checks");
|
|
2281
2792
|
return enforceCheck(ctx.out, target, { art: config.art, offline: config.art === void 0 });
|
|
2282
2793
|
};
|
|
2283
2794
|
}
|
|
@@ -2301,9 +2812,9 @@ async function run2(argv, ctx) {
|
|
|
2301
2812
|
}
|
|
2302
2813
|
|
|
2303
2814
|
// src/commands/check.ts
|
|
2304
|
-
import { stat as
|
|
2305
|
-
import
|
|
2306
|
-
async function
|
|
2815
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
2816
|
+
import path9 from "node:path";
|
|
2817
|
+
async function run2(argv, ctx) {
|
|
2307
2818
|
const args = parseCommandArgs(
|
|
2308
2819
|
argv,
|
|
2309
2820
|
{ offline: { type: "boolean" }, "no-style": { type: "boolean" } },
|
|
@@ -2312,13 +2823,13 @@ async function run3(argv, ctx) {
|
|
|
2312
2823
|
CHECK_USAGE
|
|
2313
2824
|
);
|
|
2314
2825
|
const input = args.positionals[0];
|
|
2315
|
-
const dir =
|
|
2316
|
-
const info = await
|
|
2826
|
+
const dir = path9.resolve(input);
|
|
2827
|
+
const info = await stat6(dir).catch(() => void 0);
|
|
2317
2828
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
2318
2829
|
const config = await ctx.config(dir);
|
|
2319
2830
|
const offline = boolValue(args, "offline");
|
|
2320
2831
|
const art = config.art;
|
|
2321
|
-
if (art === void 0 && !offline)
|
|
2832
|
+
if (art === void 0 && !offline) warn("no art host configured; skipping theme checks");
|
|
2322
2833
|
const result = await checkFolder(dir, {
|
|
2323
2834
|
art,
|
|
2324
2835
|
offline: offline || art === void 0,
|
|
@@ -2336,21 +2847,21 @@ async function run3(argv, ctx) {
|
|
|
2336
2847
|
}
|
|
2337
2848
|
|
|
2338
2849
|
// src/commands/preview.ts
|
|
2339
|
-
import { stat as
|
|
2340
|
-
import
|
|
2850
|
+
import { stat as stat9 } from "node:fs/promises";
|
|
2851
|
+
import path12 from "node:path";
|
|
2341
2852
|
|
|
2342
2853
|
// src/serve.ts
|
|
2343
2854
|
import { watch } from "node:fs";
|
|
2344
|
-
import { mkdtemp, readFile as
|
|
2855
|
+
import { mkdtemp as mkdtemp2, readFile as readFile10, rm as rm3, stat as stat8, writeFile as writeFile4 } from "node:fs/promises";
|
|
2345
2856
|
import { createHash as createHash2 } from "node:crypto";
|
|
2346
2857
|
import { createServer } from "node:http";
|
|
2347
2858
|
import { tmpdir } from "node:os";
|
|
2348
|
-
import
|
|
2349
|
-
import
|
|
2859
|
+
import path11 from "node:path";
|
|
2860
|
+
import process11 from "node:process";
|
|
2350
2861
|
|
|
2351
2862
|
// src/themes.ts
|
|
2352
|
-
import { cp, mkdir, readFile as
|
|
2353
|
-
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";
|
|
2354
2865
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2355
2866
|
|
|
2356
2867
|
// src/color.ts
|
|
@@ -2682,11 +3193,21 @@ function flattenDtcg(tree) {
|
|
|
2682
3193
|
}
|
|
2683
3194
|
|
|
2684
3195
|
// src/themes.ts
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
3196
|
+
function sourceDir() {
|
|
3197
|
+
try {
|
|
3198
|
+
const url = import.meta.url;
|
|
3199
|
+
return typeof url === "string" && url !== "" ? path10.dirname(fileURLToPath2(url)) : void 0;
|
|
3200
|
+
} catch {
|
|
3201
|
+
return void 0;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
var HERE = sourceDir();
|
|
3205
|
+
function nearSource(...segments) {
|
|
3206
|
+
if (HERE === void 0) return [];
|
|
3207
|
+
return [path10.resolve(HERE, "..", "..", "..", ...segments), path10.resolve(HERE, "..", ...segments)];
|
|
3208
|
+
}
|
|
3209
|
+
var BUILTIN_BASES = nearSource("themes");
|
|
3210
|
+
var BUILTIN_SKILL_BASES = nearSource("skills");
|
|
2690
3211
|
var ROLE_TOKENS = ["--bg", "--fg", "--muted", "--accent", "--layer-01"];
|
|
2691
3212
|
var TEXT_ROLES = ["--fg", "--muted", "--accent"];
|
|
2692
3213
|
var GROUND_TOKENS = ["--bg", "--layer-01"];
|
|
@@ -2701,7 +3222,7 @@ var FONT_FORMATS = {
|
|
|
2701
3222
|
".ttf": "truetype",
|
|
2702
3223
|
".otf": "opentype"
|
|
2703
3224
|
};
|
|
2704
|
-
function
|
|
3225
|
+
function isRecord3(value) {
|
|
2705
3226
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2706
3227
|
}
|
|
2707
3228
|
function stringField(value) {
|
|
@@ -2711,16 +3232,16 @@ function messageOf6(err) {
|
|
|
2711
3232
|
return err instanceof Error ? err.message : String(err);
|
|
2712
3233
|
}
|
|
2713
3234
|
async function isFile(file) {
|
|
2714
|
-
const info = await
|
|
3235
|
+
const info = await stat7(file).catch(() => void 0);
|
|
2715
3236
|
return info !== void 0 && info.isFile();
|
|
2716
3237
|
}
|
|
2717
3238
|
async function isDir(dir) {
|
|
2718
|
-
const info = await
|
|
3239
|
+
const info = await stat7(dir).catch(() => void 0);
|
|
2719
3240
|
return info !== void 0 && info.isDirectory();
|
|
2720
3241
|
}
|
|
2721
3242
|
function tokenMap(value) {
|
|
2722
3243
|
const out = {};
|
|
2723
|
-
if (!
|
|
3244
|
+
if (!isRecord3(value)) return out;
|
|
2724
3245
|
for (const [name, raw] of Object.entries(value)) {
|
|
2725
3246
|
if (typeof raw === "string" && raw.trim() !== "") out[name] = raw.trim();
|
|
2726
3247
|
}
|
|
@@ -2730,7 +3251,7 @@ function fontList(value) {
|
|
|
2730
3251
|
if (!Array.isArray(value)) return [];
|
|
2731
3252
|
const out = [];
|
|
2732
3253
|
for (const item of value) {
|
|
2733
|
-
if (!
|
|
3254
|
+
if (!isRecord3(item)) continue;
|
|
2734
3255
|
const family = stringField(item.family);
|
|
2735
3256
|
const file = stringField(item.file);
|
|
2736
3257
|
if (family === void 0 || file === void 0) continue;
|
|
@@ -2741,8 +3262,8 @@ function fontList(value) {
|
|
|
2741
3262
|
return out;
|
|
2742
3263
|
}
|
|
2743
3264
|
function themeValues(json) {
|
|
2744
|
-
const doc =
|
|
2745
|
-
const modes =
|
|
3265
|
+
const doc = isRecord3(json) ? json : {};
|
|
3266
|
+
const modes = isRecord3(doc.modeTokens) ? doc.modeTokens : {};
|
|
2746
3267
|
return {
|
|
2747
3268
|
tokens: tokenMap(doc.tokens),
|
|
2748
3269
|
light: tokenMap(modes.light),
|
|
@@ -2750,10 +3271,10 @@ function themeValues(json) {
|
|
|
2750
3271
|
};
|
|
2751
3272
|
}
|
|
2752
3273
|
async function readThemeJson(dir) {
|
|
2753
|
-
const file =
|
|
3274
|
+
const file = path10.join(dir, "theme.json");
|
|
2754
3275
|
let text;
|
|
2755
3276
|
try {
|
|
2756
|
-
text = await
|
|
3277
|
+
text = await readFile9(file, "utf8");
|
|
2757
3278
|
} catch (err) {
|
|
2758
3279
|
if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
|
|
2759
3280
|
throw new UsageError(`cannot read ${file}: ${messageOf6(err)}`);
|
|
@@ -2764,17 +3285,23 @@ async function readThemeJson(dir) {
|
|
|
2764
3285
|
} catch {
|
|
2765
3286
|
throw new UsageError(`invalid JSON in ${file}`);
|
|
2766
3287
|
}
|
|
2767
|
-
if (!
|
|
3288
|
+
if (!isRecord3(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
2768
3289
|
return { dir, text, json: parsed };
|
|
2769
3290
|
}
|
|
2770
3291
|
async function builtinThemeDir(name, bases = BUILTIN_BASES) {
|
|
2771
3292
|
if (!THEME_NAME_PATTERN.test(name)) return void 0;
|
|
2772
|
-
for (const base of bases) {
|
|
2773
|
-
const dir =
|
|
3293
|
+
for (const base of [...await binaryThemeBases(), ...bases]) {
|
|
3294
|
+
const dir = path10.join(base, name);
|
|
2774
3295
|
if (await isDir(dir)) return dir;
|
|
2775
3296
|
}
|
|
2776
3297
|
return void 0;
|
|
2777
3298
|
}
|
|
3299
|
+
async function builtinSkillsDir(bases = BUILTIN_SKILL_BASES) {
|
|
3300
|
+
for (const base of [...await binarySkillBases(), ...bases]) {
|
|
3301
|
+
if (await isDir(base)) return base;
|
|
3302
|
+
}
|
|
3303
|
+
return void 0;
|
|
3304
|
+
}
|
|
2778
3305
|
function stripPrint(css) {
|
|
2779
3306
|
let out = "";
|
|
2780
3307
|
let index = 0;
|
|
@@ -2800,8 +3327,8 @@ function stripPrint(css) {
|
|
|
2800
3327
|
return out;
|
|
2801
3328
|
}
|
|
2802
3329
|
async function themeTokenSets(dir) {
|
|
2803
|
-
const file =
|
|
2804
|
-
const text = await
|
|
3330
|
+
const file = path10.join(dir, "tokens.css");
|
|
3331
|
+
const text = await readFile9(file, "utf8").catch(() => void 0);
|
|
2805
3332
|
return extractTokens(text === void 0 ? [] : [stripPrint(text)]);
|
|
2806
3333
|
}
|
|
2807
3334
|
function declMap(list3) {
|
|
@@ -2823,9 +3350,9 @@ function declarations(values, indent) {
|
|
|
2823
3350
|
function presetTokenCss(name, values) {
|
|
2824
3351
|
const parts2 = [];
|
|
2825
3352
|
const light = { ...values.tokens, ...values.light };
|
|
2826
|
-
const
|
|
2827
|
-
if (
|
|
2828
|
-
${
|
|
3353
|
+
const root2 = declarations(light, " ");
|
|
3354
|
+
if (root2 !== "") parts2.push(`:root {
|
|
3355
|
+
${root2}
|
|
2829
3356
|
}`);
|
|
2830
3357
|
const dark = declarations(values.dark, " ");
|
|
2831
3358
|
if (dark !== "") {
|
|
@@ -2854,7 +3381,7 @@ function presetFontCss(fonts) {
|
|
|
2854
3381
|
lines.push(` font-weight: ${font.weight ?? "400"};`);
|
|
2855
3382
|
lines.push(" font-display: swap;");
|
|
2856
3383
|
const url = fontUrl(font.file);
|
|
2857
|
-
const format = FONT_FORMATS[
|
|
3384
|
+
const format = FONT_FORMATS[path10.extname(url).toLowerCase()];
|
|
2858
3385
|
const source = format === void 0 ? `url("${url}")` : `url("${url}") format("${format}")`;
|
|
2859
3386
|
lines.push(` src: ${source};`);
|
|
2860
3387
|
return `@font-face {
|
|
@@ -2879,15 +3406,15 @@ function mergeTheme(base, preset, baseName) {
|
|
|
2879
3406
|
}
|
|
2880
3407
|
async function appendFile(file, addition) {
|
|
2881
3408
|
if (addition === "") return;
|
|
2882
|
-
const before = await
|
|
3409
|
+
const before = await readFile9(file, "utf8").catch(() => "");
|
|
2883
3410
|
const glue = before === "" || before.endsWith("\n") ? "" : "\n";
|
|
2884
|
-
await
|
|
3411
|
+
await writeFile3(file, `${before}${glue}${addition}`);
|
|
2885
3412
|
}
|
|
2886
3413
|
async function buildPreset(presetDir, outDir) {
|
|
2887
3414
|
const preset = await readThemeJson(presetDir);
|
|
2888
3415
|
const baseName = stringField(preset.json.extends);
|
|
2889
|
-
await
|
|
2890
|
-
await
|
|
3416
|
+
await rm2(outDir, { recursive: true, force: true });
|
|
3417
|
+
await mkdir3(outDir, { recursive: true });
|
|
2891
3418
|
if (baseName === void 0) {
|
|
2892
3419
|
await cp(presetDir, outDir, { recursive: true });
|
|
2893
3420
|
return;
|
|
@@ -2898,15 +3425,15 @@ async function buildPreset(presetDir, outDir) {
|
|
|
2898
3425
|
await cp(baseDir, outDir, { recursive: true });
|
|
2899
3426
|
const values = themeValues(preset.json);
|
|
2900
3427
|
const name = stringField(preset.json.name) ?? baseName;
|
|
2901
|
-
await appendFile(
|
|
3428
|
+
await appendFile(path10.join(outDir, "tokens.css"), presetTokenCss(name, values));
|
|
2902
3429
|
const fonts = fontList(preset.json.fonts);
|
|
2903
3430
|
if (fonts.length > 0) {
|
|
2904
|
-
const from =
|
|
2905
|
-
if (await isDir(from)) await cp(from,
|
|
2906
|
-
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));
|
|
2907
3434
|
}
|
|
2908
3435
|
const merged = mergeTheme(base.json, preset.json, baseName);
|
|
2909
|
-
await
|
|
3436
|
+
await writeFile3(path10.join(outDir, "theme.json"), `${JSON.stringify(merged, null, 2)}
|
|
2910
3437
|
`);
|
|
2911
3438
|
}
|
|
2912
3439
|
function keyLine(text, key2, mode) {
|
|
@@ -2921,8 +3448,8 @@ function keyLine(text, key2, mode) {
|
|
|
2921
3448
|
}
|
|
2922
3449
|
return 0;
|
|
2923
3450
|
}
|
|
2924
|
-
function finding(
|
|
2925
|
-
return { path:
|
|
3451
|
+
function finding(path26, line, severity, message) {
|
|
3452
|
+
return { path: path26, line: Math.max(1, line), severity, message };
|
|
2926
3453
|
}
|
|
2927
3454
|
function resolveValue(name, map, fallback) {
|
|
2928
3455
|
let current = map[name] ?? fallback[name];
|
|
@@ -3020,13 +3547,13 @@ async function checkPreset(presetDir) {
|
|
|
3020
3547
|
}
|
|
3021
3548
|
const fonts = fontList(preset.json.fonts);
|
|
3022
3549
|
for (const font of fonts) {
|
|
3023
|
-
const file =
|
|
3550
|
+
const file = path10.join(presetDir, font.file);
|
|
3024
3551
|
if (!await isFile(file)) {
|
|
3025
3552
|
findings.push(finding("theme.json", keyLine(text, "file"), "error", `missing font file: ${font.file}`));
|
|
3026
3553
|
}
|
|
3027
3554
|
}
|
|
3028
3555
|
if (fonts.length > 0) {
|
|
3029
|
-
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))));
|
|
3030
3557
|
if (!found.some(Boolean)) {
|
|
3031
3558
|
findings.push(
|
|
3032
3559
|
finding(
|
|
@@ -3124,7 +3651,7 @@ var SCRIPT_TYPE = "text/javascript; charset=utf-8";
|
|
|
3124
3651
|
function messageOf7(err) {
|
|
3125
3652
|
return err instanceof Error ? err.message : String(err);
|
|
3126
3653
|
}
|
|
3127
|
-
function
|
|
3654
|
+
function isRecord4(value) {
|
|
3128
3655
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3129
3656
|
}
|
|
3130
3657
|
function etagOf(text) {
|
|
@@ -3144,14 +3671,14 @@ function parseValue(text) {
|
|
|
3144
3671
|
function dataLimit(key2) {
|
|
3145
3672
|
return key2.startsWith(SKETCH_DATA_KEY_PREFIX) ? MAX_SKETCH_DATA_BYTES : MAX_DATA_BYTES;
|
|
3146
3673
|
}
|
|
3147
|
-
function jsonReply(res,
|
|
3148
|
-
res.writeHead(
|
|
3674
|
+
function jsonReply(res, status2, body, headers = {}) {
|
|
3675
|
+
res.writeHead(status2, { "content-type": JSON_TYPE, "cache-control": "no-store", ...headers });
|
|
3149
3676
|
res.end(JSON.stringify(body));
|
|
3150
3677
|
}
|
|
3151
|
-
function jsonError(res, message,
|
|
3152
|
-
jsonReply(res,
|
|
3678
|
+
function jsonError(res, message, status2) {
|
|
3679
|
+
jsonReply(res, status2, { error: message });
|
|
3153
3680
|
}
|
|
3154
|
-
async function resolveFile(
|
|
3681
|
+
async function resolveFile(root2, pathname) {
|
|
3155
3682
|
let decoded;
|
|
3156
3683
|
try {
|
|
3157
3684
|
decoded = decodeURIComponent(pathname);
|
|
@@ -3159,25 +3686,26 @@ async function resolveFile(root, pathname) {
|
|
|
3159
3686
|
return void 0;
|
|
3160
3687
|
}
|
|
3161
3688
|
if (decoded.includes("\0")) return void 0;
|
|
3162
|
-
const target =
|
|
3163
|
-
const rel =
|
|
3164
|
-
if (rel.startsWith("..") ||
|
|
3165
|
-
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);
|
|
3166
3693
|
if (info === void 0) return void 0;
|
|
3167
3694
|
if (info.isFile()) return target;
|
|
3168
3695
|
if (!info.isDirectory()) return void 0;
|
|
3169
|
-
const index =
|
|
3170
|
-
const indexInfo = await
|
|
3696
|
+
const index = path11.join(target, "index.html");
|
|
3697
|
+
const indexInfo = await stat8(index).catch(() => void 0);
|
|
3171
3698
|
if (indexInfo === void 0 || !indexInfo.isFile()) return void 0;
|
|
3172
3699
|
return index;
|
|
3173
3700
|
}
|
|
3174
3701
|
async function proxy(art, pathname, method, res) {
|
|
3175
3702
|
let reply;
|
|
3703
|
+
await readyNetwork();
|
|
3176
3704
|
try {
|
|
3177
3705
|
reply = await fetch(art + pathname, { method: method === "HEAD" ? "HEAD" : "GET" });
|
|
3178
3706
|
} catch (err) {
|
|
3179
3707
|
res.writeHead(502, { "content-type": TEXT_TYPE, "x-rshow-source": "proxy" });
|
|
3180
|
-
res.end(`proxy error: ${
|
|
3708
|
+
res.end(`proxy error: ${networkText(err, art + pathname)}
|
|
3181
3709
|
`);
|
|
3182
3710
|
return;
|
|
3183
3711
|
}
|
|
@@ -3193,10 +3721,10 @@ async function proxy(art, pathname, method, res) {
|
|
|
3193
3721
|
async function prepareTheme(dir) {
|
|
3194
3722
|
const doc = await readThemeJson(dir);
|
|
3195
3723
|
const declared = typeof doc.json.name === "string" ? doc.json.name.trim() : "";
|
|
3196
|
-
const name = declared === "" ?
|
|
3724
|
+
const name = declared === "" ? path11.basename(dir) : declared;
|
|
3197
3725
|
const base = typeof doc.json.extends === "string" ? doc.json.extends.trim() : "";
|
|
3198
3726
|
if (base === "") return { name, source: dir, built: dir, label: "local" };
|
|
3199
|
-
const built = await
|
|
3727
|
+
const built = await mkdtemp2(path11.join(tmpdir(), "rshow-theme-"));
|
|
3200
3728
|
await buildPreset(dir, built);
|
|
3201
3729
|
return { name, source: dir, built, label: "preset" };
|
|
3202
3730
|
}
|
|
@@ -3267,9 +3795,9 @@ function createDataStore() {
|
|
|
3267
3795
|
const values = /* @__PURE__ */ new Map();
|
|
3268
3796
|
let dir;
|
|
3269
3797
|
const persist = async () => {
|
|
3270
|
-
if (dir === void 0) dir = await
|
|
3798
|
+
if (dir === void 0) dir = await mkdtemp2(path11.join(tmpdir(), "rshow-data-"));
|
|
3271
3799
|
const doc = Object.fromEntries(values);
|
|
3272
|
-
await
|
|
3800
|
+
await writeFile4(path11.join(dir, "data.json"), `${JSON.stringify(doc, null, 2)}
|
|
3273
3801
|
`);
|
|
3274
3802
|
};
|
|
3275
3803
|
return {
|
|
@@ -3283,7 +3811,7 @@ function createDataStore() {
|
|
|
3283
3811
|
},
|
|
3284
3812
|
close: async () => {
|
|
3285
3813
|
if (dir === void 0) return;
|
|
3286
|
-
await
|
|
3814
|
+
await rm3(dir, { recursive: true, force: true });
|
|
3287
3815
|
dir = void 0;
|
|
3288
3816
|
}
|
|
3289
3817
|
};
|
|
@@ -3315,9 +3843,9 @@ async function readBody(req, limit) {
|
|
|
3315
3843
|
function valueText(value) {
|
|
3316
3844
|
return JSON.stringify(value === void 0 ? null : value);
|
|
3317
3845
|
}
|
|
3318
|
-
async function handleData(req, res,
|
|
3846
|
+
async function handleData(req, res, store2, key2, method) {
|
|
3319
3847
|
if (method === "GET" || method === "HEAD") {
|
|
3320
|
-
const entry =
|
|
3848
|
+
const entry = store2.get(key2);
|
|
3321
3849
|
if (entry === void 0) {
|
|
3322
3850
|
jsonError(res, "not found", 404);
|
|
3323
3851
|
return;
|
|
@@ -3358,12 +3886,12 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3358
3886
|
jsonError(res, "expected a JSON body", 400);
|
|
3359
3887
|
return;
|
|
3360
3888
|
}
|
|
3361
|
-
if (!
|
|
3889
|
+
if (!isRecord4(parsed) || !("value" in parsed)) {
|
|
3362
3890
|
jsonError(res, "expected {value}", 400);
|
|
3363
3891
|
return;
|
|
3364
3892
|
}
|
|
3365
3893
|
const text = valueText(parsed.value);
|
|
3366
|
-
const existing =
|
|
3894
|
+
const existing = store2.get(key2);
|
|
3367
3895
|
const ifMatch = req.headers["if-match"] ?? null;
|
|
3368
3896
|
if (ifMatch !== null && ifMatch.trim() !== "") {
|
|
3369
3897
|
if (existing === void 0 || !etagMatches(ifMatch, etagOf(existing.text))) {
|
|
@@ -3371,7 +3899,7 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3371
3899
|
return;
|
|
3372
3900
|
}
|
|
3373
3901
|
}
|
|
3374
|
-
if (existing === void 0 &&
|
|
3902
|
+
if (existing === void 0 && store2.count() >= MAX_DATA_KEYS) {
|
|
3375
3903
|
jsonError(res, "too many keys for this artifact", 409);
|
|
3376
3904
|
return;
|
|
3377
3905
|
}
|
|
@@ -3379,7 +3907,7 @@ async function handleData(req, res, store, key2, method) {
|
|
|
3379
3907
|
jsonError(res, "value too large", 413);
|
|
3380
3908
|
return;
|
|
3381
3909
|
}
|
|
3382
|
-
const written = await
|
|
3910
|
+
const written = await store2.write(key2, text);
|
|
3383
3911
|
jsonReply(res, 200, { ok: true, key: key2, updated_at: written.updated_at }, { etag: written.etag });
|
|
3384
3912
|
}
|
|
3385
3913
|
function subscribe(res, session) {
|
|
@@ -3410,7 +3938,7 @@ function injectReload(html) {
|
|
|
3410
3938
|
${html.slice(at)}`;
|
|
3411
3939
|
}
|
|
3412
3940
|
async function sendFile(res, method, session, file, headers = {}) {
|
|
3413
|
-
const bytes = await
|
|
3941
|
+
const bytes = await readFile10(file);
|
|
3414
3942
|
const type = contentTypeFor(file);
|
|
3415
3943
|
const all = { "content-type": type, "cache-control": "no-store", ...headers };
|
|
3416
3944
|
if (method === "HEAD") {
|
|
@@ -3435,7 +3963,7 @@ function startWatchers(session, targets) {
|
|
|
3435
3963
|
const done = rebuild ? buildPreset(theme.source, theme.built) : Promise.resolve();
|
|
3436
3964
|
done.then(
|
|
3437
3965
|
() => broadcast(session),
|
|
3438
|
-
(err) =>
|
|
3966
|
+
(err) => warn(`cannot rebuild ${target.dir}: ${messageOf7(err)}`)
|
|
3439
3967
|
);
|
|
3440
3968
|
}, DEBOUNCE_MS);
|
|
3441
3969
|
timer.unref();
|
|
@@ -3445,7 +3973,7 @@ function startWatchers(session, targets) {
|
|
|
3445
3973
|
watcher.on("error", () => void 0);
|
|
3446
3974
|
watchers.push(watcher);
|
|
3447
3975
|
} catch (err) {
|
|
3448
|
-
|
|
3976
|
+
warn(`cannot watch ${target.dir}: ${messageOf7(err)}`);
|
|
3449
3977
|
}
|
|
3450
3978
|
}
|
|
3451
3979
|
return watchers;
|
|
@@ -3488,19 +4016,19 @@ async function handle(req, res, session) {
|
|
|
3488
4016
|
}
|
|
3489
4017
|
function closeOnSignals(preview2) {
|
|
3490
4018
|
const stop = () => {
|
|
3491
|
-
preview2.close().catch(() => void 0).finally(() =>
|
|
4019
|
+
preview2.close().catch(() => void 0).finally(() => process11.exit(0));
|
|
3492
4020
|
};
|
|
3493
|
-
|
|
3494
|
-
|
|
4021
|
+
process11.once("SIGINT", stop);
|
|
4022
|
+
process11.once("SIGTERM", stop);
|
|
3495
4023
|
}
|
|
3496
4024
|
async function startPreview(opts) {
|
|
3497
|
-
const dir =
|
|
4025
|
+
const dir = path11.resolve(opts.dir);
|
|
3498
4026
|
const art = (opts.art ?? "").replace(/\/+$/, "");
|
|
3499
4027
|
const host = opts.host ?? "127.0.0.1";
|
|
3500
|
-
const theme = opts.theme === void 0 ? void 0 : await prepareTheme(
|
|
3501
|
-
const
|
|
4028
|
+
const theme = opts.theme === void 0 ? void 0 : await prepareTheme(path11.resolve(opts.theme));
|
|
4029
|
+
const root2 = opts.root === "theme" && theme !== void 0 ? theme.built : dir;
|
|
3502
4030
|
const session = {
|
|
3503
|
-
root,
|
|
4031
|
+
root: root2,
|
|
3504
4032
|
art,
|
|
3505
4033
|
offline: opts.offline === true,
|
|
3506
4034
|
reload: opts.reload !== false,
|
|
@@ -3509,8 +4037,8 @@ async function startPreview(opts) {
|
|
|
3509
4037
|
clients: /* @__PURE__ */ new Set(),
|
|
3510
4038
|
builtin: /* @__PURE__ */ new Map()
|
|
3511
4039
|
};
|
|
3512
|
-
const targets = [{ dir:
|
|
3513
|
-
if (theme !== void 0 && theme.source !==
|
|
4040
|
+
const targets = [{ dir: root2, rebuild: false }];
|
|
4041
|
+
if (theme !== void 0 && theme.source !== root2) targets.push({ dir: theme.source, rebuild: true });
|
|
3514
4042
|
const watchers = startWatchers(session, targets);
|
|
3515
4043
|
const server = createServer((req, res) => {
|
|
3516
4044
|
handle(req, res, session).catch((err) => {
|
|
@@ -3525,7 +4053,7 @@ async function startPreview(opts) {
|
|
|
3525
4053
|
session.clients.clear();
|
|
3526
4054
|
await session.store.close();
|
|
3527
4055
|
if (theme !== void 0 && theme.built !== theme.source) {
|
|
3528
|
-
await
|
|
4056
|
+
await rm3(theme.built, { recursive: true, force: true });
|
|
3529
4057
|
}
|
|
3530
4058
|
};
|
|
3531
4059
|
try {
|
|
@@ -3565,12 +4093,12 @@ function portOf(value) {
|
|
|
3565
4093
|
return port;
|
|
3566
4094
|
}
|
|
3567
4095
|
async function requireFolder(input, what) {
|
|
3568
|
-
const dir =
|
|
3569
|
-
const info = await
|
|
4096
|
+
const dir = path12.resolve(input);
|
|
4097
|
+
const info = await stat9(dir).catch(() => void 0);
|
|
3570
4098
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such ${what}: ${input}`);
|
|
3571
4099
|
return dir;
|
|
3572
4100
|
}
|
|
3573
|
-
async function
|
|
4101
|
+
async function run3(argv, ctx) {
|
|
3574
4102
|
const args = parseCommandArgs(
|
|
3575
4103
|
argv,
|
|
3576
4104
|
{
|
|
@@ -3589,7 +4117,7 @@ async function run4(argv, ctx) {
|
|
|
3589
4117
|
const offline = boolValue(args, "offline");
|
|
3590
4118
|
const port = portOf(stringValue(args, "port"));
|
|
3591
4119
|
const config = await ctx.config(dir);
|
|
3592
|
-
if (config.art === void 0 && !offline)
|
|
4120
|
+
if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
|
|
3593
4121
|
const preview2 = await startPreview({
|
|
3594
4122
|
dir,
|
|
3595
4123
|
art: offline ? "" : config.art ?? "",
|
|
@@ -3613,7 +4141,14 @@ function themeRefOf(theme) {
|
|
|
3613
4141
|
return { name: theme.slice(0, at), version: theme.slice(at + 1) };
|
|
3614
4142
|
}
|
|
3615
4143
|
async function fetchVersion(art, name) {
|
|
3616
|
-
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
|
+
}
|
|
3617
4152
|
if (reply.status !== 200) throw new Error(`HTTP ${reply.status}`);
|
|
3618
4153
|
const body = await reply.json();
|
|
3619
4154
|
const version = body?.version;
|
|
@@ -3632,17 +4167,17 @@ async function resolveTheme2(config) {
|
|
|
3632
4167
|
const ref = themeRefOf(theme);
|
|
3633
4168
|
if (ref.version !== void 0 && ref.version !== "latest") return config;
|
|
3634
4169
|
if (config.art === void 0) {
|
|
3635
|
-
|
|
4170
|
+
warn("no art host configured; skipping theme version lookup");
|
|
3636
4171
|
return config;
|
|
3637
4172
|
}
|
|
3638
4173
|
try {
|
|
3639
4174
|
return { ...config, theme: await resolvedTheme(config.art, theme) };
|
|
3640
4175
|
} catch (err) {
|
|
3641
|
-
|
|
4176
|
+
warn(`cannot resolve theme ${ref.name}: ${messageOf8(err)}`);
|
|
3642
4177
|
return config.theme === void 0 ? config : { ...config, theme: ref.name };
|
|
3643
4178
|
}
|
|
3644
4179
|
}
|
|
3645
|
-
async function
|
|
4180
|
+
async function run4(argv, ctx) {
|
|
3646
4181
|
const args = parseCommandArgs(argv, { offline: { type: "boolean" } }, 0, 1, CONFIG_USAGE);
|
|
3647
4182
|
const dir = args.positionals[0];
|
|
3648
4183
|
const loaded = await ctx.config(dir);
|
|
@@ -3652,14 +4187,14 @@ async function run5(argv, ctx) {
|
|
|
3652
4187
|
}
|
|
3653
4188
|
|
|
3654
4189
|
// src/commands/init.ts
|
|
3655
|
-
import { mkdir as
|
|
3656
|
-
import
|
|
3657
|
-
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";
|
|
3658
4193
|
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost"]);
|
|
3659
4194
|
function messageOf9(err) {
|
|
3660
4195
|
return err instanceof Error ? err.message : String(err);
|
|
3661
4196
|
}
|
|
3662
|
-
function
|
|
4197
|
+
function isRecord5(value) {
|
|
3663
4198
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3664
4199
|
}
|
|
3665
4200
|
function serverUrl(value, flag) {
|
|
@@ -3681,16 +4216,16 @@ function serverUrl(value, flag) {
|
|
|
3681
4216
|
async function rawConfig(file) {
|
|
3682
4217
|
let text;
|
|
3683
4218
|
try {
|
|
3684
|
-
text = await
|
|
4219
|
+
text = await readFile11(file, "utf8");
|
|
3685
4220
|
} catch (err) {
|
|
3686
4221
|
if (err.code === "ENOENT") return {};
|
|
3687
4222
|
throw new UsageError(`cannot read ${file}: ${messageOf9(err)}`);
|
|
3688
4223
|
}
|
|
3689
4224
|
const parsed = JSON.parse(text);
|
|
3690
|
-
if (!
|
|
4225
|
+
if (!isRecord5(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
3691
4226
|
return parsed;
|
|
3692
4227
|
}
|
|
3693
|
-
async function
|
|
4228
|
+
async function run5(argv, ctx) {
|
|
3694
4229
|
const args = parseCommandArgs(
|
|
3695
4230
|
argv,
|
|
3696
4231
|
{
|
|
@@ -3706,7 +4241,7 @@ async function run6(argv, ctx) {
|
|
|
3706
4241
|
const url = serverUrl(stringValue(args, "url"), "url");
|
|
3707
4242
|
const art = serverUrl(stringValue(args, "art"), "art");
|
|
3708
4243
|
const theme = (stringValue(args, "theme") ?? "").trim();
|
|
3709
|
-
const file = globalConfigPath(
|
|
4244
|
+
const file = globalConfigPath(process12.env);
|
|
3710
4245
|
const current = await readConfigFile(file);
|
|
3711
4246
|
const same = (have, want) => have === void 0 || have.replace(/\/+$/, "") === want;
|
|
3712
4247
|
if (!boolValue(args, "force")) {
|
|
@@ -3721,8 +4256,8 @@ async function run6(argv, ctx) {
|
|
|
3721
4256
|
raw.url = url;
|
|
3722
4257
|
raw.art = art;
|
|
3723
4258
|
if (theme !== "") raw.theme = theme;
|
|
3724
|
-
await
|
|
3725
|
-
await
|
|
4259
|
+
await mkdir4(path13.dirname(file), { recursive: true });
|
|
4260
|
+
await writeFile5(file, `${JSON.stringify(raw, null, 2)}
|
|
3726
4261
|
`, "utf8");
|
|
3727
4262
|
ctx.out.data({ path: file, url, art });
|
|
3728
4263
|
ctx.out.line(`wrote ${file}`);
|
|
@@ -3730,24 +4265,226 @@ async function run6(argv, ctx) {
|
|
|
3730
4265
|
}
|
|
3731
4266
|
|
|
3732
4267
|
// src/commands/doctor.ts
|
|
3733
|
-
import { readFile as
|
|
3734
|
-
import
|
|
3735
|
-
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";
|
|
3736
4271
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
4272
|
+
|
|
4273
|
+
// src/skills.ts
|
|
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";
|
|
4277
|
+
var SKILL_TARGETS = ["claude", "pi", "codex"];
|
|
4278
|
+
var SKILL_MARKER = ".rshow-skill";
|
|
4279
|
+
var SKILL_SOURCE = "rshow";
|
|
4280
|
+
var TARGET_SEGMENTS = {
|
|
4281
|
+
claude: [".claude"],
|
|
4282
|
+
pi: [".pi", "agent"],
|
|
4283
|
+
codex: [".codex"]
|
|
4284
|
+
};
|
|
4285
|
+
async function isDir2(dir) {
|
|
4286
|
+
const info = await stat10(dir).catch(() => void 0);
|
|
4287
|
+
return info !== void 0 && info.isDirectory();
|
|
4288
|
+
}
|
|
4289
|
+
async function isFile2(file) {
|
|
4290
|
+
const info = await stat10(file).catch(() => void 0);
|
|
4291
|
+
return info !== void 0 && info.isFile();
|
|
4292
|
+
}
|
|
4293
|
+
function isUnder(file, dir) {
|
|
4294
|
+
const rel = path14.relative(dir, file);
|
|
4295
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${path14.sep}`) && !path14.isAbsolute(rel);
|
|
4296
|
+
}
|
|
4297
|
+
function homeDir(env) {
|
|
4298
|
+
const home = (env.HOME ?? "").trim();
|
|
4299
|
+
return home !== "" ? home : homedir4();
|
|
4300
|
+
}
|
|
4301
|
+
function targetParent(home, target) {
|
|
4302
|
+
return path14.join(home, ...TARGET_SEGMENTS[target]);
|
|
4303
|
+
}
|
|
4304
|
+
function targetSkillsDir(home, target) {
|
|
4305
|
+
return path14.join(targetParent(home, target), "skills");
|
|
4306
|
+
}
|
|
4307
|
+
async function detectedTargets(home) {
|
|
4308
|
+
const out = [];
|
|
4309
|
+
for (const target of SKILL_TARGETS) {
|
|
4310
|
+
if (await isDir2(targetParent(home, target))) out.push(target);
|
|
4311
|
+
}
|
|
4312
|
+
return out;
|
|
4313
|
+
}
|
|
4314
|
+
function parseTargets(names) {
|
|
4315
|
+
const out = [];
|
|
4316
|
+
for (const name of names) {
|
|
4317
|
+
const target = SKILL_TARGETS.find((item) => item === name);
|
|
4318
|
+
if (target === void 0) {
|
|
4319
|
+
throw new UsageError(`unknown target: ${name} (use ${SKILL_TARGETS.join(", ")})`);
|
|
4320
|
+
}
|
|
4321
|
+
if (!out.includes(target)) out.push(target);
|
|
4322
|
+
}
|
|
4323
|
+
return out;
|
|
4324
|
+
}
|
|
4325
|
+
function targetHint() {
|
|
4326
|
+
return SKILL_TARGETS.join(", ");
|
|
4327
|
+
}
|
|
4328
|
+
function defaultModeOf(dir) {
|
|
4329
|
+
return dir.split(path14.sep).join("/").includes("/_npx/") ? "copy" : "link";
|
|
4330
|
+
}
|
|
4331
|
+
async function skillsSource(env) {
|
|
4332
|
+
const override = (env.RSHOW_SKILLS_DIR ?? "").trim();
|
|
4333
|
+
let dir;
|
|
4334
|
+
if (override !== "") {
|
|
4335
|
+
dir = path14.resolve(override);
|
|
4336
|
+
if (!await isDir2(dir)) throw new UsageError(`RSHOW_SKILLS_DIR is not a folder: ${dir}`);
|
|
4337
|
+
} else {
|
|
4338
|
+
dir = await builtinSkillsDir();
|
|
4339
|
+
}
|
|
4340
|
+
if (dir === void 0) return void 0;
|
|
4341
|
+
const skills = [];
|
|
4342
|
+
for (const entry of await readdir3(dir, { withFileTypes: true }).catch(() => [])) {
|
|
4343
|
+
if (entry.isDirectory() && await isFile2(path14.join(dir, entry.name, "SKILL.md"))) skills.push(entry.name);
|
|
4344
|
+
}
|
|
4345
|
+
skills.sort();
|
|
4346
|
+
return { dir, mode: await isBinary() ? "copy" : defaultModeOf(dir), skills };
|
|
4347
|
+
}
|
|
4348
|
+
function isOurLink(target, source) {
|
|
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("/"));
|
|
4352
|
+
}
|
|
4353
|
+
async function linkTargetOf(dst) {
|
|
4354
|
+
const value = await readlink(dst);
|
|
4355
|
+
return path14.isAbsolute(value) ? value : path14.resolve(path14.dirname(dst), value);
|
|
4356
|
+
}
|
|
4357
|
+
async function readMarker(dir) {
|
|
4358
|
+
const text = await readFile12(path14.join(dir, SKILL_MARKER), "utf8").catch(() => void 0);
|
|
4359
|
+
if (text === void 0) return void 0;
|
|
4360
|
+
let parsed;
|
|
4361
|
+
try {
|
|
4362
|
+
parsed = JSON.parse(text);
|
|
4363
|
+
} catch {
|
|
4364
|
+
return void 0;
|
|
4365
|
+
}
|
|
4366
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
4367
|
+
const doc = parsed;
|
|
4368
|
+
if (doc.source !== SKILL_SOURCE || typeof doc.version !== "string" || doc.version === "") return void 0;
|
|
4369
|
+
return { version: doc.version, source: SKILL_SOURCE };
|
|
4370
|
+
}
|
|
4371
|
+
async function inspectSkill(dst, source, skill) {
|
|
4372
|
+
const info = await lstat(dst).catch(() => void 0);
|
|
4373
|
+
if (info === void 0) return { kind: "missing" };
|
|
4374
|
+
if (info.isSymbolicLink()) {
|
|
4375
|
+
const link = await linkTargetOf(dst);
|
|
4376
|
+
if (!isOurLink(link, source)) return { kind: "foreign" };
|
|
4377
|
+
return { kind: "link", path: link, current: path14.resolve(link) === path14.join(path14.resolve(source), skill) };
|
|
4378
|
+
}
|
|
4379
|
+
if (info.isDirectory()) {
|
|
4380
|
+
const marker = await readMarker(dst);
|
|
4381
|
+
return marker === void 0 ? { kind: "foreign" } : { kind: "copy", version: marker.version };
|
|
4382
|
+
}
|
|
4383
|
+
return { kind: "foreign" };
|
|
4384
|
+
}
|
|
4385
|
+
function skillIsCurrent(state, version) {
|
|
4386
|
+
if (state.kind === "link") return true;
|
|
4387
|
+
if (state.kind === "copy") return state.version === version;
|
|
4388
|
+
return false;
|
|
4389
|
+
}
|
|
4390
|
+
function skillStateLabel(state) {
|
|
4391
|
+
switch (state.kind) {
|
|
4392
|
+
case "missing":
|
|
4393
|
+
return "missing";
|
|
4394
|
+
case "foreign":
|
|
4395
|
+
return "foreign";
|
|
4396
|
+
case "link":
|
|
4397
|
+
return `link -> ${state.path}`;
|
|
4398
|
+
case "copy":
|
|
4399
|
+
return `copy v${state.version}`;
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
function actionFor(state, version) {
|
|
4403
|
+
if (state.kind === "missing") return "installed";
|
|
4404
|
+
if (state.kind === "foreign") return "skip";
|
|
4405
|
+
if (state.kind === "link") return state.current ? "ok" : "updated";
|
|
4406
|
+
return state.version === version ? "ok" : "updated";
|
|
4407
|
+
}
|
|
4408
|
+
async function writeSkill(dst, src, mode, version) {
|
|
4409
|
+
if (mode === "link") {
|
|
4410
|
+
await symlink(src, dst);
|
|
4411
|
+
return;
|
|
4412
|
+
}
|
|
4413
|
+
await cp2(src, dst, { recursive: true });
|
|
4414
|
+
const marker = { version, source: SKILL_SOURCE };
|
|
4415
|
+
await writeFile6(path14.join(dst, SKILL_MARKER), `${JSON.stringify(marker, null, 2)}
|
|
4416
|
+
`, "utf8");
|
|
4417
|
+
}
|
|
4418
|
+
async function installSkills(options) {
|
|
4419
|
+
const entries = [];
|
|
4420
|
+
for (const target of options.targets) {
|
|
4421
|
+
const dir = targetSkillsDir(options.home, target);
|
|
4422
|
+
if (!options.dryRun) await mkdir5(dir, { recursive: true });
|
|
4423
|
+
for (const skill of options.source.skills) {
|
|
4424
|
+
const dst = path14.join(dir, skill);
|
|
4425
|
+
const src = path14.join(options.source.dir, skill);
|
|
4426
|
+
const state = await inspectSkill(dst, options.source.dir, skill);
|
|
4427
|
+
const action = actionFor(state, options.version);
|
|
4428
|
+
if (!options.dryRun && (action === "installed" || action === "updated")) {
|
|
4429
|
+
if (action === "updated") await rm4(dst, { recursive: true, force: true });
|
|
4430
|
+
await writeSkill(dst, src, options.mode, options.version);
|
|
4431
|
+
}
|
|
4432
|
+
entries.push({ target, skill, action, path: dst });
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
return entries;
|
|
4436
|
+
}
|
|
4437
|
+
async function uninstallSkills(options) {
|
|
4438
|
+
const entries = [];
|
|
4439
|
+
for (const target of options.targets) {
|
|
4440
|
+
const dir = targetSkillsDir(options.home, target);
|
|
4441
|
+
for (const skill of (await readdir3(dir).catch(() => [])).sort()) {
|
|
4442
|
+
const dst = path14.join(dir, skill);
|
|
4443
|
+
const state = await inspectSkill(dst, options.source.dir, skill);
|
|
4444
|
+
if (state.kind !== "link" && state.kind !== "copy") continue;
|
|
4445
|
+
if (!options.dryRun) await rm4(dst, { recursive: true, force: true });
|
|
4446
|
+
entries.push({ target, skill, action: "removed", path: dst });
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
return entries;
|
|
4450
|
+
}
|
|
4451
|
+
async function skillsStatus(options) {
|
|
4452
|
+
const out = [];
|
|
4453
|
+
for (const target of options.targets) {
|
|
4454
|
+
const dir = targetSkillsDir(options.home, target);
|
|
4455
|
+
for (const skill of options.source.skills) {
|
|
4456
|
+
const dst = path14.join(dir, skill);
|
|
4457
|
+
out.push({ target, skill, state: await inspectSkill(dst, options.source.dir, skill), path: dst });
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
return out;
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
// src/commands/doctor.ts
|
|
3737
4464
|
var TIMEOUT_MS = 5e3;
|
|
3738
4465
|
function messageOf10(err) {
|
|
3739
4466
|
return err instanceof Error ? err.message : String(err);
|
|
3740
4467
|
}
|
|
3741
|
-
async function
|
|
3742
|
-
const info = await
|
|
4468
|
+
async function isFile3(file) {
|
|
4469
|
+
const info = await stat11(file).catch(() => void 0);
|
|
3743
4470
|
return info !== void 0 && info.isFile();
|
|
3744
4471
|
}
|
|
4472
|
+
function sourceDir2() {
|
|
4473
|
+
try {
|
|
4474
|
+
const url = import.meta.url;
|
|
4475
|
+
return typeof url === "string" && url !== "" ? path15.dirname(fileURLToPath3(url)) : void 0;
|
|
4476
|
+
} catch {
|
|
4477
|
+
return void 0;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
3745
4480
|
async function packageJsonPath() {
|
|
3746
|
-
|
|
4481
|
+
const start = sourceDir2();
|
|
4482
|
+
if (start === void 0) return void 0;
|
|
4483
|
+
let dir = start;
|
|
3747
4484
|
for (let i = 0; i < 6; i += 1) {
|
|
3748
|
-
const file =
|
|
3749
|
-
if (await
|
|
3750
|
-
const up =
|
|
4485
|
+
const file = path15.join(dir, "package.json");
|
|
4486
|
+
if (await isFile3(file)) return file;
|
|
4487
|
+
const up = path15.dirname(dir);
|
|
3751
4488
|
if (up === dir) break;
|
|
3752
4489
|
dir = up;
|
|
3753
4490
|
}
|
|
@@ -3771,36 +4508,41 @@ function minimumOf(range) {
|
|
|
3771
4508
|
}
|
|
3772
4509
|
async function nodeCheck() {
|
|
3773
4510
|
const name = "node";
|
|
3774
|
-
const current =
|
|
4511
|
+
const current = process13.versions.node;
|
|
3775
4512
|
const file = await packageJsonPath();
|
|
3776
|
-
if (file === void 0)
|
|
4513
|
+
if (file === void 0) {
|
|
4514
|
+
if (await isBinary()) return { name, status: "ok", detail: `v${current} (inside the binary)` };
|
|
4515
|
+
return { name, status: "warn", detail: `v${current} (package.json not found)` };
|
|
4516
|
+
}
|
|
3777
4517
|
let range;
|
|
3778
4518
|
try {
|
|
3779
|
-
const body = JSON.parse(await
|
|
4519
|
+
const body = JSON.parse(await readFile13(file, "utf8"));
|
|
3780
4520
|
range = typeof body.engines?.node === "string" ? body.engines.node : "";
|
|
3781
4521
|
} catch (err) {
|
|
3782
4522
|
return { name, status: "warn", detail: `v${current} (${messageOf10(err)})` };
|
|
3783
4523
|
}
|
|
3784
4524
|
const needed = minimumOf(range);
|
|
3785
4525
|
if (needed === void 0) return { name, status: "warn", detail: `v${current} (no engines.node)` };
|
|
3786
|
-
const
|
|
3787
|
-
return { name, status, detail: `v${current} (needs ${range})` };
|
|
4526
|
+
const status2 = atLeast(versionParts(current), needed) ? "ok" : "warn";
|
|
4527
|
+
return { name, status: status2, detail: `v${current} (needs ${range})` };
|
|
3788
4528
|
}
|
|
3789
4529
|
async function tokenOf(url) {
|
|
3790
|
-
const env = (
|
|
4530
|
+
const env = (process13.env.RSHOW_TOKEN ?? "").trim();
|
|
3791
4531
|
if (env !== "") return { token: env, source: "env" };
|
|
3792
|
-
if (url === void 0) return {
|
|
4532
|
+
if (url === void 0) return {};
|
|
3793
4533
|
try {
|
|
3794
|
-
const
|
|
3795
|
-
|
|
3796
|
-
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 {};
|
|
3797
4538
|
} catch (err) {
|
|
3798
|
-
return {
|
|
4539
|
+
return { error: messageOf10(err) };
|
|
3799
4540
|
}
|
|
3800
4541
|
}
|
|
3801
4542
|
async function getJson(url, token) {
|
|
3802
4543
|
const headers = {};
|
|
3803
4544
|
if (token !== void 0) headers.authorization = `Bearer ${token}`;
|
|
4545
|
+
await readyNetwork();
|
|
3804
4546
|
const reply = await fetch(url, { headers, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
3805
4547
|
if (reply.status !== 200) return { status: reply.status };
|
|
3806
4548
|
const body = await reply.json().catch(() => void 0);
|
|
@@ -3826,7 +4568,7 @@ function tokenCheck(token) {
|
|
|
3826
4568
|
const reason = token.error === void 0 ? "" : ` (${token.error})`;
|
|
3827
4569
|
return { name, status: "fail", detail: `none${reason}: run rshow login, or set RSHOW_TOKEN` };
|
|
3828
4570
|
}
|
|
3829
|
-
return { name, status: "ok", detail: `source: ${token.source}` };
|
|
4571
|
+
return { name, status: "ok", detail: `source: ${token.source ?? "none"}` };
|
|
3830
4572
|
}
|
|
3831
4573
|
async function apiCheck(url, token) {
|
|
3832
4574
|
const name = "api";
|
|
@@ -3840,7 +4582,7 @@ async function apiCheck(url, token) {
|
|
|
3840
4582
|
const scope = typeof body.scope === "string" ? body.scope : "";
|
|
3841
4583
|
return { name, status: "ok", detail: `${email} (scope ${scope})` };
|
|
3842
4584
|
} catch (err) {
|
|
3843
|
-
return { name, status: "fail", detail:
|
|
4585
|
+
return { name, status: "fail", detail: networkDetail(err, `${url}/api/me`) };
|
|
3844
4586
|
}
|
|
3845
4587
|
}
|
|
3846
4588
|
async function artCheck(art) {
|
|
@@ -3855,7 +4597,7 @@ async function artCheck(art) {
|
|
|
3855
4597
|
}
|
|
3856
4598
|
return { name, status: "ok", detail: `version ${version}` };
|
|
3857
4599
|
} catch (err) {
|
|
3858
|
-
return { name, status: "warn", detail:
|
|
4600
|
+
return { name, status: "warn", detail: networkDetail(err, `${art}/_themes/default.json`) };
|
|
3859
4601
|
}
|
|
3860
4602
|
}
|
|
3861
4603
|
async function themeCheck(config) {
|
|
@@ -3872,12 +4614,28 @@ async function themeCheck(config) {
|
|
|
3872
4614
|
return { name, status: "warn", detail: messageOf10(err) };
|
|
3873
4615
|
}
|
|
3874
4616
|
}
|
|
3875
|
-
async function
|
|
4617
|
+
async function skillsCheck() {
|
|
4618
|
+
const name = "skills";
|
|
4619
|
+
const home = homeDir(process13.env);
|
|
4620
|
+
const targets = await detectedTargets(home);
|
|
4621
|
+
if (targets.length === 0) return { name, status: "ok", detail: "no target detected" };
|
|
4622
|
+
const source = await skillsSource(process13.env).catch(() => void 0);
|
|
4623
|
+
if (source === void 0) return { name, status: "warn", detail: "no skills folder next to rshow" };
|
|
4624
|
+
const list3 = await skillsStatus({ home, source, targets });
|
|
4625
|
+
const problems = [];
|
|
4626
|
+
for (const target of targets) {
|
|
4627
|
+
const bad = list3.filter((item) => item.target === target && !skillIsCurrent(item.state, VERSION)).map((item) => `${item.skill} ${skillStateLabel(item.state)}`);
|
|
4628
|
+
if (bad.length > 0) problems.push(`${target}: ${bad.join(", ")}`);
|
|
4629
|
+
}
|
|
4630
|
+
if (problems.length === 0) return { name, status: "ok", detail: targets.join(", ") };
|
|
4631
|
+
return { name, status: "warn", detail: `${problems.join("; ")} (run rshow skills install)` };
|
|
4632
|
+
}
|
|
4633
|
+
async function run6(argv, ctx) {
|
|
3876
4634
|
const args = parseCommandArgs(argv, {}, 0, 1, DOCTOR_USAGE);
|
|
3877
4635
|
const dir = args.positionals[0];
|
|
3878
4636
|
const found = [];
|
|
3879
|
-
for (const file of [globalConfigPath(
|
|
3880
|
-
if (file !== void 0 && await
|
|
4637
|
+
for (const file of [globalConfigPath(process13.env), dir === void 0 ? void 0 : localConfigPath(dir)]) {
|
|
4638
|
+
if (file !== void 0 && await isFile3(file)) found.push(file);
|
|
3881
4639
|
}
|
|
3882
4640
|
let config;
|
|
3883
4641
|
let configError;
|
|
@@ -3888,7 +4646,7 @@ async function run7(argv, ctx) {
|
|
|
3888
4646
|
}
|
|
3889
4647
|
const token = await tokenOf(config?.url);
|
|
3890
4648
|
const checks = [
|
|
3891
|
-
{ name: "rshow", status: "ok", detail: VERSION },
|
|
4649
|
+
{ name: "rshow", status: "ok", detail: await isBinary() ? `${VERSION} (binary)` : VERSION },
|
|
3892
4650
|
await nodeCheck(),
|
|
3893
4651
|
configCheck(found, config, configError),
|
|
3894
4652
|
tokenCheck(token),
|
|
@@ -3897,19 +4655,20 @@ async function run7(argv, ctx) {
|
|
|
3897
4655
|
];
|
|
3898
4656
|
const theme = await themeCheck(config);
|
|
3899
4657
|
if (theme !== void 0) checks.push(theme);
|
|
4658
|
+
checks.push(await skillsCheck());
|
|
3900
4659
|
ctx.out.data({ checks });
|
|
3901
4660
|
for (const check2 of checks) ctx.out.line(`${check2.status} ${check2.name} ${check2.detail}`);
|
|
3902
4661
|
return checks.some((check2) => check2.status === "fail") ? EXIT.check : EXIT.ok;
|
|
3903
4662
|
}
|
|
3904
4663
|
|
|
3905
4664
|
// src/commands/theme.ts
|
|
3906
|
-
import { mkdir as
|
|
4665
|
+
import { mkdir as mkdir6, mkdtemp as mkdtemp3, rm as rm5, stat as stat13, writeFile as writeFile7 } from "node:fs/promises";
|
|
3907
4666
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
3908
|
-
import
|
|
4667
|
+
import path17 from "node:path";
|
|
3909
4668
|
|
|
3910
4669
|
// src/theme.ts
|
|
3911
|
-
import { readFile as
|
|
3912
|
-
import
|
|
4670
|
+
import { readFile as readFile14, stat as stat12 } from "node:fs/promises";
|
|
4671
|
+
import path16 from "node:path";
|
|
3913
4672
|
function themeSlug(name) {
|
|
3914
4673
|
return `theme-${name}`;
|
|
3915
4674
|
}
|
|
@@ -3917,10 +4676,10 @@ function themeUrl(name, n) {
|
|
|
3917
4676
|
return `/_themes/${name}@${n}/`;
|
|
3918
4677
|
}
|
|
3919
4678
|
async function readThemeMeta(dir) {
|
|
3920
|
-
const file =
|
|
4679
|
+
const file = path16.join(dir, "theme.json");
|
|
3921
4680
|
let text;
|
|
3922
4681
|
try {
|
|
3923
|
-
text = await
|
|
4682
|
+
text = await readFile14(file, "utf8");
|
|
3924
4683
|
} catch (err) {
|
|
3925
4684
|
if (err.code === "ENOENT") throw new UsageError(`${dir} has no theme.json`);
|
|
3926
4685
|
throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3941,8 +4700,8 @@ async function readThemeMeta(dir) {
|
|
|
3941
4700
|
return { name };
|
|
3942
4701
|
}
|
|
3943
4702
|
async function requireThemeDir(input) {
|
|
3944
|
-
const dir =
|
|
3945
|
-
const info = await
|
|
4703
|
+
const dir = path16.resolve(input);
|
|
4704
|
+
const info = await stat12(dir).catch(() => void 0);
|
|
3946
4705
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${input}`);
|
|
3947
4706
|
return dir;
|
|
3948
4707
|
}
|
|
@@ -3950,7 +4709,7 @@ async function requireThemeDir(input) {
|
|
|
3950
4709
|
// src/commands/theme.ts
|
|
3951
4710
|
async function presetCheck(dir) {
|
|
3952
4711
|
const findings = await checkPreset(dir);
|
|
3953
|
-
const info = await
|
|
4712
|
+
const info = await stat13(path17.join(dir, "theme.json")).catch(() => void 0);
|
|
3954
4713
|
const errors = findings.filter((finding2) => finding2.severity === "error").length;
|
|
3955
4714
|
return { findings, errors, warnings: findings.length - errors, files: 1, bytes: info?.size ?? 0 };
|
|
3956
4715
|
}
|
|
@@ -3979,14 +4738,14 @@ async function publish(argv, ctx) {
|
|
|
3979
4738
|
if (!boolValue(args, "no-check")) {
|
|
3980
4739
|
if (preset) await enforcePresetCheck(ctx.out, dir);
|
|
3981
4740
|
else {
|
|
3982
|
-
if (config.art === void 0)
|
|
4741
|
+
if (config.art === void 0) warn("no art host configured; skipping theme checks");
|
|
3983
4742
|
await enforceCheck(ctx.out, dir, { art: config.art, offline: config.art === void 0 });
|
|
3984
4743
|
}
|
|
3985
4744
|
}
|
|
3986
4745
|
let target = dir;
|
|
3987
4746
|
let temp;
|
|
3988
4747
|
if (preset) {
|
|
3989
|
-
temp = await
|
|
4748
|
+
temp = await mkdtemp3(path17.join(tmpdir2(), "rshow-theme-"));
|
|
3990
4749
|
await buildPreset(dir, temp);
|
|
3991
4750
|
target = temp;
|
|
3992
4751
|
}
|
|
@@ -4032,7 +4791,7 @@ async function publish(argv, ctx) {
|
|
|
4032
4791
|
reportPublish(ctx.out, { ...result, artifact });
|
|
4033
4792
|
ctx.out.line(url);
|
|
4034
4793
|
} finally {
|
|
4035
|
-
if (temp !== void 0) await
|
|
4794
|
+
if (temp !== void 0) await rm5(temp, { recursive: true, force: true });
|
|
4036
4795
|
}
|
|
4037
4796
|
}
|
|
4038
4797
|
async function list(argv, ctx) {
|
|
@@ -4074,14 +4833,14 @@ async function create(argv, ctx) {
|
|
|
4074
4833
|
const from = stringValue(args, "from") ?? "default";
|
|
4075
4834
|
const baseDir = await builtinThemeDir(from);
|
|
4076
4835
|
if (baseDir === void 0) throw new UsageError(`no built-in theme: ${from}`);
|
|
4077
|
-
const parent =
|
|
4078
|
-
const target =
|
|
4079
|
-
const
|
|
4080
|
-
if (
|
|
4836
|
+
const parent = path17.resolve(stringValue(args, "dir") ?? ".");
|
|
4837
|
+
const target = path17.join(parent, name);
|
|
4838
|
+
const exists2 = await stat13(target).catch(() => void 0);
|
|
4839
|
+
if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
|
|
4081
4840
|
const values = await baseRoleValues(baseDir, ROLE_TOKENS);
|
|
4082
4841
|
const doc = { name, extends: from, tokens: {}, modeTokens: values };
|
|
4083
|
-
await
|
|
4084
|
-
await
|
|
4842
|
+
await mkdir6(target, { recursive: true });
|
|
4843
|
+
await writeFile7(path17.join(target, "theme.json"), `${JSON.stringify(doc, null, 2)}
|
|
4085
4844
|
`);
|
|
4086
4845
|
ctx.out.data({ path: target, name, extends: from });
|
|
4087
4846
|
ctx.out.line(target);
|
|
@@ -4116,7 +4875,7 @@ async function preview(argv, ctx) {
|
|
|
4116
4875
|
const port = portOf(stringValue(args, "port"));
|
|
4117
4876
|
const offline = boolValue(args, "offline");
|
|
4118
4877
|
const config = await ctx.config(dir);
|
|
4119
|
-
if (config.art === void 0 && !offline)
|
|
4878
|
+
if (config.art === void 0 && !offline) warn("no art host configured; skipping the /_themes proxy");
|
|
4120
4879
|
const server = await startPreview({
|
|
4121
4880
|
dir,
|
|
4122
4881
|
theme: dir,
|
|
@@ -4130,7 +4889,7 @@ async function preview(argv, ctx) {
|
|
|
4130
4889
|
ctx.out.raw(`${server.url}/index.html`);
|
|
4131
4890
|
return EXIT.ok;
|
|
4132
4891
|
}
|
|
4133
|
-
async function
|
|
4892
|
+
async function run7(argv, ctx) {
|
|
4134
4893
|
const [sub, ...rest] = argv;
|
|
4135
4894
|
if (sub === void 0) throw new UsageError(`expected a theme command (try: ${usageLine(THEME_USAGE)})`);
|
|
4136
4895
|
if (sub === "new") return create(rest, ctx);
|
|
@@ -4147,6 +4906,122 @@ async function run8(argv, ctx) {
|
|
|
4147
4906
|
throw new UsageError(`unknown theme command: ${sub} (try: ${usageLine(THEME_USAGE)})`);
|
|
4148
4907
|
}
|
|
4149
4908
|
|
|
4909
|
+
// src/commands/skills.ts
|
|
4910
|
+
import path18 from "node:path";
|
|
4911
|
+
import process14 from "node:process";
|
|
4912
|
+
var CHANGE_ACTIONS = /* @__PURE__ */ new Set(["installed", "updated", "removed"]);
|
|
4913
|
+
async function sourceOf() {
|
|
4914
|
+
const source = await skillsSource(process14.env);
|
|
4915
|
+
if (source === void 0) {
|
|
4916
|
+
throw new UsageError(`no skills folder next to rshow (set RSHOW_SKILLS_DIR to one)`);
|
|
4917
|
+
}
|
|
4918
|
+
return source;
|
|
4919
|
+
}
|
|
4920
|
+
async function targetsWithDetection(names, home) {
|
|
4921
|
+
const chosen = parseTargets(names);
|
|
4922
|
+
if (chosen.length > 0) return chosen;
|
|
4923
|
+
return detectedTargets(home);
|
|
4924
|
+
}
|
|
4925
|
+
function noTargetsError(home) {
|
|
4926
|
+
return new UsageError(
|
|
4927
|
+
`no target detected (looked for ${path18.join(home, ".claude")}, ${path18.join(home, ".pi/agent")} and ${path18.join(home, ".codex")}); name one of ${targetHint()}`
|
|
4928
|
+
);
|
|
4929
|
+
}
|
|
4930
|
+
function actionLine(entry, dryRun) {
|
|
4931
|
+
const would = dryRun && CHANGE_ACTIONS.has(entry.action) ? "would " : "";
|
|
4932
|
+
return `${would}${entry.action} ${entry.path}`;
|
|
4933
|
+
}
|
|
4934
|
+
function countOf(entries) {
|
|
4935
|
+
return entries.filter((entry) => entry.action !== "skip").length;
|
|
4936
|
+
}
|
|
4937
|
+
var DRY_RUN_VERB = { installed: "install", removed: "remove" };
|
|
4938
|
+
function summaryLine2(verb, entries, targets, dryRun) {
|
|
4939
|
+
const phrase = dryRun ? `would ${DRY_RUN_VERB[verb] ?? verb}` : verb;
|
|
4940
|
+
return `${phrase} ${countOf(entries)} skills for ${targets.join(", ")}`;
|
|
4941
|
+
}
|
|
4942
|
+
async function install(argv, ctx) {
|
|
4943
|
+
const args = parseCommandArgs(
|
|
4944
|
+
argv,
|
|
4945
|
+
{ link: { type: "boolean" }, copy: { type: "boolean" }, "dry-run": { type: "boolean" } },
|
|
4946
|
+
0,
|
|
4947
|
+
3,
|
|
4948
|
+
SKILLS_INSTALL_USAGE
|
|
4949
|
+
);
|
|
4950
|
+
const link = boolValue(args, "link");
|
|
4951
|
+
const copy = boolValue(args, "copy");
|
|
4952
|
+
if (link && copy) throw new UsageError(`--link and --copy cannot be used together (try: ${usageLine(SKILLS_INSTALL_USAGE)})`);
|
|
4953
|
+
const home = homeDir(process14.env);
|
|
4954
|
+
const targets = await targetsWithDetection(args.positionals, home);
|
|
4955
|
+
if (targets.length === 0) throw noTargetsError(home);
|
|
4956
|
+
const source = await sourceOf();
|
|
4957
|
+
const mode = link ? "link" : copy ? "copy" : source.mode;
|
|
4958
|
+
const dryRun = boolValue(args, "dry-run");
|
|
4959
|
+
const entries = await installSkills({ home, source, targets, mode, dryRun, version: VERSION });
|
|
4960
|
+
ctx.out.data({ targets, actions: entries });
|
|
4961
|
+
const skipped = [];
|
|
4962
|
+
for (const entry of entries) {
|
|
4963
|
+
if (entry.action === "skip") skipped.push(entry.path);
|
|
4964
|
+
else ctx.out.line(actionLine(entry, dryRun));
|
|
4965
|
+
}
|
|
4966
|
+
ctx.out.line(summaryLine2("installed", entries, targets, dryRun));
|
|
4967
|
+
for (const dst of skipped) process14.stderr.write(`skip ${dst}: exists and is not from rshow
|
|
4968
|
+
`);
|
|
4969
|
+
return skipped.length > 0 ? EXIT.check : EXIT.ok;
|
|
4970
|
+
}
|
|
4971
|
+
async function uninstall(argv, ctx) {
|
|
4972
|
+
const args = parseCommandArgs(argv, { "dry-run": { type: "boolean" } }, 0, 3, SKILLS_UNINSTALL_USAGE);
|
|
4973
|
+
const home = homeDir(process14.env);
|
|
4974
|
+
const targets = await targetsWithDetection(args.positionals, home);
|
|
4975
|
+
if (targets.length === 0) return EXIT.ok;
|
|
4976
|
+
const source = await sourceOf();
|
|
4977
|
+
const dryRun = boolValue(args, "dry-run");
|
|
4978
|
+
const entries = await uninstallSkills({ home, source, targets, dryRun });
|
|
4979
|
+
ctx.out.data({ targets, actions: entries });
|
|
4980
|
+
for (const entry of entries) ctx.out.line(actionLine(entry, dryRun));
|
|
4981
|
+
ctx.out.line(summaryLine2("removed", entries, targets, dryRun));
|
|
4982
|
+
return EXIT.ok;
|
|
4983
|
+
}
|
|
4984
|
+
function actionOf(item) {
|
|
4985
|
+
if (item.state.kind === "link") return { action: "link", link: item.state.path };
|
|
4986
|
+
if (item.state.kind === "copy") return { action: "copy", version: item.state.version };
|
|
4987
|
+
return { action: item.state.kind };
|
|
4988
|
+
}
|
|
4989
|
+
async function status(argv, ctx) {
|
|
4990
|
+
const args = parseCommandArgs(argv, {}, 0, 3, SKILLS_STATUS_USAGE);
|
|
4991
|
+
const home = homeDir(process14.env);
|
|
4992
|
+
const targets = await targetsWithDetection(args.positionals, home);
|
|
4993
|
+
if (targets.length === 0) return EXIT.ok;
|
|
4994
|
+
const source = await sourceOf();
|
|
4995
|
+
const list3 = await skillsStatus({ home, source, targets });
|
|
4996
|
+
const targetWidth = Math.max(...targets.map((target) => target.length));
|
|
4997
|
+
const skillWidth = Math.max(...source.skills.map((skill) => skill.length));
|
|
4998
|
+
ctx.out.data({
|
|
4999
|
+
targets,
|
|
5000
|
+
actions: list3.map((item) => ({ target: item.target, skill: item.skill, path: item.path, ...actionOf(item) }))
|
|
5001
|
+
});
|
|
5002
|
+
for (const item of list3) {
|
|
5003
|
+
const name = `${item.target.padEnd(targetWidth)} ${item.skill.padEnd(skillWidth)}`;
|
|
5004
|
+
ctx.out.line(`${name} ${skillStateLabel(item.state)}`);
|
|
5005
|
+
if (item.state.kind === "copy" && !skillIsCurrent(item.state, VERSION)) {
|
|
5006
|
+
warn(`${item.path} is a copy of v${item.state.version}, this rshow is ${VERSION}: run rshow skills install`);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
return EXIT.ok;
|
|
5010
|
+
}
|
|
5011
|
+
async function run8(argv, ctx) {
|
|
5012
|
+
const [sub, ...rest] = argv;
|
|
5013
|
+
switch (sub) {
|
|
5014
|
+
case "install":
|
|
5015
|
+
return install(rest, ctx);
|
|
5016
|
+
case "uninstall":
|
|
5017
|
+
return uninstall(rest, ctx);
|
|
5018
|
+
case "status":
|
|
5019
|
+
return status(rest, ctx);
|
|
5020
|
+
default:
|
|
5021
|
+
throw new UsageError(`skills needs a subcommand (try: ${usageLine(SKILLS_USAGE)})`);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
|
|
4150
5025
|
// src/commands/list.ts
|
|
4151
5026
|
async function run9(argv, ctx) {
|
|
4152
5027
|
const args = parseCommandArgs(
|
|
@@ -4390,7 +5265,7 @@ async function share(argv, ctx) {
|
|
|
4390
5265
|
for (const member of members) ctx.out.line(`member ${member.email} ${member.role}`);
|
|
4391
5266
|
for (const email of removals) ctx.out.line(`removed ${email}`);
|
|
4392
5267
|
}
|
|
4393
|
-
async function
|
|
5268
|
+
async function remove4(argv, ctx) {
|
|
4394
5269
|
const args = parseCommandArgs(argv, { yes: { type: "boolean" } }, 1, 1, PROJECT_DELETE_USAGE);
|
|
4395
5270
|
const slug = validateSlug(args.positionals[0]);
|
|
4396
5271
|
if (!boolValue(args, "yes")) {
|
|
@@ -4410,7 +5285,7 @@ async function run14(argv, ctx) {
|
|
|
4410
5285
|
if (sub === "list") return list2(rest, ctx);
|
|
4411
5286
|
if (sub === "show") return show(rest, ctx);
|
|
4412
5287
|
if (sub === "share") return share(rest, ctx);
|
|
4413
|
-
if (sub === "delete") return
|
|
5288
|
+
if (sub === "delete") return remove4(rest, ctx);
|
|
4414
5289
|
throw new UsageError(`unknown project command: ${sub} (try: ${usageLine(PROJECT_USAGE)})`);
|
|
4415
5290
|
}
|
|
4416
5291
|
|
|
@@ -4425,13 +5300,13 @@ async function run15(argv, ctx) {
|
|
|
4425
5300
|
);
|
|
4426
5301
|
const slug = validateSlug(args.positionals[0]);
|
|
4427
5302
|
const add2 = normalizeTags(stringList(args, "add"));
|
|
4428
|
-
const
|
|
4429
|
-
if (add2.length === 0 &&
|
|
5303
|
+
const remove5 = normalizeTags(stringList(args, "remove"));
|
|
5304
|
+
if (add2.length === 0 && remove5.length === 0) {
|
|
4430
5305
|
throw new UsageError(`nothing to change (try: ${usageLine(TAG_USAGE)})`);
|
|
4431
5306
|
}
|
|
4432
5307
|
const api = await ctx.api();
|
|
4433
5308
|
const current = await api.artifact(slug);
|
|
4434
|
-
const tags = withTags(tagsOf(current?.tags), add2,
|
|
5309
|
+
const tags = withTags(tagsOf(current?.tags), add2, remove5);
|
|
4435
5310
|
const artifact = await api.patchArtifact(slug, { tags });
|
|
4436
5311
|
ctx.out.data({ artifact, tags });
|
|
4437
5312
|
ctx.out.line(`slug: ${slug}`);
|
|
@@ -4439,20 +5314,20 @@ async function run15(argv, ctx) {
|
|
|
4439
5314
|
}
|
|
4440
5315
|
|
|
4441
5316
|
// src/commands/handoff.ts
|
|
4442
|
-
import
|
|
5317
|
+
import process18 from "node:process";
|
|
4443
5318
|
|
|
4444
5319
|
// src/handoff.ts
|
|
4445
|
-
import { mkdir as
|
|
4446
|
-
import
|
|
4447
|
-
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";
|
|
4448
5323
|
|
|
4449
5324
|
// src/cdp.ts
|
|
4450
5325
|
import { spawn as spawn3 } from "node:child_process";
|
|
4451
5326
|
import { existsSync, rmSync } from "node:fs";
|
|
4452
|
-
import { mkdtemp as
|
|
5327
|
+
import { mkdtemp as mkdtemp4, rm as rm6 } from "node:fs/promises";
|
|
4453
5328
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
4454
|
-
import
|
|
4455
|
-
import
|
|
5329
|
+
import path19 from "node:path";
|
|
5330
|
+
import process15 from "node:process";
|
|
4456
5331
|
var CHROME_CANDIDATES = [
|
|
4457
5332
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
4458
5333
|
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
@@ -4472,9 +5347,9 @@ var LOAD_TIMEOUT_MS = 2e4;
|
|
|
4472
5347
|
var liveBrowsers = /* @__PURE__ */ new Map();
|
|
4473
5348
|
var signalHandlersInstalled = false;
|
|
4474
5349
|
function killBrowser(child, signal) {
|
|
4475
|
-
if (
|
|
5350
|
+
if (process15.platform !== "win32" && child.pid !== void 0) {
|
|
4476
5351
|
try {
|
|
4477
|
-
|
|
5352
|
+
process15.kill(-child.pid, signal);
|
|
4478
5353
|
return;
|
|
4479
5354
|
} catch {
|
|
4480
5355
|
}
|
|
@@ -4499,24 +5374,24 @@ function reapBrowsers() {
|
|
|
4499
5374
|
}
|
|
4500
5375
|
function onInterrupt() {
|
|
4501
5376
|
reapBrowsers();
|
|
4502
|
-
|
|
5377
|
+
process15.exit(130);
|
|
4503
5378
|
}
|
|
4504
5379
|
function onTerminate() {
|
|
4505
5380
|
reapBrowsers();
|
|
4506
|
-
|
|
5381
|
+
process15.exit(143);
|
|
4507
5382
|
}
|
|
4508
5383
|
function dropSignalHandlers() {
|
|
4509
5384
|
if (!signalHandlersInstalled) return;
|
|
4510
5385
|
signalHandlersInstalled = false;
|
|
4511
|
-
|
|
4512
|
-
|
|
5386
|
+
process15.off("SIGINT", onInterrupt);
|
|
5387
|
+
process15.off("SIGTERM", onTerminate);
|
|
4513
5388
|
}
|
|
4514
5389
|
function trackBrowser(child, profile) {
|
|
4515
5390
|
liveBrowsers.set(child, profile);
|
|
4516
5391
|
if (!signalHandlersInstalled) {
|
|
4517
5392
|
signalHandlersInstalled = true;
|
|
4518
|
-
|
|
4519
|
-
|
|
5393
|
+
process15.on("SIGINT", onInterrupt);
|
|
5394
|
+
process15.on("SIGTERM", onTerminate);
|
|
4520
5395
|
}
|
|
4521
5396
|
}
|
|
4522
5397
|
function forgetBrowser(child) {
|
|
@@ -4527,20 +5402,20 @@ var exitHookInstalled = false;
|
|
|
4527
5402
|
function installExitHook() {
|
|
4528
5403
|
if (exitHookInstalled) return;
|
|
4529
5404
|
exitHookInstalled = true;
|
|
4530
|
-
|
|
5405
|
+
process15.once("exit", reapBrowsers);
|
|
4531
5406
|
}
|
|
4532
5407
|
function messageOf11(err) {
|
|
4533
5408
|
return err instanceof Error ? err.message : String(err);
|
|
4534
5409
|
}
|
|
4535
5410
|
function whichInPath(name, pathValue) {
|
|
4536
|
-
for (const dir of String(pathValue ?? "").split(
|
|
5411
|
+
for (const dir of String(pathValue ?? "").split(path19.delimiter)) {
|
|
4537
5412
|
if (dir === "") continue;
|
|
4538
|
-
const file =
|
|
5413
|
+
const file = path19.join(dir, name);
|
|
4539
5414
|
if (existsSync(file)) return file;
|
|
4540
5415
|
}
|
|
4541
5416
|
return void 0;
|
|
4542
5417
|
}
|
|
4543
|
-
function findChrome(env =
|
|
5418
|
+
function findChrome(env = process15.env) {
|
|
4544
5419
|
const explicit = (env.CHROME ?? "").trim();
|
|
4545
5420
|
if (explicit !== "") {
|
|
4546
5421
|
if (existsSync(explicit)) return explicit;
|
|
@@ -4711,7 +5586,7 @@ var Chrome = class _Chrome {
|
|
|
4711
5586
|
static async launch(opts = {}) {
|
|
4712
5587
|
const binary = opts.binary ?? findChrome();
|
|
4713
5588
|
if (binary === void 0) throw new UsageError("no Chrome or Chromium found, set CHROME");
|
|
4714
|
-
const profile = await
|
|
5589
|
+
const profile = await mkdtemp4(path19.join(tmpdir3(), "rshow-chrome-"));
|
|
4715
5590
|
const child = spawn3(
|
|
4716
5591
|
binary,
|
|
4717
5592
|
[
|
|
@@ -4731,7 +5606,7 @@ var Chrome = class _Chrome {
|
|
|
4731
5606
|
"--window-size=1440,900",
|
|
4732
5607
|
"about:blank"
|
|
4733
5608
|
],
|
|
4734
|
-
{ stdio: ["ignore", "pipe", "pipe"], detached:
|
|
5609
|
+
{ stdio: ["ignore", "pipe", "pipe"], detached: process15.platform !== "win32" }
|
|
4735
5610
|
);
|
|
4736
5611
|
installExitHook();
|
|
4737
5612
|
trackBrowser(child, profile);
|
|
@@ -4742,7 +5617,7 @@ var Chrome = class _Chrome {
|
|
|
4742
5617
|
} catch (err) {
|
|
4743
5618
|
forgetBrowser(child);
|
|
4744
5619
|
killBrowser(child, "SIGKILL");
|
|
4745
|
-
await
|
|
5620
|
+
await rm6(profile, { recursive: true, force: true }).catch(() => {
|
|
4746
5621
|
});
|
|
4747
5622
|
if (err instanceof ApiError || err instanceof UsageError) throw err;
|
|
4748
5623
|
throw new ApiError(`cannot start chrome: ${messageOf11(err)}`);
|
|
@@ -4757,7 +5632,7 @@ var Chrome = class _Chrome {
|
|
|
4757
5632
|
killBrowser(this.child, "SIGTERM");
|
|
4758
5633
|
await delay(50);
|
|
4759
5634
|
if (this.child.exitCode === null) killBrowser(this.child, "SIGKILL");
|
|
4760
|
-
await
|
|
5635
|
+
await rm6(this.profile, PROFILE_REMOVE).catch(() => {
|
|
4761
5636
|
});
|
|
4762
5637
|
forgetBrowser(this.child);
|
|
4763
5638
|
}
|
|
@@ -4863,16 +5738,16 @@ var Page = class _Page {
|
|
|
4863
5738
|
};
|
|
4864
5739
|
|
|
4865
5740
|
// src/map.ts
|
|
4866
|
-
import { readFile as
|
|
4867
|
-
import
|
|
4868
|
-
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";
|
|
4869
5744
|
var MAP_FILE = "rshow.map.json";
|
|
4870
|
-
function
|
|
5745
|
+
function isRecord6(value) {
|
|
4871
5746
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4872
5747
|
}
|
|
4873
5748
|
function textMap(file, key2, value) {
|
|
4874
5749
|
if (value === void 0 || value === null) return {};
|
|
4875
|
-
if (!
|
|
5750
|
+
if (!isRecord6(value)) throw new UsageError(`${file}: ${key2} must be an object`);
|
|
4876
5751
|
const out = {};
|
|
4877
5752
|
for (const [name, target] of Object.entries(value)) {
|
|
4878
5753
|
if (typeof target !== "string") throw new UsageError(`${file}: ${key2}.${name} must be a string`);
|
|
@@ -4881,12 +5756,12 @@ function textMap(file, key2, value) {
|
|
|
4881
5756
|
return out;
|
|
4882
5757
|
}
|
|
4883
5758
|
function mapPath(dir) {
|
|
4884
|
-
return
|
|
5759
|
+
return path20.resolve(dir ?? process16.cwd(), MAP_FILE);
|
|
4885
5760
|
}
|
|
4886
5761
|
async function readMapFile(file) {
|
|
4887
5762
|
let text;
|
|
4888
5763
|
try {
|
|
4889
|
-
text = await
|
|
5764
|
+
text = await readFile15(file, "utf8");
|
|
4890
5765
|
} catch (err) {
|
|
4891
5766
|
if (err.code === "ENOENT") return void 0;
|
|
4892
5767
|
throw new UsageError(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4897,7 +5772,7 @@ async function readMapFile(file) {
|
|
|
4897
5772
|
} catch {
|
|
4898
5773
|
throw new UsageError(`invalid JSON in ${file}`);
|
|
4899
5774
|
}
|
|
4900
|
-
if (!
|
|
5775
|
+
if (!isRecord6(parsed)) throw new UsageError(`${file} must hold a JSON object`);
|
|
4901
5776
|
return {
|
|
4902
5777
|
components: textMap(file, "components", parsed.components),
|
|
4903
5778
|
tokens: textMap(file, "tokens", parsed.tokens),
|
|
@@ -4905,7 +5780,7 @@ async function readMapFile(file) {
|
|
|
4905
5780
|
};
|
|
4906
5781
|
}
|
|
4907
5782
|
async function writeMapFile(file, map) {
|
|
4908
|
-
await
|
|
5783
|
+
await writeFile8(file, `${JSON.stringify(sortMap(map), null, 2)}
|
|
4909
5784
|
`);
|
|
4910
5785
|
}
|
|
4911
5786
|
function sortRecord(value) {
|
|
@@ -5349,14 +6224,14 @@ function parseTarget(input) {
|
|
|
5349
6224
|
if (remote !== null) {
|
|
5350
6225
|
return { kind: "remote", slug: remote[1], version: Number(remote[2]) };
|
|
5351
6226
|
}
|
|
5352
|
-
const dir =
|
|
5353
|
-
return { kind: "local", dir, slug: deriveSlug(
|
|
6227
|
+
const dir = path21.resolve(input);
|
|
6228
|
+
return { kind: "local", dir, slug: deriveSlug(path21.basename(dir)) || "handoff" };
|
|
5354
6229
|
}
|
|
5355
6230
|
async function requireLocalDir(target) {
|
|
5356
6231
|
if (target.kind !== "local") throw new UsageError("expected a local folder");
|
|
5357
|
-
const info = await
|
|
6232
|
+
const info = await stat14(target.dir).catch(() => void 0);
|
|
5358
6233
|
if (info === void 0 || !info.isDirectory()) throw new UsageError(`no such folder: ${target.dir}`);
|
|
5359
|
-
const index = await
|
|
6234
|
+
const index = await stat14(path21.join(target.dir, "index.html")).catch(() => void 0);
|
|
5360
6235
|
if (index === void 0 || !index.isFile()) throw new UsageError(`${target.dir} has no index.html`);
|
|
5361
6236
|
return target.dir;
|
|
5362
6237
|
}
|
|
@@ -5371,10 +6246,10 @@ function safeName(text) {
|
|
|
5371
6246
|
return cleaned === "" ? "screen" : cleaned;
|
|
5372
6247
|
}
|
|
5373
6248
|
function displayPath(target) {
|
|
5374
|
-
const resolved =
|
|
5375
|
-
const relative =
|
|
6249
|
+
const resolved = path21.resolve(target);
|
|
6250
|
+
const relative = path21.relative(process17.cwd(), resolved);
|
|
5376
6251
|
if (relative === "") return ".";
|
|
5377
|
-
if (relative.startsWith("..") ||
|
|
6252
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative)) return resolved;
|
|
5378
6253
|
return relative;
|
|
5379
6254
|
}
|
|
5380
6255
|
function baselineName(screen, state, width, mode) {
|
|
@@ -5409,10 +6284,11 @@ async function collectStyleTexts(url, fetchImpl = fetch) {
|
|
|
5409
6284
|
}
|
|
5410
6285
|
async function assertPublic(url, art, fetchImpl = fetch) {
|
|
5411
6286
|
let reply;
|
|
6287
|
+
await readyNetwork();
|
|
5412
6288
|
try {
|
|
5413
6289
|
reply = await fetchImpl(url, { headers: { accept: "text/html" }, redirect: "follow" });
|
|
5414
6290
|
} catch (err) {
|
|
5415
|
-
throw new UsageError(`cannot reach ${url}: ${err
|
|
6291
|
+
throw new UsageError(`cannot reach ${url}: ${networkDetail(err, url)}`);
|
|
5416
6292
|
}
|
|
5417
6293
|
const host = new URL(art).host;
|
|
5418
6294
|
const wandered = new URL(reply.url === "" ? url : reply.url).host !== host;
|
|
@@ -5535,7 +6411,7 @@ async function runHandoff(target, opts) {
|
|
|
5535
6411
|
const version = target.kind === "remote" ? target.version : null;
|
|
5536
6412
|
const slug = target.slug;
|
|
5537
6413
|
const url = target.kind === "remote" ? `${art}/${slug}/v${version}/` : "";
|
|
5538
|
-
const out =
|
|
6414
|
+
const out = path21.resolve(opts.out);
|
|
5539
6415
|
let closePreview;
|
|
5540
6416
|
let pageUrl = url;
|
|
5541
6417
|
if (target.kind === "remote") {
|
|
@@ -5550,8 +6426,8 @@ async function runHandoff(target, opts) {
|
|
|
5550
6426
|
const styleTexts = await collectStyleTexts(pageUrl, fetchImpl);
|
|
5551
6427
|
const tokens = extractTokens(styleTexts);
|
|
5552
6428
|
say(`tokens: ${tokens.light.length} light, ${tokens.dark.length} dark`);
|
|
5553
|
-
await
|
|
5554
|
-
await
|
|
6429
|
+
await mkdir7(path21.join(out, "baseline"), { recursive: true });
|
|
6430
|
+
await writeFile9(path21.join(out, "tokens.json"), `${JSON.stringify(dtcgTokens(tokens), null, 2)}
|
|
5555
6431
|
`);
|
|
5556
6432
|
const screens = [];
|
|
5557
6433
|
const baselines = [];
|
|
@@ -5602,7 +6478,7 @@ async function runHandoff(target, opts) {
|
|
|
5602
6478
|
}
|
|
5603
6479
|
const png = await capture(page, screen);
|
|
5604
6480
|
const name = baselineName(screen.id, state, width, mode);
|
|
5605
|
-
await
|
|
6481
|
+
await writeFile9(path21.join(out, "baseline", name), png);
|
|
5606
6482
|
baselines.push({ screen: screen.id, state, width, mode, file: `baseline/${name}` });
|
|
5607
6483
|
}
|
|
5608
6484
|
}
|
|
@@ -5631,7 +6507,7 @@ async function runHandoff(target, opts) {
|
|
|
5631
6507
|
screens,
|
|
5632
6508
|
baselines
|
|
5633
6509
|
};
|
|
5634
|
-
await
|
|
6510
|
+
await writeFile9(path21.join(out, "spec.json"), `${JSON.stringify(spec, null, 2)}
|
|
5635
6511
|
`);
|
|
5636
6512
|
const mapFile = MAP_FILE;
|
|
5637
6513
|
let mapWritten = false;
|
|
@@ -5650,7 +6526,7 @@ async function runHandoff(target, opts) {
|
|
|
5650
6526
|
createdAt,
|
|
5651
6527
|
mapFile
|
|
5652
6528
|
});
|
|
5653
|
-
await
|
|
6529
|
+
await writeFile9(path21.join(out, "HANDOFF.md"), markdown);
|
|
5654
6530
|
return {
|
|
5655
6531
|
out,
|
|
5656
6532
|
url: pageUrl,
|
|
@@ -5707,14 +6583,14 @@ async function run16(argv, ctx) {
|
|
|
5707
6583
|
requireUrl(config);
|
|
5708
6584
|
requireArt(config);
|
|
5709
6585
|
} else if (config.art === void 0) {
|
|
5710
|
-
|
|
6586
|
+
warn("no art host configured; skipping the /_themes proxy");
|
|
5711
6587
|
}
|
|
5712
6588
|
const result = await runHandoff(target, {
|
|
5713
6589
|
out,
|
|
5714
6590
|
widths: widthList(stringValue(args, "widths")),
|
|
5715
6591
|
modes: modeList(stringValue(args, "modes")),
|
|
5716
6592
|
art: config.art ?? "",
|
|
5717
|
-
chrome:
|
|
6593
|
+
chrome: process18.env.CHROME,
|
|
5718
6594
|
onProgress: (line) => ctx.out.line(line)
|
|
5719
6595
|
});
|
|
5720
6596
|
const display = displayPath(result.out);
|
|
@@ -5740,11 +6616,11 @@ async function run16(argv, ctx) {
|
|
|
5740
6616
|
}
|
|
5741
6617
|
|
|
5742
6618
|
// src/commands/compare.ts
|
|
5743
|
-
import
|
|
6619
|
+
import process20 from "node:process";
|
|
5744
6620
|
|
|
5745
6621
|
// src/compare.ts
|
|
5746
|
-
import { copyFile, mkdir as
|
|
5747
|
-
import
|
|
6622
|
+
import { copyFile, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile10 } from "node:fs/promises";
|
|
6623
|
+
import path23 from "node:path";
|
|
5748
6624
|
|
|
5749
6625
|
// src/diff.ts
|
|
5750
6626
|
var MAX_Y = 35215;
|
|
@@ -5906,10 +6782,10 @@ function diffImages(before, after, opts = {}) {
|
|
|
5906
6782
|
|
|
5907
6783
|
// src/native.ts
|
|
5908
6784
|
import { spawn as spawn4 } from "node:child_process";
|
|
5909
|
-
import { readFile as
|
|
6785
|
+
import { readFile as readFile16, unlink as unlink2 } from "node:fs/promises";
|
|
5910
6786
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
5911
|
-
import
|
|
5912
|
-
import
|
|
6787
|
+
import path22 from "node:path";
|
|
6788
|
+
import process19 from "node:process";
|
|
5913
6789
|
var FILE_PREFIX = "file:";
|
|
5914
6790
|
var WINDOW_PREFIX = "window:";
|
|
5915
6791
|
var TMUX_PREFIX = "tmux:";
|
|
@@ -5938,9 +6814,9 @@ function isNative(source) {
|
|
|
5938
6814
|
}
|
|
5939
6815
|
async function captureNative(source, mapDir, deps = {}) {
|
|
5940
6816
|
if (source.kind === "file") {
|
|
5941
|
-
const file =
|
|
6817
|
+
const file = path22.resolve(mapDir, source.file);
|
|
5942
6818
|
try {
|
|
5943
|
-
return new Uint8Array(await
|
|
6819
|
+
return new Uint8Array(await readFile16(file));
|
|
5944
6820
|
} catch {
|
|
5945
6821
|
throw new UsageError(`no screenshot at ${source.file}, which is relative to rshow.map.json`);
|
|
5946
6822
|
}
|
|
@@ -5988,8 +6864,8 @@ function noWindow(title, seen) {
|
|
|
5988
6864
|
return new UsageError(`no window matches ${JSON.stringify(title)} (${list3}); use ${WINDOW_PREFIX} with an owner name or a title, or ${NATIVE_HINT}`);
|
|
5989
6865
|
}
|
|
5990
6866
|
var systemRunner = {
|
|
5991
|
-
run: (
|
|
5992
|
-
const child = spawn4(
|
|
6867
|
+
run: (bin3, args) => new Promise((resolve2, reject) => {
|
|
6868
|
+
const child = spawn4(bin3, [...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
5993
6869
|
let stdout = "";
|
|
5994
6870
|
let stderr = "";
|
|
5995
6871
|
child.stdout?.setEncoding("utf8");
|
|
@@ -6000,10 +6876,10 @@ var systemRunner = {
|
|
|
6000
6876
|
child.stderr?.on("data", (chunk2) => {
|
|
6001
6877
|
stderr += chunk2;
|
|
6002
6878
|
});
|
|
6003
|
-
child.on("error", (err) => reject(new UsageError(`cannot run ${
|
|
6879
|
+
child.on("error", (err) => reject(new UsageError(`cannot run ${bin3}: ${err.message}`)));
|
|
6004
6880
|
child.on("close", (code) => {
|
|
6005
6881
|
if (code !== 0) {
|
|
6006
|
-
reject(new UsageError(stderr.trim() === "" ? `${
|
|
6882
|
+
reject(new UsageError(stderr.trim() === "" ? `${bin3} exited ${code}` : `${bin3}: ${stderr.trim()}`));
|
|
6007
6883
|
return;
|
|
6008
6884
|
}
|
|
6009
6885
|
resolve2(stdout);
|
|
@@ -6011,9 +6887,9 @@ var systemRunner = {
|
|
|
6011
6887
|
})
|
|
6012
6888
|
};
|
|
6013
6889
|
async function captureWindow(title, deps = {}) {
|
|
6014
|
-
const platform = deps.platform ??
|
|
6890
|
+
const platform = deps.platform ?? process19.platform;
|
|
6015
6891
|
const runner = deps.runner ?? systemRunner;
|
|
6016
|
-
const scratch =
|
|
6892
|
+
const scratch = path22.join(tmpdir4(), `rshow-window-${process19.pid}-${Date.now()}.png`);
|
|
6017
6893
|
const wrap = (err) => err instanceof UsageError ? err : new UsageError(`window: ${title}: ${err instanceof Error ? err.message : String(err)}; ${NATIVE_HINT}`);
|
|
6018
6894
|
try {
|
|
6019
6895
|
if (platform === "darwin") {
|
|
@@ -6029,11 +6905,11 @@ async function captureWindow(title, deps = {}) {
|
|
|
6029
6905
|
} else {
|
|
6030
6906
|
throw new UsageError(`window: capture is not supported on ${platform}; ${NATIVE_HINT}`);
|
|
6031
6907
|
}
|
|
6032
|
-
return new Uint8Array(await
|
|
6908
|
+
return new Uint8Array(await readFile16(scratch));
|
|
6033
6909
|
} catch (err) {
|
|
6034
6910
|
throw wrap(err);
|
|
6035
6911
|
} finally {
|
|
6036
|
-
await
|
|
6912
|
+
await unlink2(scratch).catch(() => {
|
|
6037
6913
|
});
|
|
6038
6914
|
}
|
|
6039
6915
|
}
|
|
@@ -6070,23 +6946,23 @@ function extractPalette(image, k = 8) {
|
|
|
6070
6946
|
const g = image.data[pos + 1];
|
|
6071
6947
|
const b = image.data[pos + 2];
|
|
6072
6948
|
const key2 = r >> BIN_SHIFT << BITS * 2 | g >> BIN_SHIFT << BITS | b >> BIN_SHIFT;
|
|
6073
|
-
const
|
|
6074
|
-
if (
|
|
6949
|
+
const bin3 = bins.get(key2);
|
|
6950
|
+
if (bin3 === void 0) bins.set(key2, { count: 1, r, g, b });
|
|
6075
6951
|
else {
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6952
|
+
bin3.count += 1;
|
|
6953
|
+
bin3.r += r;
|
|
6954
|
+
bin3.g += g;
|
|
6955
|
+
bin3.b += b;
|
|
6080
6956
|
}
|
|
6081
6957
|
used += 1;
|
|
6082
6958
|
}
|
|
6083
6959
|
}
|
|
6084
6960
|
if (used === 0) return [];
|
|
6085
6961
|
const seeds = [...bins.values()].sort((left, right) => right.count - left.count).slice(0, k);
|
|
6086
|
-
let centers = seeds.map((
|
|
6087
|
-
const points = [...bins.values()].map((
|
|
6088
|
-
color: { r:
|
|
6089
|
-
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
|
|
6090
6966
|
}));
|
|
6091
6967
|
for (let round2 = 0; round2 < ROUNDS && centers.length > 1; round2 += 1) {
|
|
6092
6968
|
const sums = centers.map(() => ({ r: 0, g: 0, b: 0, weight: 0 }));
|
|
@@ -6801,9 +7677,9 @@ function compareGrid(expected, actual) {
|
|
|
6801
7677
|
}
|
|
6802
7678
|
return { rows, differing, pass: differing.length === 0 };
|
|
6803
7679
|
}
|
|
6804
|
-
function captureTmux(target,
|
|
7680
|
+
function captureTmux(target, bin3 = "tmux") {
|
|
6805
7681
|
return new Promise((resolve2, reject) => {
|
|
6806
|
-
const child = spawn5(
|
|
7682
|
+
const child = spawn5(bin3, ["capture-pane", "-p", "-e", "-t", target], { stdio: ["ignore", "pipe", "pipe"] });
|
|
6807
7683
|
let stdout = "";
|
|
6808
7684
|
let stderr = "";
|
|
6809
7685
|
child.stdout?.setEncoding("utf8");
|
|
@@ -6815,11 +7691,11 @@ function captureTmux(target, bin2 = "tmux") {
|
|
|
6815
7691
|
stderr += chunk2;
|
|
6816
7692
|
});
|
|
6817
7693
|
child.on("error", (err) => {
|
|
6818
|
-
reject(new ApiError(`cannot run ${
|
|
7694
|
+
reject(new ApiError(`cannot run ${bin3}: ${err.message}`));
|
|
6819
7695
|
});
|
|
6820
7696
|
child.on("close", (code) => {
|
|
6821
7697
|
if (code !== 0) {
|
|
6822
|
-
reject(new ApiError(stderr.trim() === "" ? `${
|
|
7698
|
+
reject(new ApiError(stderr.trim() === "" ? `${bin3} exited ${code}` : stderr.trim()));
|
|
6823
7699
|
return;
|
|
6824
7700
|
}
|
|
6825
7701
|
resolve2(stdout);
|
|
@@ -6843,9 +7719,9 @@ var EMPTY_MAP = { components: {}, tokens: {}, screens: {} };
|
|
|
6843
7719
|
async function readJson(file, what) {
|
|
6844
7720
|
let text;
|
|
6845
7721
|
try {
|
|
6846
|
-
text = await
|
|
7722
|
+
text = await readFile17(file, "utf8");
|
|
6847
7723
|
} catch {
|
|
6848
|
-
throw new UsageError(`${what} is missing ${
|
|
7724
|
+
throw new UsageError(`${what} is missing ${path23.basename(file)} (run rshow handoff first)`);
|
|
6849
7725
|
}
|
|
6850
7726
|
try {
|
|
6851
7727
|
return JSON.parse(text);
|
|
@@ -6854,13 +7730,13 @@ async function readJson(file, what) {
|
|
|
6854
7730
|
}
|
|
6855
7731
|
}
|
|
6856
7732
|
async function readHandoff(dir) {
|
|
6857
|
-
const spec = await readJson(
|
|
7733
|
+
const spec = await readJson(path23.join(dir, "spec.json"), dir);
|
|
6858
7734
|
if (spec === null || typeof spec !== "object" || !Array.isArray(spec.screens)) {
|
|
6859
7735
|
throw new UsageError(`${dir}/spec.json does not hold a handoff spec`);
|
|
6860
7736
|
}
|
|
6861
7737
|
let tokens = { light: [], dark: [] };
|
|
6862
7738
|
try {
|
|
6863
|
-
tokens = flattenDtcg(JSON.parse(await
|
|
7739
|
+
tokens = flattenDtcg(JSON.parse(await readFile17(path23.join(dir, "tokens.json"), "utf8")));
|
|
6864
7740
|
} catch {
|
|
6865
7741
|
tokens = { light: [], dark: [] };
|
|
6866
7742
|
}
|
|
@@ -6883,15 +7759,15 @@ function clipOf(info, screenId) {
|
|
|
6883
7759
|
async function runCompare(opts) {
|
|
6884
7760
|
const say = opts.onProgress ?? (() => {
|
|
6885
7761
|
});
|
|
6886
|
-
const dir =
|
|
7762
|
+
const dir = path23.resolve(opts.against);
|
|
6887
7763
|
const { spec, tokens } = await readHandoff(dir);
|
|
6888
|
-
const mapFile = opts.map === void 0 ? mapPath() :
|
|
7764
|
+
const mapFile = opts.map === void 0 ? mapPath() : path23.resolve(opts.map);
|
|
6889
7765
|
const map = await readMapFile(mapFile) ?? EMPTY_MAP;
|
|
6890
|
-
const mapDir =
|
|
6891
|
-
const reportDir =
|
|
6892
|
-
const imagesDir =
|
|
6893
|
-
await
|
|
6894
|
-
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 });
|
|
6895
7771
|
const results = [];
|
|
6896
7772
|
const collected = [];
|
|
6897
7773
|
let skipped = 0;
|
|
@@ -6926,16 +7802,16 @@ async function runCompare(opts) {
|
|
|
6926
7802
|
throw new UsageError(`tmux capture failed for ${target}: ${err instanceof Error ? err.message : String(err)}`);
|
|
6927
7803
|
});
|
|
6928
7804
|
const text = opts.tuiColors ? captured : stripAnsi(captured);
|
|
6929
|
-
const baselineFile =
|
|
7805
|
+
const baselineFile = path23.join(dir, "baseline", `${safeName(screen)}.txt`);
|
|
6930
7806
|
let expected;
|
|
6931
7807
|
try {
|
|
6932
|
-
expected = await
|
|
7808
|
+
expected = await readFile17(baselineFile, "utf8");
|
|
6933
7809
|
} catch {
|
|
6934
7810
|
expected = void 0;
|
|
6935
7811
|
}
|
|
6936
7812
|
if (expected === void 0) {
|
|
6937
|
-
await
|
|
6938
|
-
await
|
|
7813
|
+
await mkdir8(path23.dirname(baselineFile), { recursive: true });
|
|
7814
|
+
await writeFile10(baselineFile, text);
|
|
6939
7815
|
results.push({
|
|
6940
7816
|
screen,
|
|
6941
7817
|
state: "default",
|
|
@@ -6980,12 +7856,12 @@ async function runCompare(opts) {
|
|
|
6980
7856
|
const raw = (map.screens[screen] ?? "").trim();
|
|
6981
7857
|
say(`capture: ${raw}`);
|
|
6982
7858
|
let capture2;
|
|
6983
|
-
let
|
|
7859
|
+
let failure;
|
|
6984
7860
|
try {
|
|
6985
7861
|
capture2 = decodePng(await captureNative(group.source, mapDir));
|
|
6986
7862
|
} catch (err) {
|
|
6987
7863
|
if (err instanceof UsageError) throw err;
|
|
6988
|
-
|
|
7864
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
6989
7865
|
}
|
|
6990
7866
|
const screenSpec = spec.screens.find((item) => item.id === screen);
|
|
6991
7867
|
const rubricFile = nativeName(screen);
|
|
@@ -7004,13 +7880,13 @@ async function runCompare(opts) {
|
|
|
7004
7880
|
rubric: rubricFile,
|
|
7005
7881
|
mismatches: []
|
|
7006
7882
|
};
|
|
7007
|
-
if (
|
|
7008
|
-
results.push({ ...base, note: `capture failed: ${
|
|
7883
|
+
if (failure !== void 0 || capture2 === void 0) {
|
|
7884
|
+
results.push({ ...base, note: `capture failed: ${failure ?? "no pixels"}` });
|
|
7009
7885
|
continue;
|
|
7010
7886
|
}
|
|
7011
7887
|
let expectedImage;
|
|
7012
7888
|
try {
|
|
7013
|
-
expectedImage = decodePng(new Uint8Array(await
|
|
7889
|
+
expectedImage = decodePng(new Uint8Array(await readFile17(path23.join(dir, baseline.file))));
|
|
7014
7890
|
} catch {
|
|
7015
7891
|
results.push({ ...base, note: `missing baseline ${baseline.file}` });
|
|
7016
7892
|
continue;
|
|
@@ -7022,10 +7898,10 @@ async function runCompare(opts) {
|
|
|
7022
7898
|
threshold: opts.threshold
|
|
7023
7899
|
});
|
|
7024
7900
|
const diffPng = encodePng(diff.image);
|
|
7025
|
-
await
|
|
7026
|
-
await
|
|
7027
|
-
await
|
|
7028
|
-
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`));
|
|
7029
7905
|
const expected = screenSpec === void 0 ? [] : normativeColors(screenSpec, tokens, baseline.state, baseline.mode);
|
|
7030
7906
|
const palette = checkPalette(extractPalette(scaled), expected);
|
|
7031
7907
|
const pixelOk = diff.ratio <= opts.nativeMaxDiffRatio;
|
|
@@ -7070,7 +7946,7 @@ async function runCompare(opts) {
|
|
|
7070
7946
|
});
|
|
7071
7947
|
}
|
|
7072
7948
|
if (screenSpec !== void 0) {
|
|
7073
|
-
await
|
|
7949
|
+
await writeFile10(path23.join(reportDir, rubricFile), `${rubricMarkdown({ screen: screenSpec, shots })}
|
|
7074
7950
|
`);
|
|
7075
7951
|
}
|
|
7076
7952
|
}
|
|
@@ -7127,12 +8003,12 @@ async function runCompare(opts) {
|
|
|
7127
8003
|
}
|
|
7128
8004
|
const clip = clipOf(info, screenId) ? await screenClip(page, screenId) : null;
|
|
7129
8005
|
const actual = await page.screenshot(clip ?? void 0);
|
|
7130
|
-
const actualFile =
|
|
7131
|
-
await
|
|
7132
|
-
const baselineFile =
|
|
8006
|
+
const actualFile = path23.join(imagesDir, `${stem}-actual.png`);
|
|
8007
|
+
await writeFile10(actualFile, actual);
|
|
8008
|
+
const baselineFile = path23.join(dir, baseline.file);
|
|
7133
8009
|
let expected;
|
|
7134
8010
|
try {
|
|
7135
|
-
expected = new Uint8Array(await
|
|
8011
|
+
expected = new Uint8Array(await readFile17(baselineFile));
|
|
7136
8012
|
} catch {
|
|
7137
8013
|
results.push({
|
|
7138
8014
|
screen: baseline.screen,
|
|
@@ -7147,14 +8023,14 @@ async function runCompare(opts) {
|
|
|
7147
8023
|
});
|
|
7148
8024
|
continue;
|
|
7149
8025
|
}
|
|
7150
|
-
const baselineCopy =
|
|
8026
|
+
const baselineCopy = path23.join(imagesDir, `${stem}-baseline.png`);
|
|
7151
8027
|
await copyFile(baselineFile, baselineCopy);
|
|
7152
8028
|
const left = decodePng(expected);
|
|
7153
8029
|
const right = decodePng(actual);
|
|
7154
8030
|
const diff = diffImages(left, right, { threshold: opts.threshold });
|
|
7155
8031
|
const diffPng = encodePng(diff.image);
|
|
7156
|
-
await
|
|
7157
|
-
await
|
|
8032
|
+
await writeFile10(path23.join(imagesDir, `${stem}-diff.png`), diffPng);
|
|
8033
|
+
await writeFile10(path23.join(dir, "diff", `${stem}.png`), diffPng);
|
|
7158
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}`;
|
|
7159
8035
|
results.push({
|
|
7160
8036
|
screen: baseline.screen,
|
|
@@ -7223,8 +8099,8 @@ async function runCompare(opts) {
|
|
|
7223
8099
|
const checked = results.filter((item) => item.source !== "").length;
|
|
7224
8100
|
const review = results.filter((item) => item.status === "review").length;
|
|
7225
8101
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7226
|
-
await
|
|
7227
|
-
|
|
8102
|
+
await writeFile10(
|
|
8103
|
+
path23.join(reportDir, "report.json"),
|
|
7228
8104
|
`${JSON.stringify(
|
|
7229
8105
|
{
|
|
7230
8106
|
schema: 1,
|
|
@@ -7260,8 +8136,8 @@ async function runCompare(opts) {
|
|
|
7260
8136
|
)}
|
|
7261
8137
|
`
|
|
7262
8138
|
);
|
|
7263
|
-
await
|
|
7264
|
-
|
|
8139
|
+
await writeFile10(
|
|
8140
|
+
path23.join(reportDir, "index.html"),
|
|
7265
8141
|
reportHtml({
|
|
7266
8142
|
spec,
|
|
7267
8143
|
results,
|
|
@@ -7314,15 +8190,15 @@ function reportHtml(input) {
|
|
|
7314
8190
|
const nativeLimit = input.nativeMaxDiffRatio ?? 0.15;
|
|
7315
8191
|
const statusOf = (item) => item.status ?? (item.pass ? "pass" : "fail");
|
|
7316
8192
|
const rows = results.map((item) => {
|
|
7317
|
-
const
|
|
7318
|
-
const mark =
|
|
8193
|
+
const status2 = statusOf(item);
|
|
8194
|
+
const mark = status2 === "pass" ? '<span class="pass">pass</span>' : status2 === "review" ? '<span class="review">review</span>' : '<span class="fail">fail</span>';
|
|
7319
8195
|
const ratio2 = item.diffRatio === void 0 ? "-" : item.diffRatio.toFixed(4);
|
|
7320
8196
|
const rubric = item.rubric === void 0 ? "" : ` <a href="${escapeHtml(item.rubric)}">rubric</a>`;
|
|
7321
8197
|
return `<tr><td>${escapeHtml(item.screen)}</td><td>${escapeHtml(item.state)}</td><td>${escapeHtml(item.mode)}</td><td>${item.width === 0 ? "-" : item.width}</td><td>${mark}</td><td>${ratio2}</td><td>${item.mismatches.length}</td><td>${escapeHtml(item.note ?? "")}${rubric}</td></tr>`;
|
|
7322
8198
|
}).join("\n");
|
|
7323
8199
|
const sections = results.filter((item) => item.images !== void 0 || item.mismatches.length > 0).map((item) => {
|
|
7324
|
-
const
|
|
7325
|
-
const badge = `<span class="badge ${
|
|
8200
|
+
const status2 = statusOf(item);
|
|
8201
|
+
const badge = `<span class="badge ${status2}">${status2}</span>`;
|
|
7326
8202
|
const captureCaption = item.kind === "native" ? "capture" : "actual";
|
|
7327
8203
|
const shots = item.images === void 0 ? "" : `<div class="shots">
|
|
7328
8204
|
<figure><img src="${escapeHtml(item.images.baseline)}" alt="baseline"><figcaption>baseline</figcaption></figure>
|
|
@@ -7412,7 +8288,7 @@ async function run17(argv, ctx) {
|
|
|
7412
8288
|
DEFAULT_NATIVE_MAX_DIFF_RATIO
|
|
7413
8289
|
),
|
|
7414
8290
|
tuiColors: boolValue(args, "tui-colors"),
|
|
7415
|
-
chrome:
|
|
8291
|
+
chrome: process20.env.CHROME,
|
|
7416
8292
|
onProgress: (line) => ctx.out.line(line)
|
|
7417
8293
|
});
|
|
7418
8294
|
const failing = result.results.filter((item) => !item.pass);
|
|
@@ -7427,8 +8303,8 @@ async function run17(argv, ctx) {
|
|
|
7427
8303
|
const where = item.width === 0 ? item.mode : `${item.mode} ${item.width}`;
|
|
7428
8304
|
const ratio2 = item.diffRatio === void 0 ? "" : ` diff ${item.diffRatio.toFixed(4)}`;
|
|
7429
8305
|
const note = item.note === void 0 ? "" : ` (${item.note})`;
|
|
7430
|
-
const
|
|
7431
|
-
ctx.out.line(`${
|
|
8306
|
+
const status2 = item.status ?? (item.pass ? "pass" : "fail");
|
|
8307
|
+
ctx.out.line(`${status2} ${item.screen} ${item.state} ${where}${ratio2}${note}`);
|
|
7432
8308
|
for (const mismatch of item.mismatches.slice(0, 20)) {
|
|
7433
8309
|
ctx.out.line(
|
|
7434
8310
|
` mismatch ${item.screen} ${item.state} ${mismatch.component}: ${mismatch.property} expected ${mismatch.expected} actual ${mismatch.actual}`
|
|
@@ -7460,8 +8336,8 @@ async function run17(argv, ctx) {
|
|
|
7460
8336
|
}
|
|
7461
8337
|
|
|
7462
8338
|
// src/commands/docs.ts
|
|
7463
|
-
import { mkdir as
|
|
7464
|
-
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";
|
|
7465
8341
|
var USAGE = {
|
|
7466
8342
|
publish: PUBLISH_USAGE,
|
|
7467
8343
|
check: CHECK_USAGE,
|
|
@@ -7477,6 +8353,10 @@ var USAGE = {
|
|
|
7477
8353
|
"theme preview": THEME_PREVIEW_USAGE,
|
|
7478
8354
|
"theme publish": THEME_PUBLISH_USAGE,
|
|
7479
8355
|
"theme list": THEME_LIST_USAGE,
|
|
8356
|
+
skills: SKILLS_USAGE,
|
|
8357
|
+
"skills install": SKILLS_INSTALL_USAGE,
|
|
8358
|
+
"skills uninstall": SKILLS_UNINSTALL_USAGE,
|
|
8359
|
+
"skills status": SKILLS_STATUS_USAGE,
|
|
7480
8360
|
project: PROJECT_USAGE,
|
|
7481
8361
|
"project create": PROJECT_CREATE_USAGE,
|
|
7482
8362
|
"project list": PROJECT_LIST_USAGE,
|
|
@@ -7498,7 +8378,7 @@ var WHEN = {
|
|
|
7498
8378
|
auth: "the API refused the token with 401 or 403.",
|
|
7499
8379
|
api: "the API returned another error.",
|
|
7500
8380
|
build: "the `--build` command failed.",
|
|
7501
|
-
check: "`rshow check`, `rshow publish` or `rshow theme publish` found an error.",
|
|
8381
|
+
check: "`rshow check`, `rshow publish` or `rshow theme publish` found an error, or `rshow skills install` found a skill folder that is not from rshow.",
|
|
7502
8382
|
compare: "`rshow compare` found a difference."
|
|
7503
8383
|
};
|
|
7504
8384
|
var ARG_WORD = /^(?:<[^>]+>|\[[^\]]*\]|[a-z0-9-]+@v<n>|--?\S+)$/;
|
|
@@ -7736,7 +8616,7 @@ function heading(label) {
|
|
|
7736
8616
|
async function themeTokensPage() {
|
|
7737
8617
|
const dir = await builtinThemeDir("default");
|
|
7738
8618
|
if (dir === void 0) throw new Error("no built-in theme: default");
|
|
7739
|
-
const css = await
|
|
8619
|
+
const css = await readFile18(path24.join(dir, "tokens.css"), "utf8");
|
|
7740
8620
|
const end = css.indexOf("*/");
|
|
7741
8621
|
const roles = parseRoles(end < 0 ? css : css.slice(0, end));
|
|
7742
8622
|
const sections = parseSections(rootBlock(css));
|
|
@@ -7784,28 +8664,28 @@ function exitCodesPage() {
|
|
|
7784
8664
|
`;
|
|
7785
8665
|
}
|
|
7786
8666
|
async function generate(out) {
|
|
7787
|
-
const dir =
|
|
7788
|
-
await
|
|
7789
|
-
await
|
|
8667
|
+
const dir = path24.join(out, "commands");
|
|
8668
|
+
await rm7(dir, { recursive: true, force: true });
|
|
8669
|
+
await mkdir9(dir, { recursive: true });
|
|
7790
8670
|
const rows = commandRows();
|
|
7791
8671
|
const files = [];
|
|
7792
8672
|
for (const row of rows) {
|
|
7793
|
-
const file =
|
|
7794
|
-
await
|
|
8673
|
+
const file = path24.join(dir, row.file);
|
|
8674
|
+
await writeFile11(file, commandPage(row, rows));
|
|
7795
8675
|
files.push(file);
|
|
7796
8676
|
}
|
|
7797
|
-
const index =
|
|
7798
|
-
await
|
|
8677
|
+
const index = path24.join(dir, "index.md");
|
|
8678
|
+
await writeFile11(index, indexPage(rows));
|
|
7799
8679
|
files.push(index);
|
|
7800
8680
|
for (const [name, text] of [
|
|
7801
8681
|
["theme-tokens.md", await themeTokensPage()],
|
|
7802
8682
|
["exit-codes.md", exitCodesPage()]
|
|
7803
8683
|
]) {
|
|
7804
|
-
const file =
|
|
7805
|
-
await
|
|
8684
|
+
const file = path24.join(out, name);
|
|
8685
|
+
await writeFile11(file, text);
|
|
7806
8686
|
files.push(file);
|
|
7807
8687
|
}
|
|
7808
|
-
return { commands: rows.length, files: files.map((file) =>
|
|
8688
|
+
return { commands: rows.length, files: files.map((file) => path24.relative(out, file)).sort() };
|
|
7809
8689
|
}
|
|
7810
8690
|
async function run18(argv, ctx) {
|
|
7811
8691
|
const args = parseCommandArgs(argv, { out: { type: "string" } }, 0, 0, DOCS_USAGE);
|
|
@@ -7813,16 +8693,16 @@ async function run18(argv, ctx) {
|
|
|
7813
8693
|
if (out === void 0) throw new UsageError(`docs needs --out <dir> (try: ${usageLine(DOCS_USAGE)})`);
|
|
7814
8694
|
const result = await generate(out);
|
|
7815
8695
|
ctx.out.data(result);
|
|
7816
|
-
ctx.out.line(`wrote ${result.commands} command pages and the index under ${
|
|
7817
|
-
ctx.out.line(`wrote ${
|
|
7818
|
-
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")}`);
|
|
7819
8699
|
}
|
|
7820
8700
|
|
|
7821
8701
|
// src/commands/login.ts
|
|
7822
|
-
import
|
|
8702
|
+
import process21 from "node:process";
|
|
7823
8703
|
async function hiddenLine() {
|
|
7824
|
-
|
|
7825
|
-
const stdin =
|
|
8704
|
+
process21.stderr.write("token: ");
|
|
8705
|
+
const stdin = process21.stdin;
|
|
7826
8706
|
stdin.setRawMode(true);
|
|
7827
8707
|
stdin.resume();
|
|
7828
8708
|
stdin.setEncoding("utf8");
|
|
@@ -7832,7 +8712,7 @@ async function hiddenLine() {
|
|
|
7832
8712
|
stdin.off("data", onData);
|
|
7833
8713
|
stdin.setRawMode(false);
|
|
7834
8714
|
stdin.pause();
|
|
7835
|
-
|
|
8715
|
+
process21.stderr.write("\n");
|
|
7836
8716
|
if (err === void 0) resolve2(text);
|
|
7837
8717
|
else reject(err);
|
|
7838
8718
|
};
|
|
@@ -7855,16 +8735,16 @@ async function hiddenLine() {
|
|
|
7855
8735
|
}
|
|
7856
8736
|
async function pipedText() {
|
|
7857
8737
|
const chunks = [];
|
|
7858
|
-
for await (const chunk2 of
|
|
8738
|
+
for await (const chunk2 of process21.stdin) chunks.push(chunk2);
|
|
7859
8739
|
return Buffer.concat(chunks).toString("utf8").trim();
|
|
7860
8740
|
}
|
|
7861
8741
|
async function tokenFromStdin() {
|
|
7862
|
-
const token =
|
|
8742
|
+
const token = process21.stdin.isTTY === true ? await hiddenLine() : await pipedText();
|
|
7863
8743
|
if (token === "") throw new UsageError("expected a token on stdin");
|
|
7864
8744
|
if (!token.startsWith(TOKEN_PREFIX)) throw new UsageError(`that is not an rshow token: it must start with ${TOKEN_PREFIX}`);
|
|
7865
8745
|
return token;
|
|
7866
8746
|
}
|
|
7867
|
-
async function run19(argv, ctx) {
|
|
8747
|
+
async function run19(argv, ctx, platform = process21.platform) {
|
|
7868
8748
|
const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGIN_USAGE);
|
|
7869
8749
|
const settings = await ctx.config();
|
|
7870
8750
|
const given = (stringValue(args, "url") ?? "").trim();
|
|
@@ -7873,32 +8753,45 @@ async function run19(argv, ctx) {
|
|
|
7873
8753
|
const me = (await new Client(url, token).send("GET", "/api/me")).body;
|
|
7874
8754
|
const email = typeof me?.email === "string" ? me.email : "";
|
|
7875
8755
|
const scope = typeof me?.scope === "string" ? me.scope : "";
|
|
7876
|
-
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
|
+
}
|
|
7877
8761
|
ctx.out.data({ email, scope, url });
|
|
7878
|
-
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}`);
|
|
7879
8763
|
}
|
|
7880
8764
|
|
|
7881
8765
|
// src/commands/logout.ts
|
|
7882
|
-
|
|
8766
|
+
import process22 from "node:process";
|
|
8767
|
+
async function run20(argv, ctx, platform = process22.platform) {
|
|
7883
8768
|
const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
|
|
7884
8769
|
const settings = await ctx.config();
|
|
7885
8770
|
const given = (stringValue(args, "url") ?? "").trim();
|
|
7886
8771
|
const url = (given === "" ? requireUrl(settings) : given).replace(/\/+$/, "");
|
|
7887
|
-
const removed =
|
|
7888
|
-
|
|
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 ")}`);
|
|
7889
8781
|
}
|
|
7890
8782
|
|
|
7891
8783
|
// src/main.ts
|
|
7892
8784
|
var COMMANDS = {
|
|
7893
|
-
publish: { usage: PUBLISH_USAGE, run
|
|
7894
|
-
check: { usage: CHECK_USAGE, run:
|
|
8785
|
+
publish: { usage: PUBLISH_USAGE, run },
|
|
8786
|
+
check: { usage: CHECK_USAGE, run: run2 },
|
|
7895
8787
|
handoff: { usage: HANDOFF_USAGE, run: run16 },
|
|
7896
8788
|
compare: { usage: COMPARE_USAGE, run: run17 },
|
|
7897
|
-
preview: { usage: PREVIEW_USAGE, run:
|
|
7898
|
-
config: { usage: CONFIG_USAGE, run:
|
|
7899
|
-
init: { usage: INIT_USAGE, run:
|
|
7900
|
-
doctor: { usage: DOCTOR_USAGE, run:
|
|
7901
|
-
theme: { usage: THEME_USAGE, run:
|
|
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 },
|
|
7902
8795
|
project: { usage: PROJECT_USAGE, run: run14 },
|
|
7903
8796
|
tag: { usage: TAG_USAGE, run: run15 },
|
|
7904
8797
|
list: { usage: LIST_USAGE, run: run9 },
|
|
@@ -7913,7 +8806,7 @@ var COMMANDS = {
|
|
|
7913
8806
|
};
|
|
7914
8807
|
function fail(err) {
|
|
7915
8808
|
const message = err instanceof Error ? err.message : String(err);
|
|
7916
|
-
|
|
8809
|
+
process23.stderr.write(`rshow: ${message}
|
|
7917
8810
|
`);
|
|
7918
8811
|
if (err instanceof UsageError) return EXIT.usage;
|
|
7919
8812
|
if (err instanceof CheckError) return EXIT.check;
|
|
@@ -7930,17 +8823,18 @@ async function main(argv) {
|
|
|
7930
8823
|
const name = args.find((arg) => !arg.startsWith("-"));
|
|
7931
8824
|
const spec = name === void 0 ? void 0 : COMMANDS[name];
|
|
7932
8825
|
if (wantsVersion) {
|
|
7933
|
-
|
|
8826
|
+
const suffix = await isBinary() ? " (binary)" : "";
|
|
8827
|
+
process23.stdout.write(`rshow ${VERSION}${suffix}
|
|
7934
8828
|
`);
|
|
7935
8829
|
return EXIT.ok;
|
|
7936
8830
|
}
|
|
7937
8831
|
if (wantsHelp) {
|
|
7938
|
-
|
|
8832
|
+
process23.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
|
|
7939
8833
|
`);
|
|
7940
8834
|
return EXIT.ok;
|
|
7941
8835
|
}
|
|
7942
8836
|
if (name === void 0) {
|
|
7943
|
-
|
|
8837
|
+
process23.stderr.write(`${GENERAL_USAGE}
|
|
7944
8838
|
`);
|
|
7945
8839
|
return EXIT.usage;
|
|
7946
8840
|
}
|
|
@@ -7951,12 +8845,12 @@ async function main(argv) {
|
|
|
7951
8845
|
const rest = [...args.slice(0, index), ...args.slice(index + 1)];
|
|
7952
8846
|
const configs = /* @__PURE__ */ new Map();
|
|
7953
8847
|
const clients = /* @__PURE__ */ new Map();
|
|
7954
|
-
const keyOf = (dir) => dir === void 0 ? "" :
|
|
8848
|
+
const keyOf = (dir) => dir === void 0 ? "" : path25.resolve(dir);
|
|
7955
8849
|
const configFor = (dir) => {
|
|
7956
8850
|
const key2 = keyOf(dir);
|
|
7957
8851
|
let pending = configs.get(key2);
|
|
7958
8852
|
if (pending === void 0) {
|
|
7959
|
-
pending = loadConfig(
|
|
8853
|
+
pending = loadConfig(process23.env, dir);
|
|
7960
8854
|
configs.set(key2, pending);
|
|
7961
8855
|
}
|
|
7962
8856
|
return pending;
|
|
@@ -7969,7 +8863,7 @@ async function main(argv) {
|
|
|
7969
8863
|
if (cached !== void 0) return cached;
|
|
7970
8864
|
const settings = await configFor(dir);
|
|
7971
8865
|
const url = requireUrl(settings);
|
|
7972
|
-
const client = new Client(url, await readToken(
|
|
8866
|
+
const client = new Client(url, await readToken(process23.env, url));
|
|
7973
8867
|
clients.set(key2, client);
|
|
7974
8868
|
return client;
|
|
7975
8869
|
},
|
|
@@ -7978,4 +8872,11 @@ async function main(argv) {
|
|
|
7978
8872
|
const code = await spec.run(rest, ctx);
|
|
7979
8873
|
return code ?? EXIT.ok;
|
|
7980
8874
|
}
|
|
7981
|
-
|
|
8875
|
+
main(process23.argv.slice(2)).then(
|
|
8876
|
+
(code) => {
|
|
8877
|
+
process23.exitCode = code;
|
|
8878
|
+
},
|
|
8879
|
+
(err) => {
|
|
8880
|
+
process23.exitCode = fail(err);
|
|
8881
|
+
}
|
|
8882
|
+
);
|