ymmv-cli 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +81 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ymmv-cli
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The tools you actually use. Publish from the CLI, diff against anyone's.**
|
|
4
4
|
|
|
5
|
-
Editor, OS, shell, terminal, theme
|
|
5
|
+
Editor, OS, shell, terminal, theme (and more), published to a
|
|
6
6
|
clean page at `ymmv.fyi/<handle>` in about 10 seconds. See a live one:
|
|
7
7
|
[ymmv.fyi/bardisty](https://ymmv.fyi/bardisty).
|
|
8
8
|
|
package/dist/cli.js
CHANGED
|
@@ -270,8 +270,8 @@ var MAX_PARSE_EXTRAS = 256;
|
|
|
270
270
|
var MAX_PARSE_VALUE = 4096;
|
|
271
271
|
var MAX_PARSE_LABEL = 4096;
|
|
272
272
|
var ProfileParseError = class extends Error {
|
|
273
|
-
constructor(
|
|
274
|
-
super(
|
|
273
|
+
constructor(message2) {
|
|
274
|
+
super(message2);
|
|
275
275
|
this.name = "ProfileParseError";
|
|
276
276
|
}
|
|
277
277
|
};
|
|
@@ -286,8 +286,14 @@ function parseProfile(raw) {
|
|
|
286
286
|
}
|
|
287
287
|
if (typeof raw.handle !== "string")
|
|
288
288
|
throw new ProfileParseError("handle is not a string");
|
|
289
|
+
if (raw.handle.length > MAX_PARSE_LABEL) {
|
|
290
|
+
throw new ProfileParseError(`handle exceeds ${MAX_PARSE_LABEL} chars`);
|
|
291
|
+
}
|
|
289
292
|
if (typeof raw.updated_at !== "string")
|
|
290
293
|
throw new ProfileParseError("updated_at is not a string");
|
|
294
|
+
if (raw.updated_at.length > MAX_PARSE_LABEL) {
|
|
295
|
+
throw new ProfileParseError(`updated_at exceeds ${MAX_PARSE_LABEL} chars`);
|
|
296
|
+
}
|
|
291
297
|
if (!Array.isArray(raw.entries))
|
|
292
298
|
throw new ProfileParseError("entries is not an array");
|
|
293
299
|
if (raw.entries.length > MAX_PARSE_ENTRIES) {
|
|
@@ -299,6 +305,9 @@ function parseProfile(raw) {
|
|
|
299
305
|
if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.value !== "string") {
|
|
300
306
|
throw new ProfileParseError(`entry ${i} is not {key,value} strings`);
|
|
301
307
|
}
|
|
308
|
+
if (entry.key.length > MAX_PARSE_LABEL) {
|
|
309
|
+
throw new ProfileParseError(`entry ${i} key exceeds ${MAX_PARSE_LABEL} chars`);
|
|
310
|
+
}
|
|
302
311
|
if (entry.value.length > MAX_PARSE_VALUE) {
|
|
303
312
|
throw new ProfileParseError(`entry ${i} value exceeds ${MAX_PARSE_VALUE} chars`);
|
|
304
313
|
}
|
|
@@ -330,7 +339,7 @@ function parseProfile(raw) {
|
|
|
330
339
|
}
|
|
331
340
|
|
|
332
341
|
// ../shared/dist/reserved.js
|
|
333
|
-
var RESERVED_ROUTES = ["api", "login", "logout"];
|
|
342
|
+
var RESERVED_ROUTES = ["404", "api", "login", "logout"];
|
|
334
343
|
var CLI_VERBS = ["login", "logout", "set", "unset", "delete", "view", "help"];
|
|
335
344
|
var RESERVED = [.../* @__PURE__ */ new Set([...RESERVED_ROUTES, ...CLI_VERBS])];
|
|
336
345
|
var RESERVED_SET = new Set(RESERVED);
|
|
@@ -353,6 +362,10 @@ var NO_CODES = { amber: "", faint: "", bold: "", reset: "" };
|
|
|
353
362
|
function palette(color) {
|
|
354
363
|
return color ? CODES : NO_CODES;
|
|
355
364
|
}
|
|
365
|
+
function message(text) {
|
|
366
|
+
return `
|
|
367
|
+
${text.split(/\r?\n/).map((l) => l ? ` ${l}` : l).join("\n")}`;
|
|
368
|
+
}
|
|
356
369
|
var ESC_INTRODUCERS = `${String.fromCharCode(27)}${String.fromCharCode(155)}`;
|
|
357
370
|
var BEL = String.fromCharCode(7);
|
|
358
371
|
var ANSI_RE = new RegExp(
|
|
@@ -449,7 +462,6 @@ function renderProfile(profile, opts) {
|
|
|
449
462
|
if (!preview) {
|
|
450
463
|
lines.push("", ` ${c.faint}updated ${relTime(profile.updated_at, opts.now)}${c.reset}`);
|
|
451
464
|
}
|
|
452
|
-
lines.push("");
|
|
453
465
|
return lines.join("\n");
|
|
454
466
|
}
|
|
455
467
|
var MISSING = "\u2014";
|
|
@@ -499,20 +511,18 @@ function renderDiff(result, opts) {
|
|
|
499
511
|
}
|
|
500
512
|
}
|
|
501
513
|
lines.push(...extrasBlock(result.extras, theirsLabel, mineLabel, c));
|
|
502
|
-
lines.push("", ` ${c.faint}${result.differ} differ ${result.shared} shared${c.reset}
|
|
514
|
+
lines.push("", ` ${c.faint}${result.differ} differ ${result.shared} shared${c.reset}`);
|
|
503
515
|
return lines.join("\n");
|
|
504
516
|
}
|
|
505
517
|
function nudge(color) {
|
|
506
518
|
const c = palette(color);
|
|
507
519
|
return `
|
|
508
|
-
${c.amber}publish yours to diff \u2192${c.reset} run ${c.bold}ymmv${c.reset}
|
|
509
|
-
`;
|
|
520
|
+
${c.amber}publish yours to diff \u2192${c.reset} run ${c.bold}ymmv${c.reset}`;
|
|
510
521
|
}
|
|
511
522
|
function notFound(handle, color, base) {
|
|
512
523
|
return `
|
|
513
524
|
no ymmv profile for "${sanitizeValue(handle)}" yet.
|
|
514
|
-
publish one at ${link(base, color)} with: npx ymmv-cli
|
|
515
|
-
`;
|
|
525
|
+
publish one at ${link(base, color)} with: npx ymmv-cli`;
|
|
516
526
|
}
|
|
517
527
|
|
|
518
528
|
// src/http.ts
|
|
@@ -574,7 +584,7 @@ async function mintYmmvToken(accessToken) {
|
|
|
574
584
|
if (res.status === 429) {
|
|
575
585
|
const retry = res.headers.get("retry-after");
|
|
576
586
|
const msg = body.message ? wireText(body.message) : "Too many login attempts. Slow down and try again shortly";
|
|
577
|
-
throw new Error(retry ? `${msg} (retry in ${retry}s)` : msg);
|
|
587
|
+
throw new Error(retry && /^\d+$/.test(retry) ? `${msg} (retry in ${retry}s)` : msg);
|
|
578
588
|
}
|
|
579
589
|
throw new Error(`login failed: ${res.status} ${wireText(body.error ?? "")}`.trim());
|
|
580
590
|
}
|
|
@@ -754,11 +764,11 @@ async function login(deps = {}) {
|
|
|
754
764
|
const c = palette(color);
|
|
755
765
|
const verifyUri = /^https:\/\/github\.com\//.test(dc.verification_uri) ? link(dc.verification_uri, color) : sanitizeValue(dc.verification_uri);
|
|
756
766
|
console.log(
|
|
757
|
-
|
|
758
|
-
|
|
767
|
+
message(
|
|
768
|
+
`Open ${verifyUri} and enter code: ${c.bold}${sanitizeValue(dc.user_code)}${c.reset}
|
|
769
|
+
${c.faint}waiting for GitHub approval\u2026 (Ctrl+C to cancel)${c.reset}`
|
|
770
|
+
)
|
|
759
771
|
);
|
|
760
|
-
console.log(` ${c.faint}waiting for GitHub approval\u2026 (Ctrl+C to cancel)${c.reset}
|
|
761
|
-
`);
|
|
762
772
|
const accessToken = await pollForToken(dc, deps);
|
|
763
773
|
const { token, handle } = await mintYmmvToken(accessToken);
|
|
764
774
|
try {
|
|
@@ -769,8 +779,9 @@ async function login(deps = {}) {
|
|
|
769
779
|
throw e;
|
|
770
780
|
}
|
|
771
781
|
console.log(
|
|
772
|
-
|
|
773
|
-
` : "
|
|
782
|
+
message(
|
|
783
|
+
handle ? `Logged in as ${handle}.` : "Logged in. No handle bound (your GitHub username is a reserved word)."
|
|
784
|
+
)
|
|
774
785
|
);
|
|
775
786
|
}
|
|
776
787
|
|
|
@@ -783,7 +794,7 @@ async function rateLimitMessage(res) {
|
|
|
783
794
|
if (typeof body?.message === "string" && body.message) msg = wireText(body.message);
|
|
784
795
|
} catch {
|
|
785
796
|
}
|
|
786
|
-
return retry ? `${msg} (retry in ${retry}s)` : msg;
|
|
797
|
+
return retry && /^\d+$/.test(retry) ? `${msg} (retry in ${retry}s)` : msg;
|
|
787
798
|
}
|
|
788
799
|
async function ensureLogin() {
|
|
789
800
|
const existing = await loadToken();
|
|
@@ -1201,15 +1212,25 @@ function makePrompter() {
|
|
|
1201
1212
|
const answer = (await question(promptLine(label, def, color))).trim();
|
|
1202
1213
|
return answer === "" ? clean ?? "" : answer;
|
|
1203
1214
|
},
|
|
1215
|
+
// Prompts are output units (render.ts convention): confirm/choice open with the unit's one
|
|
1216
|
+
// blank line here — never in the caller's question string. Field ask()s stay tight: the
|
|
1217
|
+
// 13-key walk is a single unit opened by its hint line.
|
|
1204
1218
|
async confirm(q, defYes) {
|
|
1205
|
-
const answer = (await question(`
|
|
1219
|
+
const answer = (await question(`
|
|
1220
|
+
${q} ${c.faint}[${defYes ? "Y/n" : "y/N"}]${c.reset} `)).trim().toLowerCase();
|
|
1206
1221
|
if (answer === "") return defYes;
|
|
1207
1222
|
return answer === "y" || answer === "yes";
|
|
1208
1223
|
},
|
|
1209
1224
|
async choice(q, keys, def, hint) {
|
|
1225
|
+
let prefix = "\n";
|
|
1210
1226
|
for (; ; ) {
|
|
1211
|
-
const hit = matchChoice(
|
|
1227
|
+
const hit = matchChoice(
|
|
1228
|
+
await question(`${prefix} ${q} ${c.faint}[${hint}]${c.reset} `),
|
|
1229
|
+
keys,
|
|
1230
|
+
def
|
|
1231
|
+
);
|
|
1212
1232
|
if (hit !== null) return hit;
|
|
1233
|
+
prefix = "";
|
|
1213
1234
|
}
|
|
1214
1235
|
},
|
|
1215
1236
|
close() {
|
|
@@ -1223,7 +1244,9 @@ function makePrompter() {
|
|
|
1223
1244
|
function requireHandle(cred) {
|
|
1224
1245
|
if (cred.handle) return cred.handle;
|
|
1225
1246
|
console.error(
|
|
1226
|
-
|
|
1247
|
+
message(
|
|
1248
|
+
"Your GitHub username is a reserved word, so no handle is bound. Rename on GitHub, then run `ymmv login` again."
|
|
1249
|
+
)
|
|
1227
1250
|
);
|
|
1228
1251
|
process.exitCode = 1;
|
|
1229
1252
|
return null;
|
|
@@ -1245,7 +1268,7 @@ function newProfile(handle, entries, extras) {
|
|
|
1245
1268
|
};
|
|
1246
1269
|
}
|
|
1247
1270
|
function printPublished(res, color) {
|
|
1248
|
-
console.log(`Published ${res.handle} \u2192 ${link(res.url, color)}`);
|
|
1271
|
+
console.log(message(`Published ${res.handle} \u2192 ${link(res.url, color)}`));
|
|
1249
1272
|
}
|
|
1250
1273
|
function pagePointer(handle) {
|
|
1251
1274
|
const color = colorEnabled();
|
|
@@ -1254,8 +1277,7 @@ function pagePointer(handle) {
|
|
|
1254
1277
|
}
|
|
1255
1278
|
async function promptEntries(defaults, prompter) {
|
|
1256
1279
|
const c = palette(colorEnabled());
|
|
1257
|
-
console.log(`
|
|
1258
|
-
${c.faint}Enter to keep, "-" to clear${c.reset}`);
|
|
1280
|
+
console.log(message(`${c.faint}Enter to keep, "-" to clear${c.reset}`));
|
|
1259
1281
|
const chosen = /* @__PURE__ */ new Map();
|
|
1260
1282
|
for (const key of CURATED_KEYS) {
|
|
1261
1283
|
const answer = (await prompter.ask(KEY_LABELS[key], defaults.get(key))).trim();
|
|
@@ -1266,7 +1288,9 @@ async function promptEntries(defaults, prompter) {
|
|
|
1266
1288
|
}
|
|
1267
1289
|
async function publish(io) {
|
|
1268
1290
|
if (!io.interactive && !io.yes) {
|
|
1269
|
-
console.error(
|
|
1291
|
+
console.error(
|
|
1292
|
+
message("Non-interactive publish needs -y (nothing publishes unconfirmed): ymmv -y")
|
|
1293
|
+
);
|
|
1270
1294
|
process.exitCode = 1;
|
|
1271
1295
|
return;
|
|
1272
1296
|
}
|
|
@@ -1291,11 +1315,12 @@ async function publish(io) {
|
|
|
1291
1315
|
console.log(
|
|
1292
1316
|
renderProfile(newProfile(handle, entries, extras), { color, site, mode: "preview" })
|
|
1293
1317
|
);
|
|
1318
|
+
const notes = [];
|
|
1294
1319
|
if (carried.length > 0) {
|
|
1295
1320
|
const s = carried.length === 1 ? "" : "s";
|
|
1296
|
-
|
|
1321
|
+
notes.push(`(+${carried.length} newer field${s} kept as-is; upgrade ymmv-cli to edit them)`);
|
|
1297
1322
|
for (const e of carried) {
|
|
1298
|
-
|
|
1323
|
+
notes.push(` ${sanitizeValue(e.key)} = ${sanitizeValue(e.value)}`);
|
|
1299
1324
|
}
|
|
1300
1325
|
}
|
|
1301
1326
|
const publishedLabels = new Set(
|
|
@@ -1304,9 +1329,10 @@ async function publish(io) {
|
|
|
1304
1329
|
for (const x of extras) {
|
|
1305
1330
|
if (publishedLabels.has(x.label.trim().toLowerCase())) {
|
|
1306
1331
|
const label = sanitizeValue(x.label.trim());
|
|
1307
|
-
|
|
1332
|
+
notes.push(`(extra "${label}" duplicates a curated field; ymmv unset --extra "${label}")`);
|
|
1308
1333
|
}
|
|
1309
1334
|
}
|
|
1335
|
+
if (notes.length > 0) console.log(message(notes.join("\n")));
|
|
1310
1336
|
};
|
|
1311
1337
|
let values = defaults;
|
|
1312
1338
|
const assemble = () => [...entriesFromMap(values), ...carried];
|
|
@@ -1332,14 +1358,15 @@ async function publish(io) {
|
|
|
1332
1358
|
return;
|
|
1333
1359
|
}
|
|
1334
1360
|
if (ans === "n") {
|
|
1335
|
-
console.log("Aborted. Nothing published.");
|
|
1361
|
+
console.log(message("Aborted. Nothing published."));
|
|
1336
1362
|
return;
|
|
1337
1363
|
}
|
|
1338
1364
|
values = await promptEntries(values, io.prompter);
|
|
1339
1365
|
}
|
|
1340
1366
|
} catch (e) {
|
|
1341
1367
|
if (e instanceof PromptAborted) {
|
|
1342
|
-
console.log(
|
|
1368
|
+
console.log(`
|
|
1369
|
+
${message("Aborted. Nothing published.")}`);
|
|
1343
1370
|
process.exitCode = 130;
|
|
1344
1371
|
return;
|
|
1345
1372
|
}
|
|
@@ -1379,7 +1406,7 @@ async function runSet(target) {
|
|
|
1379
1406
|
const { entries, extras } = applySet(existing, target);
|
|
1380
1407
|
const res = await publishProfile(newProfile(handle, entries, extras));
|
|
1381
1408
|
const line = target.kind === "curated" ? `Set ${KEY_LABELS[target.key]} = ${target.value}.` : `Set extra ${target.label} = ${target.value}.`;
|
|
1382
|
-
console.log(`${line}${pagePointer(res.handle)}`);
|
|
1409
|
+
console.log(message(`${line}${pagePointer(res.handle)}`));
|
|
1383
1410
|
}
|
|
1384
1411
|
async function runUnset(target) {
|
|
1385
1412
|
const cred = await ensureLogin();
|
|
@@ -1388,19 +1415,21 @@ async function runUnset(target) {
|
|
|
1388
1415
|
const existing = await fetchProfileJson(handle);
|
|
1389
1416
|
assertHandleUnchanged(existing, handle);
|
|
1390
1417
|
if (!existing) {
|
|
1391
|
-
console.log("No profile yet. Run `ymmv` to publish one.");
|
|
1418
|
+
console.log(message("No profile yet. Run `ymmv` to publish one."));
|
|
1392
1419
|
return;
|
|
1393
1420
|
}
|
|
1394
1421
|
const { entries, extras, removed } = applyUnset(existing, target);
|
|
1395
1422
|
if (!removed) {
|
|
1396
1423
|
console.log(
|
|
1397
|
-
|
|
1424
|
+
message(
|
|
1425
|
+
target.kind === "curated" ? `${KEY_LABELS[target.key]} is not set.` : `No extra "${target.label}".`
|
|
1426
|
+
)
|
|
1398
1427
|
);
|
|
1399
1428
|
return;
|
|
1400
1429
|
}
|
|
1401
1430
|
const res = await publishProfile(newProfile(handle, entries, extras));
|
|
1402
1431
|
const line = target.kind === "curated" ? `Removed ${KEY_LABELS[target.key]} (was "${sanitizeValue(removed.value)}").` : `Removed extra "${sanitizeValue(removed.label)}" (was "${sanitizeValue(removed.value)}").`;
|
|
1403
|
-
console.log(`${line}${pagePointer(res.handle)}`);
|
|
1432
|
+
console.log(message(`${line}${pagePointer(res.handle)}`));
|
|
1404
1433
|
}
|
|
1405
1434
|
async function runDelete(io) {
|
|
1406
1435
|
const cred = await ensureLogin();
|
|
@@ -1408,7 +1437,9 @@ async function runDelete(io) {
|
|
|
1408
1437
|
if (!io.yes) {
|
|
1409
1438
|
if (!io.interactive || !io.prompter) {
|
|
1410
1439
|
console.error(
|
|
1411
|
-
|
|
1440
|
+
message(
|
|
1441
|
+
`Refusing to delete ${target} without confirmation. Re-run with -y to confirm: ymmv delete -y`
|
|
1442
|
+
)
|
|
1412
1443
|
);
|
|
1413
1444
|
process.exitCode = 1;
|
|
1414
1445
|
return;
|
|
@@ -1418,20 +1449,21 @@ async function runDelete(io) {
|
|
|
1418
1449
|
go = await io.prompter.confirm(`Delete ${target}? This is permanent`, false);
|
|
1419
1450
|
} catch (e) {
|
|
1420
1451
|
if (e instanceof PromptAborted) {
|
|
1421
|
-
console.log(
|
|
1452
|
+
console.log(`
|
|
1453
|
+
${message("Cancelled. Nothing deleted.")}`);
|
|
1422
1454
|
process.exitCode = 130;
|
|
1423
1455
|
return;
|
|
1424
1456
|
}
|
|
1425
1457
|
throw e;
|
|
1426
1458
|
}
|
|
1427
1459
|
if (!go) {
|
|
1428
|
-
console.log("Cancelled. Nothing deleted.");
|
|
1460
|
+
console.log(message("Cancelled. Nothing deleted."));
|
|
1429
1461
|
return;
|
|
1430
1462
|
}
|
|
1431
1463
|
}
|
|
1432
1464
|
await deleteProfile();
|
|
1433
1465
|
await deleteToken();
|
|
1434
|
-
console.log(`Deleted ${target}. Run \`ymmv\` to publish again.`);
|
|
1466
|
+
console.log(message(`Deleted ${target}. Run \`ymmv\` to publish again.`));
|
|
1435
1467
|
}
|
|
1436
1468
|
|
|
1437
1469
|
// src/resolve.ts
|
|
@@ -1541,7 +1573,9 @@ async function logout() {
|
|
|
1541
1573
|
if (!stored) {
|
|
1542
1574
|
const otherBase = await peekBase();
|
|
1543
1575
|
console.log(
|
|
1544
|
-
|
|
1576
|
+
message(
|
|
1577
|
+
otherBase && otherBase !== BASE ? `Not logged in to ${BASE} (a token for ${otherBase} exists; set YMMV_API to that to log out of it).` : "Not logged in."
|
|
1578
|
+
)
|
|
1545
1579
|
);
|
|
1546
1580
|
return;
|
|
1547
1581
|
}
|
|
@@ -1550,13 +1584,15 @@ async function logout() {
|
|
|
1550
1584
|
revoked = await revokeYmmvToken(stored.token);
|
|
1551
1585
|
} catch {
|
|
1552
1586
|
console.error(
|
|
1553
|
-
|
|
1587
|
+
message(
|
|
1588
|
+
"Couldn't reach the server to revoke. Your token is still active. Run `ymmv logout` again when connected."
|
|
1589
|
+
)
|
|
1554
1590
|
);
|
|
1555
1591
|
process.exitCode = 1;
|
|
1556
1592
|
return;
|
|
1557
1593
|
}
|
|
1558
1594
|
await deleteToken();
|
|
1559
|
-
console.log(revoked ? "Logged out." : "Logged out (no active session on this server).");
|
|
1595
|
+
console.log(message(revoked ? "Logged out." : "Logged out (no active session on this server)."));
|
|
1560
1596
|
}
|
|
1561
1597
|
function printVersion() {
|
|
1562
1598
|
try {
|
|
@@ -1596,7 +1632,7 @@ async function main(argv) {
|
|
|
1596
1632
|
case "login": {
|
|
1597
1633
|
await login();
|
|
1598
1634
|
const c = palette(colorEnabled());
|
|
1599
|
-
console.log(
|
|
1635
|
+
console.log(message(`${c.faint}next: run ymmv to publish your stack${c.reset}`));
|
|
1600
1636
|
break;
|
|
1601
1637
|
}
|
|
1602
1638
|
case "logout":
|
|
@@ -1609,7 +1645,7 @@ async function main(argv) {
|
|
|
1609
1645
|
printVersion();
|
|
1610
1646
|
break;
|
|
1611
1647
|
case "error":
|
|
1612
|
-
console.error(cmd.message);
|
|
1648
|
+
console.error(message(cmd.message));
|
|
1613
1649
|
process.exitCode = 1;
|
|
1614
1650
|
break;
|
|
1615
1651
|
}
|
|
@@ -1617,6 +1653,8 @@ async function main(argv) {
|
|
|
1617
1653
|
|
|
1618
1654
|
// src/cli.ts
|
|
1619
1655
|
main(process.argv.slice(2)).catch((err) => {
|
|
1620
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1656
|
+
console.error(message(err instanceof Error ? err.message : String(err)));
|
|
1621
1657
|
process.exitCode = 1;
|
|
1658
|
+
}).finally(() => {
|
|
1659
|
+
(process.exitCode ? process.stderr : process.stdout).write("\n");
|
|
1622
1660
|
});
|