@goflag/cli 0.2.10 → 0.2.12

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/cli.js CHANGED
@@ -6,13 +6,15 @@ import {
6
6
  PROFILE_NAMES,
7
7
  PROSE_RULES,
8
8
  RULES,
9
+ SITE_PROSE_RULES,
9
10
  SITE_RULES,
10
11
  exitCode,
12
+ renderPreview,
11
13
  renderSummaryTerminal,
12
14
  renderTerminal,
13
15
  runAudit,
14
16
  summarize
15
- } from "./chunk-DKGBLJ4H.js";
17
+ } from "./chunk-QKXUKHXB.js";
16
18
 
17
19
  // src/report/diff.ts
18
20
  function collectFindings(report) {
@@ -312,7 +314,9 @@ var FLAGS = [
312
314
  group: "output",
313
315
  help: [
314
316
  "Roll findings up (dedup by link/rule/code). Pairs",
315
- "with --json for a compact, agent-friendly payload."
317
+ "with --json for a compact, agent-friendly payload.",
318
+ "Not available with --baseline: there the diff is the",
319
+ "answer, and --report writes the full report."
316
320
  ],
317
321
  takesValue: false,
318
322
  apply: ({ parsed }) => {
@@ -753,6 +757,16 @@ var COMMANDS = [
753
757
  "table this help is rendered from, so the two cannot",
754
758
  "disagree."
755
759
  ]
760
+ },
761
+ {
762
+ name: "preview",
763
+ usage: "preview <url>",
764
+ help: [
765
+ "Write .goflag/preview.html \u2014 the page's share cards",
766
+ "as Google, Open Graph, X, LinkedIn, Slack, Discord",
767
+ "and WhatsApp make them, each with the rigor of its",
768
+ "source. Never gates: exits 0 unless the run failed."
769
+ ]
756
770
  }
757
771
  ];
758
772
  var DESCRIPTION_COLUMN = 25;
@@ -771,8 +785,9 @@ function renderHelp() {
771
785
  ];
772
786
  for (const command of COMMANDS) {
773
787
  const pad = " ".repeat(28);
788
+ const label = command.usage ?? command.name;
774
789
  command.help.forEach((line, i) => {
775
- lines.push(i === 0 ? ` goflag ${command.name.padEnd(19)}${line}` : `${pad}${line}`);
790
+ lines.push(i === 0 ? ` goflag ${label.padEnd(19)}${line}` : `${pad}${line}`);
776
791
  });
777
792
  }
778
793
  lines.push("", "Options:");
@@ -786,6 +801,9 @@ function renderHelp() {
786
801
  // src/cli-args.ts
787
802
  var HELP = renderHelp();
788
803
  var COMMAND_NAMES = COMMANDS.map((c) => c.name);
804
+ function isCommand(arg) {
805
+ return COMMAND_NAMES.includes(arg);
806
+ }
789
807
  function parseArgs(argv) {
790
808
  const parsed = {
791
809
  json: false,
@@ -818,10 +836,25 @@ function parseArgs(argv) {
818
836
  continue;
819
837
  }
820
838
  if (arg && arg.startsWith("-")) throw new Error(`unknown option: ${arg}`);
821
- if (arg && COMMAND_NAMES.includes(arg) && i === 0) parsed.command = arg;
839
+ if (arg && isCommand(arg) && !parsed.url && !parsed.command) parsed.command = arg;
822
840
  else if (arg && !parsed.url) parsed.url = arg;
823
841
  else if (arg) throw new Error(`unexpected argument: ${arg}`);
824
842
  }
843
+ if (parsed.command === "preview") {
844
+ const inert = [
845
+ parsed.json && "--json",
846
+ parsed.summary && "--summary",
847
+ parsed.baseline && "--baseline",
848
+ parsed.updateBaseline && "--update-baseline",
849
+ parsed.regressionsOnly && "--regressions-only",
850
+ parsed.maxDebt !== void 0 && "--max-debt"
851
+ ].filter((flag) => typeof flag === "string");
852
+ if (inert.length > 0) {
853
+ throw new Error(
854
+ `preview renders what the crawl saw, it does not gate or reformat, so ${inert.join(", ")} would change nothing: drop them, or ask for the JSON with --report <file>`
855
+ );
856
+ }
857
+ }
825
858
  if (parsed.updateBaseline && !parsed.baseline) {
826
859
  throw new Error("--update-baseline needs a --baseline <file> to write to");
827
860
  }
@@ -833,6 +866,11 @@ function parseArgs(argv) {
833
866
  if (parsed.regressionsOnly && !parsed.baseline) {
834
867
  throw new Error("--regressions-only needs a --baseline <file> to compare against");
835
868
  }
869
+ if (parsed.summary && parsed.baseline) {
870
+ throw new Error(
871
+ "--summary cannot summarise a diff, and --baseline prints the diff: drop --summary, or use --report <file> to keep the full report"
872
+ );
873
+ }
836
874
  return parsed;
837
875
  }
838
876
 
@@ -1256,9 +1294,11 @@ function buildRuleCatalog(version) {
1256
1294
  severity: rule.severity,
1257
1295
  kind: "boolean",
1258
1296
  summary: rule.summary,
1259
- // `null` where a cross-page rule has not declared one yet — the three
1260
- // that predate the field still need their sources chosen rather than
1261
- // guessed, and emitting the gap is how it stays visible.
1297
+ // Kept nullable, and no longer used: every cross-page rule cites its
1298
+ // sources since 2026-08-15. The last holdout could not, so it stopped
1299
+ // being a rule — `./site-prose.ts` asks it as a question instead. The
1300
+ // `?? null` stays because the field is optional on `SiteRule`, not
1301
+ // because a gap is expected.
1262
1302
  rigor: rule.rigor ?? null,
1263
1303
  sources: rule.sources ?? []
1264
1304
  });
@@ -1278,6 +1318,19 @@ function buildRuleCatalog(version) {
1278
1318
  prose: rule.prose
1279
1319
  });
1280
1320
  }
