ymmv-cli 0.6.0 → 0.6.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +71 -42
  2. package/package.json +1 -1
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(message) {
274
- super(message);
273
+ constructor(message2) {
274
+ super(message2);
275
275
  this.name = "ProfileParseError";
276
276
  }
277
277
  };
@@ -353,6 +353,10 @@ var NO_CODES = { amber: "", faint: "", bold: "", reset: "" };
353
353
  function palette(color) {
354
354
  return color ? CODES : NO_CODES;
355
355
  }
356
+ function message(text) {
357
+ return `
358
+ ${text.split(/\r?\n/).map((l) => l ? ` ${l}` : l).join("\n")}`;
359
+ }
356
360
  var ESC_INTRODUCERS = `${String.fromCharCode(27)}${String.fromCharCode(155)}`;
357
361
  var BEL = String.fromCharCode(7);
358
362
  var ANSI_RE = new RegExp(
@@ -449,7 +453,6 @@ function renderProfile(profile, opts) {
449
453
  if (!preview) {
450
454
  lines.push("", ` ${c.faint}updated ${relTime(profile.updated_at, opts.now)}${c.reset}`);
451
455
  }
452
- lines.push("");
453
456
  return lines.join("\n");
454
457
  }
455
458
  var MISSING = "\u2014";
@@ -499,20 +502,18 @@ function renderDiff(result, opts) {
499
502
  }
500
503
  }
501
504
  lines.push(...extrasBlock(result.extras, theirsLabel, mineLabel, c));
502
- lines.push("", ` ${c.faint}${result.differ} differ ${result.shared} shared${c.reset}`, "");
505
+ lines.push("", ` ${c.faint}${result.differ} differ ${result.shared} shared${c.reset}`);
503
506
  return lines.join("\n");
504
507
  }
505
508
  function nudge(color) {
506
509
  const c = palette(color);
507
510
  return `
508
- ${c.amber}publish yours to diff \u2192${c.reset} run ${c.bold}ymmv${c.reset}
509
- `;
511
+ ${c.amber}publish yours to diff \u2192${c.reset} run ${c.bold}ymmv${c.reset}`;
510
512
  }
511
513
  function notFound(handle, color, base) {
512
514
  return `
513
515
  no ymmv profile for "${sanitizeValue(handle)}" yet.
514
- publish one at ${link(base, color)} with: npx ymmv-cli
515
- `;
516
+ publish one at ${link(base, color)} with: npx ymmv-cli`;
516
517
  }
517
518
 
518
519
  // src/http.ts
@@ -574,7 +575,7 @@ async function mintYmmvToken(accessToken) {
574
575
  if (res.status === 429) {
575
576
  const retry = res.headers.get("retry-after");
576
577
  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);
578
+ throw new Error(retry && /^\d+$/.test(retry) ? `${msg} (retry in ${retry}s)` : msg);
578
579
  }
579
580
  throw new Error(`login failed: ${res.status} ${wireText(body.error ?? "")}`.trim());
580
581
  }
@@ -754,11 +755,11 @@ async function login(deps = {}) {
754
755
  const c = palette(color);
755
756
  const verifyUri = /^https:\/\/github\.com\//.test(dc.verification_uri) ? link(dc.verification_uri, color) : sanitizeValue(dc.verification_uri);
756
757
  console.log(
757
- `
758
- Open ${verifyUri} and enter code: ${c.bold}${sanitizeValue(dc.user_code)}${c.reset}`
758
+ message(
759
+ `Open ${verifyUri} and enter code: ${c.bold}${sanitizeValue(dc.user_code)}${c.reset}
760
+ ${c.faint}waiting for GitHub approval\u2026 (Ctrl+C to cancel)${c.reset}`
761
+ )
759
762
  );
760
- console.log(` ${c.faint}waiting for GitHub approval\u2026 (Ctrl+C to cancel)${c.reset}
761
- `);
762
763
  const accessToken = await pollForToken(dc, deps);
763
764
  const { token, handle } = await mintYmmvToken(accessToken);
764
765
  try {
@@ -769,8 +770,9 @@ async function login(deps = {}) {
769
770
  throw e;
770
771
  }
771
772
  console.log(
772
- handle ? ` Logged in as ${handle}.
773
- ` : " Logged in. No handle bound (your GitHub username is a reserved word).\n"
773
+ message(
774
+ handle ? `Logged in as ${handle}.` : "Logged in. No handle bound (your GitHub username is a reserved word)."
775
+ )
774
776
  );
775
777
  }
776
778
 
@@ -783,7 +785,7 @@ async function rateLimitMessage(res) {
783
785
  if (typeof body?.message === "string" && body.message) msg = wireText(body.message);
784
786
  } catch {
785
787
  }
786
- return retry ? `${msg} (retry in ${retry}s)` : msg;
788
+ return retry && /^\d+$/.test(retry) ? `${msg} (retry in ${retry}s)` : msg;
787
789
  }
