@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/README.md +123 -43
- package/dist/{chunk-DKGBLJ4H.js → chunk-QKXUKHXB.js} +1087 -82
- package/dist/chunk-QKXUKHXB.js.map +1 -0
- package/dist/cli.js +118 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +132 -2
- package/dist/index.js +3 -1
- package/flags.json +1 -1
- package/package.json +2 -2
- package/rules.json +105 -10
- package/dist/chunk-DKGBLJ4H.js.map +0 -1
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-
|
|
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 ${
|
|
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 &&
|
|
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
|
-
//
|
|
1260
|
-
//
|
|
1261
|
-
//
|
|
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
|
|
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
|
|
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,
|
|
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, {
|
|
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
|
-
|
|
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
|