1321
+ for (const rule of SITE_PROSE_RULES) {
1322
+ rules.push({
1323
+ id: rule.id,
1324
+ scope: "prose",
1325
+ severity: null,
1326
+ kind: "prose",
1327
+ summary: rule.summary,
1328
+ why: rule.why,
1329
+ rigor: rule.rigor ?? null,
1330
+ sources: [...rule.sources ?? []],
1331
+ prose: rule.prose
1332
+ });
1333
+ }
1281
1334
  rules.sort((a, b) => a.id.localeCompare(b.id));
1282
1335
  const cited = new Set(rules.flatMap((r) => r.sources));
1283
1336
  const sources = {};
@@ -1298,7 +1351,11 @@ function buildRuleCatalog(version) {
1298
1351
  counts: {
1299
1352
  page: RULES.length,
1300
1353
  site: SITE_RULES.length,
1301
- prose: PROSE_RULES.length
1354
+ // Both registries, because both land in the `prose` bucket above and a
1355
+ // count that disagreed with the list it summarises is worse than no
1356
+ // count. Counted off the registries rather than by filtering `rules`, so
1357
+ // the two stay independently derived and a mismatch shows up as one.
1358
+ prose: PROSE_RULES.length + SITE_PROSE_RULES.length
1302
1359
  },
1303
1360
  sources,
1304
1361
  rules
@@ -1350,13 +1407,17 @@ async function readVersion() {
1350
1407
  return "0.0.0";
1351
1408
  }
1352
1409
  }