788
790
  async function ensureLogin() {
789
791
  const existing = await loadToken();
@@ -1201,15 +1203,25 @@ function makePrompter() {
1201
1203
  const answer = (await question(promptLine(label, def, color))).trim();
1202
1204
  return answer === "" ? clean ?? "" : answer;
1203
1205
  },
1206
+ // Prompts are output units (render.ts convention): confirm/choice open with the unit's one
1207
+ // blank line here — never in the caller's question string. Field ask()s stay tight: the
1208
+ // 13-key walk is a single unit opened by its hint line.
1204
1209
  async confirm(q, defYes) {
1205
- const answer = (await question(` ${q} ${c.faint}[${defYes ? "Y/n" : "y/N"}]${c.reset} `)).trim().toLowerCase();
1210
+ const answer = (await question(`
1211
+ ${q} ${c.faint}[${defYes ? "Y/n" : "y/N"}]${c.reset} `)).trim().toLowerCase();
1206
1212
  if (answer === "") return defYes;
1207
1213
  return answer === "y" || answer === "yes";
1208
1214
  },
1209
1215
  async choice(q, keys, def, hint) {
1216
+ let prefix = "\n";
1210
1217
  for (; ; ) {
1211
- const hit = matchChoice(await question(` ${q} ${c.faint}[${hint}]${c.reset} `), keys, def);
1218
+ const hit = matchChoice(
1219
+ await question(`${prefix} ${q} ${c.faint}[${hint}]${c.reset} `),
1220
+ keys,
1221
+ def
1222
+ );
1212
1223
  if (hit !== null) return hit;
1224
+ prefix = "";
1213
1225
  }
1214
1226
  },
1215
1227
  close() {
@@ -1223,7 +1235,9 @@ function makePrompter() {
1223
1235
  function requireHandle(cred) {
1224
1236
  if (cred.handle) return cred.handle;
1225
1237
  console.error(
1226
- "Your GitHub username is a reserved word, so no handle is bound. Rename on GitHub, then run `ymmv login` again."
1238
+ message(
1239
+ "Your GitHub username is a reserved word, so no handle is bound. Rename on GitHub, then run `ymmv login` again."
1240
+ )
1227
1241
  );
1228
1242
  process.exitCode = 1;
1229
1243
  return null;
@@ -1245,7 +1259,7 @@ function newProfile(handle, entries, extras) {
1245
1259
  };
1246
1260
  }
1247
1261
  function printPublished(res, color) {
1248
- console.log(`Published ${res.handle} \u2192 ${link(res.url, color)}`);
1262
+ console.log(message(`Published ${res.handle} \u2192 ${link(res.url, color)}`));
1249
1263
  }
1250
1264
  function pagePointer(handle) {
1251
1265
  const color = colorEnabled();
@@ -1254,8 +1268,7 @@ function pagePointer(handle) {
1254
1268
  }
1255
1269
  async function promptEntries(defaults, prompter) {
1256
1270
  const c = palette(colorEnabled());
1257
- console.log(`
1258
- ${c.faint}Enter to keep, "-" to clear${c.reset}`);
1271
+ console.log(message(`${c.faint}Enter to keep, "-" to clear${c.reset}`));
1259
1272
  const chosen = /* @__PURE__ */ new Map();
1260
1273
  for (const key of CURATED_KEYS) {
1261
1274
  const answer = (await prompter.ask(KEY_LABELS[key], defaults.get(key))).trim();
@@ -1266,7 +1279,9 @@ async function promptEntries(defaults, prompter) {
1266
1279
  }
1267
1280
  async function publish(io) {
1268
1281
  if (!io.interactive && !io.yes) {
1269
- console.error("Non-interactive publish needs -y (nothing publishes unconfirmed): ymmv -y");
1282
+ console.error(
1283
+ message("Non-interactive publish needs -y (nothing publishes unconfirmed): ymmv -y")
1284
+ );
1270
1285
  process.exitCode = 1;
1271
1286
  return;
1272
1287
  }
@@ -1291,11 +1306,12 @@ async function publish(io) {
1291
1306
  console.log(
1292
1307
  renderProfile(newProfile(handle, entries, extras), { color, site, mode: "preview" })
1293
1308
  );
1309
+ const notes = [];
1294
1310
  if (carried.length > 0) {
1295
1311
  const s = carried.length === 1 ? "" : "s";
1296
- console.log(`(+${carried.length} newer field${s} kept as-is; upgrade ymmv-cli to edit them)`);
1312
+ notes.push(`(+${carried.length} newer field${s} kept as-is; upgrade ymmv-cli to edit them)`);
1297
1313
  for (const e of carried) {
1298
- console.log(` ${sanitizeValue(e.key)} = ${sanitizeValue(e.value)}`);
1314
+ notes.push(` ${sanitizeValue(e.key)} = ${sanitizeValue(e.value)}`);
1299
1315
  }
1300
1316
  }
1301
1317
  const publishedLabels = new Set(
@@ -1304,9 +1320,10 @@ async function publish(io) {
1304
1320
  for (const x of extras) {
1305
1321
  if (publishedLabels.has(x.label.trim().toLowerCase())) {
1306
1322
  const label = sanitizeValue(x.label.trim());
1307
- console.log(`(extra "${label}" duplicates a curated field; ymmv unset --extra "${label}")`);
1323
+ notes.push(`(extra "${label}" duplicates a curated field; ymmv unset --extra "${label}")`);
1308
1324
  }
1309
1325
  }
1326
+ if (notes.length > 0) console.log(message(notes.join("\n")));
1310
1327
  };
1311
1328
  let values = defaults;
1312
1329
  const assemble = () => [...entriesFromMap(values), ...carried];
@@ -1332,14 +1349,15 @@ async function publish(io) {
1332
1349
  return;
1333
1350
  }
1334
1351
  if (ans === "n") {
1335
- console.log("Aborted. Nothing published.");
1352
+ console.log(message("Aborted. Nothing published."));
1336
1353
  return;
1337
1354
  }
1338
1355
  values = await promptEntries(values, io.prompter);
1339
1356
  }
1340
1357
  } catch (e) {
1341
1358
  if (e instanceof PromptAborted) {
1342
- console.log("\nAborted. Nothing published.");
1359
+ console.log(`
1360
+ ${message("Aborted. Nothing published.")}`);
1343
1361
  process.exitCode = 130;
1344
1362
  return;
1345
1363
  }
@@ -1379,7 +1397,7 @@ async function runSet(target) {
1379
1397
  const { entries, extras } = applySet(existing, target);
1380
1398
  const res = await publishProfile(newProfile(handle, entries, extras));
1381
1399
  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)}`);
1400
+ console.log(message(`${line}${pagePointer(res.handle)}`));
1383
1401
  }
1384
1402
  async function runUnset(target) {
1385
1403
  const cred = await ensureLogin();
@@ -1388,19 +1406,21 @@ async function runUnset(target) {
1388
1406
  const existing = await fetchProfileJson(handle);
1389
1407
  assertHandleUnchanged(existing, handle);
1390
1408
  if (!existing) {
1391
- console.log("No profile yet. Run `ymmv` to publish one.");
1409
+ console.log(message("No profile yet. Run `ymmv` to publish one."));
1392
1410
  return;
1393
1411
  }
1394
1412
  const { entries, extras, removed } = applyUnset(existing, target);
1395
1413
  if (!removed) {
1396
1414
  console.log(
1397
- target.kind === "curated" ? `${KEY_LABELS[target.key]} is not set.` : `No extra "${target.label}".`
1415
+ message(
1416
+ target.kind === "curated" ? `${KEY_LABELS[target.key]} is not set.` : `No extra "${target.label}".`
1417
+ )
1398
1418
  );
1399
1419
  return;
1400
1420
  }
1401
1421
  const res = await publishProfile(newProfile(handle, entries, extras));
1402
1422
  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)}`);
1423
+ console.log(message(`${line}${pagePointer(res.handle)}`));
1404
1424
  }
1405
1425
  async function runDelete(io) {
1406
1426
  const cred = await ensureLogin();
@@ -1408,7 +1428,9 @@ async function runDelete(io) {
1408
1428
  if (!io.yes) {
1409
1429
  if (!io.interactive || !io.prompter) {
1410
1430
  console.error(
1411
- `Refusing to delete ${target} without confirmation. Re-run with -y to confirm: ymmv delete -y`
1431
+ message(
1432
+ `Refusing to delete ${target} without confirmation. Re-run with -y to confirm: ymmv delete -y`
1433
+ )
1412
1434
  );
1413
1435
  process.exitCode = 1;
1414
1436
  return;
@@ -1418,20 +1440,21 @@ async function runDelete(io) {
1418
1440
  go = await io.prompter.confirm(`Delete ${target}? This is permanent`, false);
1419
1441
  } catch (e) {
1420
1442
  if (e instanceof PromptAborted) {
1421
- console.log("\nCancelled. Nothing deleted.");
1443
+ console.log(`
1444
+ ${message("Cancelled. Nothing deleted.")}`);
1422
1445
  process.exitCode = 130;
1423
1446
  return;
1424
1447
  }
1425
1448
  throw e;
1426
1449
  }
1427
1450
  if (!go) {
1428
- console.log("Cancelled. Nothing deleted.");
1451
+ console.log(message("Cancelled. Nothing deleted."));
1429
1452
  return;
1430
1453
  }
1431
1454
  }
1432
1455
  await deleteProfile();
1433
1456
  await deleteToken();
1434
- console.log(`Deleted ${target}. Run \`ymmv\` to publish again.`);
1457
+ console.log(message(`Deleted ${target}. Run \`ymmv\` to publish again.`));
1435
1458
  }
1436
1459
 
1437
1460
  // src/resolve.ts
@@ -1541,7 +1564,9 @@ async function logout() {
1541
1564
  if (!stored) {
1542
1565
  const otherBase = await peekBase();
1543
1566
  console.log(
1544
- 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."
1567
+ message(
1568
+ 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."
1569
+ )
1545
1570
  );
1546
1571
  return;
1547
1572
  }
@@ -1550,13 +1575,15 @@ async function logout() {
1550
1575
  revoked = await revokeYmmvToken(stored.token);
1551
1576
  } catch {
1552
1577
  console.error(
1553
- "Couldn't reach the server to revoke. Your token is still active. Run `ymmv logout` again when connected."
1578
+ message(
1579
+ "Couldn't reach the server to revoke. Your token is still active. Run `ymmv logout` again when connected."
1580
+ )
1554
1581
  );
1555
1582
  process.exitCode = 1;
1556
1583
  return;
1557
1584
  }
1558
1585
  await deleteToken();
1559
- console.log(revoked ? "Logged out." : "Logged out (no active session on this server).");
1586
+ console.log(message(revoked ? "Logged out." : "Logged out (no active session on this server)."));
1560
1587
  }
1561
1588
  function printVersion() {
1562
1589
  try {
@@ -1596,7 +1623,7 @@ async function main(argv) {
1596
1623
  case "login": {
1597
1624
  await login();
1598
1625
  const c = palette(colorEnabled());
1599
- console.log(` ${c.faint}next: run ymmv to publish your stack${c.reset}`);
1626
+ console.log(message(`${c.faint}next: run ymmv to publish your stack${c.reset}`));
1600
1627
  break;
1601
1628
  }
1602
1629
  case "logout":
@@ -1609,7 +1636,7 @@ async function main(argv) {
1609
1636
  printVersion();
1610
1637
  break;
1611
1638
  case "error":
1612
- console.error(cmd.message);
1639
+ console.error(message(cmd.message));
1613
1640
  process.exitCode = 1;
1614
1641
  break;
1615
1642
  }
@@ -1617,6 +1644,8 @@ async function main(argv) {
1617
1644
 
1618
1645
  // src/cli.ts
1619
1646
  main(process.argv.slice(2)).catch((err) => {
1620
- console.error(err instanceof Error ? err.message : String(err));
1647
+ console.error(message(err instanceof Error ? err.message : String(err)));
1621
1648
  process.exitCode = 1;
1649
+ }).finally(() => {
1650
+ (process.exitCode ? process.stderr : process.stdout).write("\n");
1622
1651
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ymmv-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Publish and diff terminal-native developer tool-stack profiles at ymmv.fyi.",
5
5
  "type": "module",
6
6
  "bin": {