1353
- async function writeJson(path, value) {
1410
+ async function writeText(path, contents) {
1354
1411
  const { mkdirSync, writeFileSync } = await import("fs");
1355
1412
  const { dirname } = await import("path");
1356
1413
  mkdirSync(dirname(path), { recursive: true });
1357
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
1358
- `, "utf8");
1414
+ writeFileSync(path, contents, "utf8");
1359
1415
  }
1416
+ async function writeJson(path, value) {
1417
+ await writeText(path, `${JSON.stringify(value, null, 2)}
1418
+ `);
1419
+ }
1420
+ var PREVIEW_PATH = ".goflag/preview.html";
1360
1421
  async function main() {
1361
1422
  let args;
1362
1423
  try {
@@ -1413,7 +1474,14 @@ ${HELP}
1413
1474
  });
1414
1475
  }
1415
1476
  logger.note(`goflag: auditing ${args.url} \u2026`);
1416
- report = await runAudit(args.url, { ...args.options, onProgress: logger.onProgress });
1477
+ report = await runAudit(args.url, {
1478
+ ...args.options,
1479
+ // The preview is drawn from what the pages declared, and only this
1480
+ // section carries that. Nothing else asks for it, so nothing else pays
1481
+ // for it.
1482
+ ...args.command === "preview" ? { extractions: true } : {},
1483
+ onProgress: logger.onProgress
1484
+ });
1417
1485
  logger.stop();
1418
1486
  } catch (err) {
1419
1487
  logger.stop();
@@ -1433,6 +1501,22 @@ goflag: retry, or pass --no-sitemap to audit by crawling on purpose.
1433
1501
  );
1434
1502
  return 2;
1435
1503
  }
1504
+ if (args.command === "preview") {
1505
+ if (args.report) {
1506
+ await writeJson(args.report, report);
1507
+ process.stderr.write(`goflag: report written to ${args.report}
1508
+ `);
1509
+ }
1510
+ await writeText(PREVIEW_PATH, renderPreview(report));
1511
+ const pages = report.extractions?.length ?? 0;
1512
+ process.stderr.write(
1513
+ `goflag: preview written to ${PREVIEW_PATH} \u2014 ${pages} page${pages === 1 ? "" : "s"}
1514
+ `
1515
+ );
1516
+ process.stdout.write(`${PREVIEW_PATH}
1517
+ `);
1518
+ return 0;
1519
+ }
1436
1520
  if (args.baseline) {
1437
1521
  const { existsSync, readFileSync } = await import("fs");
1438
1522
  if (existsSync(args.baseline) || !args.updateBaseline) {
@@ -1448,6 +1532,11 @@ goflag: retry, or pass --no-sitemap to audit by crawling on purpose.
1448
1532
  }
1449
1533
  }
1450
1534
  }
1535
+ if (args.report) {
1536
+ await writeJson(args.report, report);
1537
+ process.stderr.write(`goflag: report written to ${args.report}
1538
+ `);
1539
+ }
1451
1540
  if (args.updateBaseline && args.baseline) {
1452
1541
  await writeJson(args.baseline, report);
1453
1542
  const total2 = totalFindings(report);
@@ -1473,11 +1562,6 @@ goflag: retry, or pass --no-sitemap to audit by crawling on purpose.
1473
1562
  );
1474
1563
  return 0;
1475
1564
  }
1476
- if (args.report) {
1477
- await writeJson(args.report, report);
1478
- process.stderr.write(`goflag: report written to ${args.report}
1479
- `);
1480
- }
1481
1565
  if (report.diff && !args.json) {
1482
1566
  process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}
1483
1567
  `);
@@ -1517,9 +1601,25 @@ goflag: retry, or pass --no-sitemap to audit by crawling on purpose.
1517
1601
  }
1518
1602
  return exitCode(report, args.failOn);
1519
1603
  }
1520
- main().then((code) => process.exit(code)).catch((err) => {
1604
+ function flush(stream) {
1605
+ return new Promise((resolve) => {
1606
+ if (stream.writableEnded || stream.destroyed) return resolve();
1607
+ stream.write("", () => resolve());
1608
+ });
1609
+ }
1610
+ var undelivered = false;
1611
+ for (const stream of [process.stdout, process.stderr]) {
1612
+ stream.on("error", (err) => {
1613
+ if (err.code !== "EPIPE") undelivered = true;
1614
+ });
1615
+ }
1616
+ main().then(async (code) => {
1617
+ await Promise.all([flush(process.stdout), flush(process.stderr)]);
1618
+ process.exit(undelivered ? 2 : code);
1619
+ }).catch(async (err) => {
1521
1620
  process.stderr.write(`goflag: unexpected error: ${err.stack ?? err}
1522
1621
  `);
1622
+ await flush(process.stderr);
1523
1623
  process.exit(2);
1524
1624
  });
1525
1625
  //# sourceMappingURL=cli.js.map