@goflag/cli 0.2.1 → 0.2.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/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  renderTerminal,
10
10
  runAudit,
11
11
  summarize
12
- } from "./chunk-6WOWKLXY.js";
12
+ } from "./chunk-F74SRZVM.js";
13
13
 
14
14
  // src/report/diff.ts
15
15
  function collectFindings(report) {
@@ -300,6 +300,11 @@ Options:
300
300
  --report <file> Write the (full) JSON report to <file>.
301
301
  --depth <n> Crawl depth (0 = entry page only). Default: 2.
302
302
  --max-pages <n> Hard cap on pages crawled. Default: 200.
303
+ --coverage <mode> How to choose which pages to audit.
304
+ "structural" (default when a sitemap is found) keeps
305
+ every standalone page and samples families of pages
306
+ built from one template. "all" audits everything the
307
+ sitemap lists, up to --max-pages.
303
308
  --include <glob> Only crawl paths matching <glob> (repeatable).
304
309
  --exclude <glob> Skip paths matching <glob> (repeatable).
305
310
  --locales <list> Comma-separated locales the site serves, e.g.
@@ -486,6 +491,15 @@ function parseArgs(argv) {
486
491
  parsed.options.depth = toInt(next(i, arg), arg);
487
492
  i++;
488
493
  break;
494
+ case "--coverage": {
495
+ const mode = next(i, arg);
496
+ if (mode !== "structural" && mode !== "all") {
497
+ throw new Error(`--coverage takes "structural" or "all", not ${JSON.stringify(mode)}`);
498
+ }
499
+ parsed.options.coverage = mode;
500
+ i += 1;
501
+ break;
502
+ }
489
503
  case "--max-pages":
490
504
  parsed.options.maxPages = toInt(next(i, arg), arg);
491
505
  i++;
@@ -540,6 +554,13 @@ async function readVersion() {
540
554
  return "0.0.0";
541
555
  }
542
556
  }
557
+ async function writeJson(path, value) {
558
+ const { mkdirSync, writeFileSync } = await import("fs");
559
+ const { dirname } = await import("path");
560
+ mkdirSync(dirname(path), { recursive: true });
561
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
562
+ `, "utf8");
563
+ }
543
564
  async function main() {
544
565
  let args;
545
566
  try {
@@ -598,6 +619,16 @@ ${HELP}
598
619
  } finally {
599
620
  await server?.stop();
600
621
  }
622
+ const sitemap = report.diagnostics.sitemap;
623
+ if (sitemap?.unreachable) {
624
+ process.stderr.write(
625
+ `goflag: the sitemap could not be fetched \u2014 ${sitemap.unreachable}
626
+ goflag: this run crawled links instead, so it saw a different site from one that reads the sitemap, and its findings are not comparable.
627
+ goflag: retry, or pass --no-sitemap to audit by crawling on purpose.
628
+ `
629
+ );
630
+ return 2;
631
+ }
601
632
  if (args.baseline) {
602
633
  const { existsSync, readFileSync } = await import("fs");
603
634
  if (existsSync(args.baseline) || !args.updateBaseline) {
@@ -614,9 +645,7 @@ ${HELP}
614
645
  }
615
646
  }
616
647
  if (args.updateBaseline && args.baseline) {
617
- const { writeFileSync } = await import("fs");
618
- writeFileSync(args.baseline, `${JSON.stringify(report, null, 2)}
619
- `, "utf8");
648
+ await writeJson(args.baseline, report);
620
649
  const total2 = totalFindings(report);
621
650
  if (report.diff) {
622
651
  const { added, resolved } = report.diff;
@@ -641,9 +670,7 @@ ${HELP}
641
670
  return 0;
642
671
  }
643
672
  if (args.report) {
644
- const { writeFileSync } = await import("fs");
645
- writeFileSync(args.report, `${JSON.stringify(report, null, 2)}
646
- `, "utf8");
673
+ await writeJson(args.report, report);
647
674
  process.stderr.write(`goflag: report written to ${args.report}
648
675
  `);
649
676
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/report/diff.ts","../src/report/render-diff.ts","../src/lib/runner/dev-server.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["/**\n * Baseline comparison.\n *\n * Every finding already carries a `fingerprint` id derived from the *identity*\n * of the problem rather than its transient details, and page URLs are\n * normalised to origin-independent routes — so the same defect fingerprints\n * identically on localhost, staging and production. That machinery was built\n * for exactly this and had no consumer.\n *\n * What it changes is what the tool *is*. Without a baseline the CI gate is\n * binary: any finding fails, which is unusable on a site that is not clean\n * yet, so teams either never turn it on or learn to ignore it. With one, the\n * gate becomes \"no *new* findings\" — a promise a real codebase can keep while\n * the backlog is worked down.\n *\n * Pure function of two reports. No I/O; the CLI reads the baseline file.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport { DEFAULT_PROFILE } from \"../lib/rules/profiles\";\nimport type { GoflagReport } from \"./types\";\n\n/** Which part of the report a finding came from. */\nexport type FindingKind =\n \"brokenLink\" | \"unreachablePage\" | \"translationHole\" | \"reciprocity\" | \"seo\" | \"site\";\n\n/** One finding, flattened out of its report section for comparison. */\nexport interface DiffEntry {\n id: string;\n kind: FindingKind;\n /**\n * Severity used for the exit-code decision. Rule findings carry their own;\n * the rest are mapped to match the verdict logic in `build.ts` — a broken\n * link or an unreachable page is an error, a translation gap is a warning.\n */\n severity: Severity;\n /** One-line description, for the rendered diff. */\n summary: string;\n /** Page the finding sits on, when it has one. */\n pageUrl?: string;\n}\n\nexport interface ReportDiff {\n /** Where, when, and under which rule profile the baseline was taken. */\n baseline: { url: string; finishedAt: string; profile: string };\n /**\n * Set when this run was judged under a different profile than the baseline\n * was captured under. Not an error — comparing `spec-only` against a\n * `default` baseline is legitimate, it is just not like-for-like, and\n * \"0 new findings\" means less than it appears. So it is reported loudly\n * and gates nothing: silently diffing across two policies is the failure\n * mode worth preventing.\n */\n profileMismatch?: { baseline: string; current: string };\n /** Findings present now and absent from the baseline — what should fail CI. */\n added: DiffEntry[];\n /** Findings in the baseline that are gone. Worth showing: progress is data too. */\n resolved: DiffEntry[];\n /** Findings present in both. */\n unchanged: number;\n}\n\n/** Flatten every finding collection into one comparable list. */\nexport function collectFindings(report: GoflagReport): DiffEntry[] {\n const out: DiffEntry[] = [];\n\n for (const link of report.brokenLinks) {\n out.push({\n id: link.id,\n kind: \"brokenLink\",\n severity: \"error\",\n summary: `${link.verdict} link → ${link.href}`,\n pageUrl: link.pageUrl,\n });\n }\n\n for (const page of report.unreachablePages) {\n out.push({\n id: page.id,\n kind: \"unreachablePage\",\n severity: \"error\",\n summary: `${page.status || \"network error\"} on ${page.url}`,\n pageUrl: page.url,\n });\n }\n\n for (const hole of report.missingTranslations.holes) {\n out.push({\n id: hole.id,\n kind: \"translationHole\",\n severity: \"warning\",\n summary: `${hole.route} missing ${hole.missingLocales.join(\", \")}`,\n });\n }\n\n for (const issue of report.missingTranslations.reciprocity) {\n out.push({\n id: issue.id,\n kind: \"reciprocity\",\n severity: \"warning\",\n summary: `${issue.code} on ${issue.url}`,\n pageUrl: issue.url,\n });\n }\n\n for (const issue of report.seoIssues) {\n out.push({\n id: issue.id,\n kind: \"seo\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n for (const issue of report.siteIssues) {\n out.push({\n id: issue.id,\n kind: \"site\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n return out;\n}\n\n/** Deterministic ordering so a rendered diff is stable and reviewable. */\nconst SEVERITY_RANK: Record<Severity, number> = { error: 0, warning: 1, info: 2 };\n\nfunction sortEntries(entries: DiffEntry[]): DiffEntry[] {\n return [...entries].sort(\n (a, b) =>\n SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] ||\n a.kind.localeCompare(b.kind) ||\n a.summary.localeCompare(b.summary),\n );\n}\n\n/** Total findings the site currently carries, new and known alike. */\nexport function totalFindings(report: GoflagReport): number {\n return collectFindings(report).length;\n}\n\n/** Compare a fresh report against a stored baseline. */\nexport function diffReports(baseline: GoflagReport, current: GoflagReport): ReportDiff {\n const before = new Map(collectFindings(baseline).map((e) => [e.id, e]));\n const now = new Map(collectFindings(current).map((e) => [e.id, e]));\n\n const added: DiffEntry[] = [];\n let unchanged = 0;\n for (const [id, entry] of now) {\n if (before.has(id)) unchanged += 1;\n else added.push(entry);\n }\n\n const resolved: DiffEntry[] = [];\n for (const [id, entry] of before) {\n if (!now.has(id)) resolved.push(entry);\n }\n\n // A baseline stored before profiles existed has no `profile`; the behaviour\n // it captured is what `default` still does, so that is the honest reading.\n const baselineProfile = baseline.profile ?? DEFAULT_PROFILE;\n const currentProfile = current.profile ?? DEFAULT_PROFILE;\n\n return {\n baseline: { url: baseline.url, finishedAt: baseline.finishedAt, profile: baselineProfile },\n added: sortEntries(added),\n resolved: sortEntries(resolved),\n unchanged,\n ...(baselineProfile === currentProfile\n ? {}\n : { profileMismatch: { baseline: baselineProfile, current: currentProfile } }),\n };\n}\n\n/**\n * Exit code under `--regressions-only`: fail on findings that are *new* at or\n * above the threshold, or on a debt budget being exceeded.\n *\n * Deliberately blind to the overall verdict, unlike `exitCode`. A site with a\n * hundred known problems and no new ones passes — that is what the mode is\n * for, and why it has to be asked for by name.\n *\n * `maxDebt` is the counterweight. Gating on regressions alone lets a backlog\n * sit untouched forever behind a passing build; a ceiling you lower as you fix\n * is the only part of this design that makes the debt actually go down.\n */\nexport function diffExitCode(\n diff: ReportDiff,\n failOn: \"warning\" | \"error\" | \"never\",\n debt?: { total: number; max?: number },\n): number {\n if (debt?.max !== undefined && debt.total > debt.max) return 1;\n if (failOn === \"never\") return 0;\n const threshold = failOn === \"error\" ? 0 : 1;\n const blocking = diff.added.filter((e) => SEVERITY_RANK[e.severity] <= threshold);\n return blocking.length > 0 ? 1 : 0;\n}\n","/**\n * Render a `ReportDiff` for a terminal.\n *\n * Deliberately leads with what is new, because that is what fails the build\n * and what the reader has to act on. Resolved findings come second and are\n * kept — a run that only ever shows problems gives no signal that the backlog\n * is moving, which is how a gate stops being read.\n *\n * Pure function of the diff; no I/O.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport type { DiffEntry, ReportDiff } from \"./diff\";\n\ninterface RenderOptions {\n color?: boolean;\n /** Injected clock, so the rendered age is testable. */\n now?: number;\n}\n\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n cyan: \"\\x1b[36m\",\n};\n\nfunction paint(enabled: boolean) {\n return (code: string, text: string) => (enabled ? `${code}${text}${ANSI.reset}` : text);\n}\n\nfunction severityTag(c: (code: string, t: string) => string, severity: Severity): string {\n if (severity === \"error\") return c(ANSI.red, \"error\");\n if (severity === \"warning\") return c(ANSI.yellow, \"warn \");\n return c(ANSI.dim, \"info \");\n}\n\n/** How many entries to list before collapsing into a \"+N more\" line. */\nconst LIST_LIMIT = 20;\n\nfunction listEntries(\n entries: DiffEntry[],\n marker: string,\n markerColor: string,\n c: (code: string, t: string) => string,\n): string[] {\n const lines: string[] = [];\n for (const entry of entries.slice(0, LIST_LIMIT)) {\n // A broken link's identity is (page, target): the same dead URL referenced\n // from four pages is four findings, and without the page they render as\n // four identical lines that look like a rendering bug.\n const where =\n entry.pageUrl && !entry.summary.includes(entry.pageUrl)\n ? c(ANSI.dim, ` on ${entry.pageUrl}`)\n : \"\";\n lines.push(\n ` ${c(markerColor, marker)} ${severityTag(c, entry.severity)} ${c(ANSI.dim, entry.kind)} ${entry.summary}${where}`,\n );\n }\n if (entries.length > LIST_LIMIT) {\n lines.push(` ${c(ANSI.dim, `… +${entries.length - LIST_LIMIT} more`)}`);\n }\n return lines;\n}\n\n/** Whole days between the baseline timestamp and now, or null if unparseable. */\nfunction baselineAgeDays(finishedAt: string, now: number): number | null {\n const taken = Date.parse(finishedAt);\n if (Number.isNaN(taken)) return null;\n return Math.max(0, Math.floor((now - taken) / 86_400_000));\n}\n\nexport function renderDiffTerminal(diff: ReportDiff, options: RenderOptions = {}): string {\n const c = paint(options.color ?? false);\n const lines: string[] = [];\n const debt = diff.unchanged;\n\n // Never green, and never the word \"clean\". This mode passes builds on sites\n // with known defects; a green flag would say the opposite of what happened,\n // and a reader who learns to see green here stops reading the number beside\n // it. The debt is the headline, not a footnote.\n const verdict =\n diff.added.length > 0\n ? c(ANSI.red, \"REGRESSION\")\n : debt > 0\n ? c(ANSI.yellow, \"REGRESSION GATE\")\n : c(ANSI.green, \"CLEAN\");\n\n const age = baselineAgeDays(diff.baseline.finishedAt, options.now ?? Date.now());\n\n lines.push(\"\");\n lines.push(`${c(ANSI.bold, \"goflag\")} ${c(ANSI.dim, \"--regressions-only\")}`);\n lines.push(\n `${verdict} ${diff.added.length} new · ` +\n (debt > 0\n ? c(ANSI.bold, `${debt} known findings NOT gating this build`)\n : c(ANSI.dim, \"no known findings\")) +\n (diff.resolved.length > 0 ? c(ANSI.dim, ` · ${diff.resolved.length} resolved`) : \"\"),\n );\n lines.push(\n c(\n ANSI.dim,\n `baseline ${diff.baseline.url} — taken ${diff.baseline.finishedAt}` +\n (age === null ? \"\" : age === 0 ? \" (today)\" : ` (${age} day${age === 1 ? \"\" : \"s\"} ago)`),\n ),\n );\n // Loud, not dim: a cross-profile diff still prints \"0 new\", and that number\n // means something different from what the reader assumes.\n if (diff.profileMismatch) {\n lines.push(\n c(\n ANSI.yellow,\n `note: baseline was captured under profile \\`${diff.profileMismatch.baseline}\\`, ` +\n `this run used \\`${diff.profileMismatch.current}\\` — the two are not like-for-like.`,\n ),\n );\n }\n lines.push(\"\");\n\n if (diff.added.length > 0) {\n lines.push(c(ANSI.bold, \"New findings\"));\n lines.push(...listEntries(diff.added, \"+\", ANSI.red, c));\n lines.push(\"\");\n }\n\n if (diff.resolved.length > 0) {\n lines.push(c(ANSI.bold, \"Resolved\"));\n lines.push(...listEntries(diff.resolved, \"-\", ANSI.green, c));\n lines.push(\"\");\n }\n\n if (diff.added.length === 0 && diff.resolved.length === 0) {\n lines.push(\n c(\n ANSI.dim,\n debt > 0\n ? `Nothing moved since the baseline — ${debt} findings still open. Run without --regressions-only to see them.`\n : \"Nothing moved since the baseline.\",\n ),\n );\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Boot-and-audit support for `--start`.\n *\n * goflag was only ever able to audit a deployed site. That makes it a post-hoc\n * auditor, and post-hoc auditors do not get run: by the time a finding appears,\n * the regression already shipped. `--start` closes that gap — boot the app,\n * wait until it answers, audit `localhost`, tear it down — so the same checks\n * can gate a merge instead of documenting a mistake.\n *\n * Deliberately dumb about readiness: rather than parsing a framework's stdout\n * for \"ready in 1.2s\" (every framework words it differently, and the wording\n * changes between minor versions), it polls the URL goflag is about to audit.\n * If that URL answers, the audit can proceed — which is the only definition of\n * \"ready\" that actually matters here.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nexport interface StartedServer {\n /** Terminate the process group and resolve once it is gone. */\n stop: () => Promise<void>;\n}\n\nexport interface StartServerOptions {\n /** Shell command to run, e.g. `pnpm start`. */\n command: string;\n /**\n * Directory to run it in. Defaults to the process's own, which is right for\n * the usual `cd my-site && npx goflag …` — but not for a monorepo audited\n * from its root, where the app lives a few directories down.\n */\n cwd?: string;\n /** URL polled until it answers; normally the audit entry point. */\n url: string;\n /** Give up after this long. Defaults to 60s. */\n timeoutMs?: number;\n /** Poll interval. Defaults to 250ms. */\n intervalMs?: number;\n /** Called with each stdout/stderr chunk, for `--verbose`. */\n onOutput?: (chunk: string) => void;\n /** Accept self-signed TLS while polling (a local https dev server). */\n allowInsecureTls?: boolean;\n}\n\n/** True once the URL returns any HTTP response at all. */\nasync function answers(url: string, allowInsecureTls: boolean): Promise<boolean> {\n const previous = process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n if (allowInsecureTls) process.env.NODE_TLS_REJECT_UNAUTHORIZED = \"0\";\n try {\n // Any status counts: a 404 on the entry path still proves the server is\n // up, and the audit itself is what judges the response.\n await fetch(url, { redirect: \"manual\" });\n return true;\n } catch {\n return false;\n } finally {\n if (allowInsecureTls) {\n if (previous === undefined) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n else process.env.NODE_TLS_REJECT_UNAUTHORIZED = previous;\n }\n }\n}\n\nfunction terminate(child: ChildProcess): Promise<void> {\n return new Promise((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolve();\n return;\n }\n\n // Kill the whole group: `pnpm start` spawns the real server as a child,\n // and signalling only the wrapper orphans a process holding the port.\n const killGroup = (signal: NodeJS.Signals) => {\n try {\n if (child.pid) process.kill(-child.pid, signal);\n else child.kill(signal);\n } catch {\n // Already dead, or the group is gone — nothing left to do.\n }\n };\n\n const force = setTimeout(() => killGroup(\"SIGKILL\"), 5_000);\n child.once(\"exit\", () => {\n clearTimeout(force);\n resolve();\n });\n killGroup(\"SIGTERM\");\n });\n}\n\n/**\n * Spawn `command` and resolve once `url` answers.\n *\n * Rejects — after stopping the process — when the command exits early or the\n * timeout expires. Both are hard failures: auditing a server that never came\n * up would report a site-wide outage as a hundred SEO findings.\n */\nexport async function startServer(options: StartServerOptions): Promise<StartedServer> {\n const timeoutMs = options.timeoutMs ?? 60_000;\n const intervalMs = options.intervalMs ?? 250;\n const allowInsecureTls = options.allowInsecureTls === true;\n\n const cwd = options.cwd ?? process.cwd();\n const child = spawn(options.command, {\n cwd,\n shell: true,\n // Own process group, so `terminate` can take the whole tree down.\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let exited: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n child.once(\"exit\", (code, signal) => {\n exited = { code, signal };\n });\n\n const tail: string[] = [];\n const record = (chunk: Buffer) => {\n const text = chunk.toString();\n options.onOutput?.(text);\n // Keep a short tail so a boot failure can be reported with its own words\n // instead of a bare \"command exited\".\n tail.push(text);\n if (tail.length > 40) tail.shift();\n };\n child.stdout?.on(\"data\", record);\n child.stderr?.on(\"data\", record);\n\n const server: StartedServer = { stop: () => terminate(child) };\n const deadline = Date.now() + timeoutMs;\n\n for (;;) {\n if (exited) {\n await server.stop();\n const why = exited.signal ? `signal ${exited.signal}` : `exit code ${exited.code}`;\n // Naming the directory is most of the diagnosis: a command that runs\n // fine by hand and dies here has almost always been started somewhere\n // other than where its package.json lives.\n throw new Error(\n `--start command exited before ${options.url} answered (${why}).\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `${tail.join(\"\").trimEnd()}`,\n );\n }\n if (await answers(options.url, allowInsecureTls)) return server;\n if (Date.now() >= deadline) {\n await server.stop();\n throw new Error(\n `--start command did not serve ${options.url} within ${timeoutMs}ms.\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `Raise --start-timeout, check the command listens on that URL, or point ` +\n `--start-cwd at the directory it should run in.`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n","/**\n * CLI argument parsing — split out from `cli.ts` so it can be unit-tested\n * without importing the module's top-level `main()` side effect.\n *\n * `parseArgs` is a pure function of `argv` (plus the ambient TTY / NO_COLOR\n * signals used only for the default color choice). It throws a plain `Error`\n * with a user-facing message on malformed input; the CLI turns that into an\n * exit-code-2 with the help text.\n */\n\nimport { PROFILE_NAMES, PROFILES } from \"./lib/rules/profiles\";\nimport type { AuditOptions, FailOn } from \"./report/build\";\nimport type { LogMode } from \"./report/logger\";\n\n/** `--profile` names and what each one means, indented for the help block. */\nconst PROFILE_HELP = PROFILE_NAMES.map(\n (name) => ` ${name} — ${PROFILES[name]!.description}`,\n).join(\"\\n\");\n\nexport const HELP = `goflag — audit a site for broken links, missing translations, and SEO metadata\n\nUsage:\n goflag <url> [options]\n\nOptions:\n --json Print the JSON report to stdout (nothing else).\n --summary, -s Roll findings up (dedup by link/rule/code). Pairs\n with --json for a compact, agent-friendly payload.\n --report <file> Write the (full) JSON report to <file>.\n --depth <n> Crawl depth (0 = entry page only). Default: 2.\n --max-pages <n> Hard cap on pages crawled. Default: 200.\n --include <glob> Only crawl paths matching <glob> (repeatable).\n --exclude <glob> Skip paths matching <glob> (repeatable).\n --locales <list> Comma-separated locales the site serves, e.g.\n \"fr,en,pt-br\". Authoritative: overrides what the\n sitemap and crawl suggest, and makes a locale the\n site does not serve yet show up as missing.\n --ignore-holes <glob> Route (locale-free) that is deliberately not\n translated everywhere, so its gaps are not reported\n as missing translations (repeatable).\n --no-sitemap Do not discover the sitemap; crawl from <url> only.\n Discovery is on by default because link-only crawling\n cannot find locales a site never links to.\n --profile <name> Policy overlay on the rule set — it changes how\n loudly a rule fires, never what it observes:\n${PROFILE_HELP}\n --conformance Report EVERY rule's status on every page (pass /\n fail / warn / na), not just the violations. Answers\n \"where do we stand against the catalog?\"; pairs with\n --json.\n --advisories Attach the prose rules — the judgment calls goflag\n refuses to fake — each with the observed facts an\n agent needs to judge it. Never affects the gate.\n --fail-on <level> Exit 1 at or above this severity: warning (default),\n error, or never.\n --regressions-only Weaken the gate: fail only on findings that are NEW\n relative to --baseline. Known findings stop blocking\n the build — a green run no longer means a clean site.\n Requires --baseline.\n --baseline <file> Stored report to compare against. Use with\n --regressions-only.\n --update-baseline Write this run to --baseline and exit 0, instead of\n judging against it. Use it to capture a baseline, or\n to accept findings you have decided to live with —\n it says what it accepted.\n --max-debt <n> Fail when the site carries more than <n> findings in\n total, whether new or known. Lower it as you fix, to\n stop a baseline from fossilising.\n --start <cmd> Boot <cmd>, wait for <url> to answer, audit, then\n stop it. Use to gate a merge on the built app before\n it ships.\n --start-cwd <dir> Directory to run --start in. Defaults to the current\n one; set it when auditing a monorepo package from the\n repository root.\n --start-timeout <ms> How long to wait for --start to answer. Default: 60000.\n --no-external Do not probe off-origin (external) links.\n --static Static HTML only; never launch headless Chromium.\n --allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).\n --timeout <ms> Per-request timeout in ms. Default: 8000.\n --verbose, -V Log every page as it is analyzed.\n --quiet, -q Suppress the live progress output.\n --no-color Disable coloured output.\n -h, --help Show this help.\n -v, --version Show the version.\n\nExit codes: 0 clean, 1 findings found, 2 fatal error.`;\n\nexport interface ParsedArgs {\n url?: string;\n json: boolean;\n summary: boolean;\n report?: string;\n color: boolean;\n help: boolean;\n version: boolean;\n /** Live-progress verbosity for the logger. */\n logMode: LogMode;\n /** Severity at or above which the process exits 1. */\n failOn: FailOn;\n /** Path to a stored report to compare against (`--baseline`). */\n baseline?: string;\n /** Gate on new findings only, letting known ones through. */\n regressionsOnly: boolean;\n /** Write the run to `--baseline` rather than judging against it. */\n updateBaseline: boolean;\n /** Hard ceiling on total findings, new or known. */\n maxDebt?: number;\n /** Command to boot before auditing, and stop afterwards (`--start`). */\n start?: string;\n /** How long to wait for `--start` to answer, in ms. */\n startTimeoutMs: number;\n /** Directory to run the `--start` command in. */\n startCwd?: string;\n options: AuditOptions;\n}\n\nconst FAIL_ON_LEVELS: readonly FailOn[] = [\"warning\", \"error\", \"never\"];\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const parsed: ParsedArgs = {\n json: false,\n summary: false,\n color: process.stdout.isTTY === true && !process.env.NO_COLOR,\n help: false,\n version: false,\n logMode: \"compact\",\n failOn: \"warning\",\n regressionsOnly: false,\n updateBaseline: false,\n startTimeoutMs: 60_000,\n options: { include: [], exclude: [] },\n };\n\n const next = (i: number, flag: string): string => {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`${flag} requires a value`);\n return value;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n switch (arg) {\n case \"-h\":\n case \"--help\":\n parsed.help = true;\n break;\n case \"-v\":\n case \"--version\":\n parsed.version = true;\n break;\n case \"--json\":\n parsed.json = true;\n break;\n case \"-s\":\n case \"--summary\":\n parsed.summary = true;\n break;\n case \"--no-color\":\n parsed.color = false;\n break;\n case \"-V\":\n case \"--verbose\":\n parsed.logMode = \"verbose\";\n break;\n case \"-q\":\n case \"--quiet\":\n parsed.logMode = \"quiet\";\n break;\n case \"--static\":\n parsed.options.static = true;\n break;\n case \"--allow-insecure-tls\":\n parsed.options.allowInsecureTls = true;\n break;\n case \"--no-external\":\n parsed.options.checkExternal = false;\n break;\n case \"--no-sitemap\":\n parsed.options.noSitemap = true;\n break;\n case \"--conformance\":\n parsed.options.conformance = true;\n break;\n case \"--advisories\":\n parsed.options.advisories = true;\n break;\n case \"--ignore-holes\":\n parsed.options.ignoreHoles = [...(parsed.options.ignoreHoles ?? []), next(i, arg)];\n i++;\n break;\n case \"--locales\": {\n // Split on commas so `--locales fr,en` and repeated flags both work.\n const value = next(i, arg);\n const tags = value\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n if (tags.length === 0) throw new Error(`${arg} expects at least one locale`);\n parsed.options.locales = [...(parsed.options.locales ?? []), ...tags];\n i++;\n break;\n }\n case \"--profile\": {\n const value = next(i, arg);\n // Validated here, not in `runAudit`: a mistyped policy should cost\n // an argument-parse error, not a crawl.\n if (!PROFILE_NAMES.includes(value)) {\n throw new Error(`${arg} expects one of: ${PROFILE_NAMES.join(\", \")}`);\n }\n parsed.options.profile = value;\n i++;\n break;\n }\n case \"--fail-on\": {\n const value = next(i, arg) as FailOn;\n if (!FAIL_ON_LEVELS.includes(value)) {\n throw new Error(`${arg} expects one of: ${FAIL_ON_LEVELS.join(\", \")}`);\n }\n parsed.failOn = value;\n i++;\n break;\n }\n case \"--start\":\n parsed.start = next(i, arg);\n i++;\n break;\n case \"--start-timeout\":\n parsed.startTimeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--start-cwd\":\n parsed.startCwd = next(i, arg);\n i++;\n break;\n case \"--report\":\n parsed.report = next(i, arg);\n i++;\n break;\n case \"--baseline\":\n parsed.baseline = next(i, arg);\n i++;\n break;\n case \"--update-baseline\":\n parsed.updateBaseline = true;\n break;\n case \"--regressions-only\":\n parsed.regressionsOnly = true;\n break;\n case \"--max-debt\":\n parsed.maxDebt = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--depth\":\n parsed.options.depth = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--max-pages\":\n parsed.options.maxPages = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--timeout\":\n parsed.options.timeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--include\":\n parsed.options.include!.push(next(i, arg));\n i++;\n break;\n case \"--exclude\":\n parsed.options.exclude!.push(next(i, arg));\n i++;\n break;\n default:\n if (arg && arg.startsWith(\"-\")) throw new Error(`unknown option: ${arg}`);\n if (arg && !parsed.url) parsed.url = arg;\n else if (arg) throw new Error(`unexpected argument: ${arg}`);\n }\n }\n\n // Each flag is meaningless without the other, and guessing which one the\n // caller meant would silently change how strict the build is.\n if (parsed.updateBaseline && !parsed.baseline) {\n throw new Error(\"--update-baseline needs a --baseline <file> to write to\");\n }\n // Capturing a baseline is not gating against one, so the explicit opt-in that\n // --regressions-only exists to force does not apply.\n if (parsed.baseline && !parsed.regressionsOnly && !parsed.updateBaseline) {\n throw new Error(\n \"--baseline weakens the gate, so it must be requested explicitly: add --regressions-only\",\n );\n }\n if (parsed.regressionsOnly && !parsed.baseline) {\n throw new Error(\"--regressions-only needs a --baseline <file> to compare against\");\n }\n\n return parsed;\n}\n\nfunction toInt(value: string, flag: string): number {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) throw new Error(`${flag} expects a non-negative integer`);\n return n;\n}\n","/**\n * goflag CLI.\n *\n * goflag <url> [options]\n *\n * Crawls a site and reports broken links, missing translation pages, and\n * missing/misconfigured SEO metadata. The JSON report is the source of\n * truth; the terminal output is rendered from it.\n *\n * Exit codes:\n * 0 clean (green flag)\n * 1 findings present (yellow/red flag) — useful as a CI gate\n * 2 fatal (bad URL, unexpected error)\n */\n\nimport { runAudit, exitCode } from \"./report/build\";\nimport { diffReports, diffExitCode, totalFindings } from \"./report/diff\";\nimport { renderDiffTerminal } from \"./report/render-diff\";\nimport { startServer, type StartedServer } from \"./lib/runner/dev-server\";\nimport { renderTerminal } from \"./report/render-terminal\";\nimport { renderSummaryTerminal } from \"./report/render-summary\";\nimport { summarize } from \"./report/summarize\";\nimport { Logger } from \"./report/logger\";\nimport { HELP, parseArgs, type ParsedArgs } from \"./cli-args\";\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFileSync } = await import(\"node:fs\");\n const { fileURLToPath } = await import(\"node:url\");\n const { dirname, join } = await import(\"node:path\");\n const here = dirname(fileURLToPath(import.meta.url));\n const pkg = JSON.parse(readFileSync(join(here, \"..\", \"package.json\"), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nasync function main(): Promise<number> {\n let args: ParsedArgs;\n try {\n args = parseArgs(process.argv.slice(2));\n } catch (err) {\n process.stderr.write(`goflag: ${(err as Error).message}\\n\\n${HELP}\\n`);\n return 2;\n }\n\n if (args.help) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (args.version) {\n process.stdout.write(`${await readVersion()}\\n`);\n return 0;\n }\n if (!args.url) {\n process.stderr.write(`goflag: missing <url>\\n\\n${HELP}\\n`);\n return 2;\n }\n\n // Progress always goes to stderr, so stdout (JSON / rendered report)\n // stays clean and pipeable. Colour follows the stderr TTY.\n const logger = new Logger({\n stream: process.stderr,\n mode: args.logMode,\n color: process.stderr.isTTY === true && !process.env.NO_COLOR,\n });\n\n // `--start` owns a child process for the whole audit; it must be torn down\n // on every exit path, including a failed audit, or CI leaks a held port.\n let server: StartedServer | undefined;\n let report;\n try {\n if (args.start) {\n logger.note(`goflag: starting \\`${args.start}\\` …`);\n server = await startServer({\n command: args.start,\n cwd: args.startCwd,\n url: args.url,\n timeoutMs: args.startTimeoutMs,\n allowInsecureTls: args.options.allowInsecureTls,\n onOutput: args.logMode === \"verbose\" ? (chunk) => process.stderr.write(chunk) : undefined,\n });\n }\n logger.note(`goflag: auditing ${args.url} …`);\n report = await runAudit(args.url, { ...args.options, onProgress: logger.onProgress });\n logger.stop();\n } catch (err) {\n logger.stop();\n process.stderr.write(`goflag: ${(err as Error).message}\\n`);\n return 2;\n } finally {\n await server?.stop();\n }\n\n // A baseline changes the question from \"is this site clean?\" to \"did this\n // change make it worse?\" — the only one a site with a backlog can answer.\n if (args.baseline) {\n const { existsSync, readFileSync } = await import(\"node:fs\");\n // Capturing a baseline for the first time is the one case where the file\n // is legitimately absent. Everywhere else a missing baseline is a typo,\n // and continuing would turn it into a green build.\n if (existsSync(args.baseline) || !args.updateBaseline) {\n try {\n const baseline = JSON.parse(readFileSync(args.baseline, \"utf8\"));\n report.diff = diffReports(baseline, report);\n } catch (err) {\n process.stderr.write(\n `goflag: could not read baseline ${args.baseline}: ${(err as Error).message}\\n`,\n );\n return 2;\n }\n }\n }\n\n // Writing the baseline is accepting everything in it. The one thing this\n // must not do is accept it quietly: a counter that drops without explanation\n // reads as \"the problem went away\".\n if (args.updateBaseline && args.baseline) {\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(args.baseline, `${JSON.stringify(report, null, 2)}\\n`, \"utf8\");\n const total = totalFindings(report);\n if (report.diff) {\n const { added, resolved } = report.diff;\n process.stdout.write(\n `goflag: baseline updated — ${added.length} newly accepted, ${resolved.length} resolved, ` +\n `${total} findings now grandfathered in ${args.baseline}\\n`,\n );\n if (added.length > 0) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n }\n } else {\n process.stdout.write(\n `goflag: baseline captured — ${total} findings grandfathered in ${args.baseline}\\n`,\n );\n }\n process.stdout.write(\n `goflag: set --max-debt ${total} to stop that number growing, and lower it as you fix.\\n`,\n );\n return 0;\n }\n\n if (args.report) {\n // The report file is always the full report — the source of truth a\n // baseline/diff can rely on, regardless of the --summary view choice.\n const { writeFileSync } = await import(\"node:fs\");\n writeFileSync(args.report, `${JSON.stringify(report, null, 2)}\\n`, \"utf8\");\n process.stderr.write(`goflag: report written to ${args.report}\\n`);\n }\n\n // In baseline mode the diff *is* the answer. Printing the full report first\n // buries \"nothing changed\" under a hundred known findings — the reader stops\n // looking, which is precisely the failure the baseline exists to prevent.\n // The complete report stays available via --json and --report.\n if (report.diff && !args.json) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n }\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n if (args.summary) {\n const summary = summarize(report);\n process.stdout.write(\n args.json\n ? `${JSON.stringify(summary, null, 2)}\\n`\n : `${renderSummaryTerminal(summary, { color: args.color })}\\n`,\n );\n } else {\n process.stdout.write(\n args.json\n ? `${JSON.stringify(report, null, 2)}\\n`\n : `${renderTerminal(report, { color: args.color })}\\n`,\n );\n }\n\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n return 1;\n }\n\n if (report.diff) {\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n return exitCode(report, args.failOn);\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err) => {\n process.stderr.write(`goflag: unexpected error: ${(err as Error).stack ?? err}\\n`);\n process.exit(2);\n });\n"],"mappings":"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","names":["total"]}
1
+ {"version":3,"sources":["../src/report/diff.ts","../src/report/render-diff.ts","../src/lib/runner/dev-server.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["/**\n * Baseline comparison.\n *\n * Every finding already carries a `fingerprint` id derived from the *identity*\n * of the problem rather than its transient details, and page URLs are\n * normalised to origin-independent routes — so the same defect fingerprints\n * identically on localhost, staging and production. That machinery was built\n * for exactly this and had no consumer.\n *\n * What it changes is what the tool *is*. Without a baseline the CI gate is\n * binary: any finding fails, which is unusable on a site that is not clean\n * yet, so teams either never turn it on or learn to ignore it. With one, the\n * gate becomes \"no *new* findings\" — a promise a real codebase can keep while\n * the backlog is worked down.\n *\n * Pure function of two reports. No I/O; the CLI reads the baseline file.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport { DEFAULT_PROFILE } from \"../lib/rules/profiles\";\nimport type { GoflagReport } from \"./types\";\n\n/** Which part of the report a finding came from. */\nexport type FindingKind =\n \"brokenLink\" | \"unreachablePage\" | \"translationHole\" | \"reciprocity\" | \"seo\" | \"site\";\n\n/** One finding, flattened out of its report section for comparison. */\nexport interface DiffEntry {\n id: string;\n kind: FindingKind;\n /**\n * Severity used for the exit-code decision. Rule findings carry their own;\n * the rest are mapped to match the verdict logic in `build.ts` — a broken\n * link or an unreachable page is an error, a translation gap is a warning.\n */\n severity: Severity;\n /** One-line description, for the rendered diff. */\n summary: string;\n /** Page the finding sits on, when it has one. */\n pageUrl?: string;\n}\n\nexport interface ReportDiff {\n /** Where, when, and under which rule profile the baseline was taken. */\n baseline: { url: string; finishedAt: string; profile: string };\n /**\n * Set when this run was judged under a different profile than the baseline\n * was captured under. Not an error — comparing `spec-only` against a\n * `default` baseline is legitimate, it is just not like-for-like, and\n * \"0 new findings\" means less than it appears. So it is reported loudly\n * and gates nothing: silently diffing across two policies is the failure\n * mode worth preventing.\n */\n profileMismatch?: { baseline: string; current: string };\n /** Findings present now and absent from the baseline — what should fail CI. */\n added: DiffEntry[];\n /** Findings in the baseline that are gone. Worth showing: progress is data too. */\n resolved: DiffEntry[];\n /** Findings present in both. */\n unchanged: number;\n}\n\n/** Flatten every finding collection into one comparable list. */\nexport function collectFindings(report: GoflagReport): DiffEntry[] {\n const out: DiffEntry[] = [];\n\n for (const link of report.brokenLinks) {\n out.push({\n id: link.id,\n kind: \"brokenLink\",\n severity: \"error\",\n summary: `${link.verdict} link → ${link.href}`,\n pageUrl: link.pageUrl,\n });\n }\n\n for (const page of report.unreachablePages) {\n out.push({\n id: page.id,\n kind: \"unreachablePage\",\n severity: \"error\",\n summary: `${page.status || \"network error\"} on ${page.url}`,\n pageUrl: page.url,\n });\n }\n\n for (const hole of report.missingTranslations.holes) {\n out.push({\n id: hole.id,\n kind: \"translationHole\",\n severity: \"warning\",\n summary: `${hole.route} missing ${hole.missingLocales.join(\", \")}`,\n });\n }\n\n for (const issue of report.missingTranslations.reciprocity) {\n out.push({\n id: issue.id,\n kind: \"reciprocity\",\n severity: \"warning\",\n summary: `${issue.code} on ${issue.url}`,\n pageUrl: issue.url,\n });\n }\n\n for (const issue of report.seoIssues) {\n out.push({\n id: issue.id,\n kind: \"seo\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n for (const issue of report.siteIssues) {\n out.push({\n id: issue.id,\n kind: \"site\",\n severity: issue.severity,\n summary: `${issue.ruleId} on ${issue.pageUrl}`,\n pageUrl: issue.pageUrl,\n });\n }\n\n return out;\n}\n\n/** Deterministic ordering so a rendered diff is stable and reviewable. */\nconst SEVERITY_RANK: Record<Severity, number> = { error: 0, warning: 1, info: 2 };\n\nfunction sortEntries(entries: DiffEntry[]): DiffEntry[] {\n return [...entries].sort(\n (a, b) =>\n SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] ||\n a.kind.localeCompare(b.kind) ||\n a.summary.localeCompare(b.summary),\n );\n}\n\n/** Total findings the site currently carries, new and known alike. */\nexport function totalFindings(report: GoflagReport): number {\n return collectFindings(report).length;\n}\n\n/** Compare a fresh report against a stored baseline. */\nexport function diffReports(baseline: GoflagReport, current: GoflagReport): ReportDiff {\n const before = new Map(collectFindings(baseline).map((e) => [e.id, e]));\n const now = new Map(collectFindings(current).map((e) => [e.id, e]));\n\n const added: DiffEntry[] = [];\n let unchanged = 0;\n for (const [id, entry] of now) {\n if (before.has(id)) unchanged += 1;\n else added.push(entry);\n }\n\n const resolved: DiffEntry[] = [];\n for (const [id, entry] of before) {\n if (!now.has(id)) resolved.push(entry);\n }\n\n // A baseline stored before profiles existed has no `profile`; the behaviour\n // it captured is what `default` still does, so that is the honest reading.\n const baselineProfile = baseline.profile ?? DEFAULT_PROFILE;\n const currentProfile = current.profile ?? DEFAULT_PROFILE;\n\n return {\n baseline: { url: baseline.url, finishedAt: baseline.finishedAt, profile: baselineProfile },\n added: sortEntries(added),\n resolved: sortEntries(resolved),\n unchanged,\n ...(baselineProfile === currentProfile\n ? {}\n : { profileMismatch: { baseline: baselineProfile, current: currentProfile } }),\n };\n}\n\n/**\n * Exit code under `--regressions-only`: fail on findings that are *new* at or\n * above the threshold, or on a debt budget being exceeded.\n *\n * Deliberately blind to the overall verdict, unlike `exitCode`. A site with a\n * hundred known problems and no new ones passes — that is what the mode is\n * for, and why it has to be asked for by name.\n *\n * `maxDebt` is the counterweight. Gating on regressions alone lets a backlog\n * sit untouched forever behind a passing build; a ceiling you lower as you fix\n * is the only part of this design that makes the debt actually go down.\n */\nexport function diffExitCode(\n diff: ReportDiff,\n failOn: \"warning\" | \"error\" | \"never\",\n debt?: { total: number; max?: number },\n): number {\n if (debt?.max !== undefined && debt.total > debt.max) return 1;\n if (failOn === \"never\") return 0;\n const threshold = failOn === \"error\" ? 0 : 1;\n const blocking = diff.added.filter((e) => SEVERITY_RANK[e.severity] <= threshold);\n return blocking.length > 0 ? 1 : 0;\n}\n","/**\n * Render a `ReportDiff` for a terminal.\n *\n * Deliberately leads with what is new, because that is what fails the build\n * and what the reader has to act on. Resolved findings come second and are\n * kept — a run that only ever shows problems gives no signal that the backlog\n * is moving, which is how a gate stops being read.\n *\n * Pure function of the diff; no I/O.\n */\n\nimport type { Severity } from \"../lib/core/types\";\nimport type { DiffEntry, ReportDiff } from \"./diff\";\n\ninterface RenderOptions {\n color?: boolean;\n /** Injected clock, so the rendered age is testable. */\n now?: number;\n}\n\nconst ANSI = {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n cyan: \"\\x1b[36m\",\n};\n\nfunction paint(enabled: boolean) {\n return (code: string, text: string) => (enabled ? `${code}${text}${ANSI.reset}` : text);\n}\n\nfunction severityTag(c: (code: string, t: string) => string, severity: Severity): string {\n if (severity === \"error\") return c(ANSI.red, \"error\");\n if (severity === \"warning\") return c(ANSI.yellow, \"warn \");\n return c(ANSI.dim, \"info \");\n}\n\n/** How many entries to list before collapsing into a \"+N more\" line. */\nconst LIST_LIMIT = 20;\n\nfunction listEntries(\n entries: DiffEntry[],\n marker: string,\n markerColor: string,\n c: (code: string, t: string) => string,\n): string[] {\n const lines: string[] = [];\n for (const entry of entries.slice(0, LIST_LIMIT)) {\n // A broken link's identity is (page, target): the same dead URL referenced\n // from four pages is four findings, and without the page they render as\n // four identical lines that look like a rendering bug.\n const where =\n entry.pageUrl && !entry.summary.includes(entry.pageUrl)\n ? c(ANSI.dim, ` on ${entry.pageUrl}`)\n : \"\";\n lines.push(\n ` ${c(markerColor, marker)} ${severityTag(c, entry.severity)} ${c(ANSI.dim, entry.kind)} ${entry.summary}${where}`,\n );\n }\n if (entries.length > LIST_LIMIT) {\n lines.push(` ${c(ANSI.dim, `… +${entries.length - LIST_LIMIT} more`)}`);\n }\n return lines;\n}\n\n/** Whole days between the baseline timestamp and now, or null if unparseable. */\nfunction baselineAgeDays(finishedAt: string, now: number): number | null {\n const taken = Date.parse(finishedAt);\n if (Number.isNaN(taken)) return null;\n return Math.max(0, Math.floor((now - taken) / 86_400_000));\n}\n\nexport function renderDiffTerminal(diff: ReportDiff, options: RenderOptions = {}): string {\n const c = paint(options.color ?? false);\n const lines: string[] = [];\n const debt = diff.unchanged;\n\n // Never green, and never the word \"clean\". This mode passes builds on sites\n // with known defects; a green flag would say the opposite of what happened,\n // and a reader who learns to see green here stops reading the number beside\n // it. The debt is the headline, not a footnote.\n const verdict =\n diff.added.length > 0\n ? c(ANSI.red, \"REGRESSION\")\n : debt > 0\n ? c(ANSI.yellow, \"REGRESSION GATE\")\n : c(ANSI.green, \"CLEAN\");\n\n const age = baselineAgeDays(diff.baseline.finishedAt, options.now ?? Date.now());\n\n lines.push(\"\");\n lines.push(`${c(ANSI.bold, \"goflag\")} ${c(ANSI.dim, \"--regressions-only\")}`);\n lines.push(\n `${verdict} ${diff.added.length} new · ` +\n (debt > 0\n ? c(ANSI.bold, `${debt} known findings NOT gating this build`)\n : c(ANSI.dim, \"no known findings\")) +\n (diff.resolved.length > 0 ? c(ANSI.dim, ` · ${diff.resolved.length} resolved`) : \"\"),\n );\n lines.push(\n c(\n ANSI.dim,\n `baseline ${diff.baseline.url} — taken ${diff.baseline.finishedAt}` +\n (age === null ? \"\" : age === 0 ? \" (today)\" : ` (${age} day${age === 1 ? \"\" : \"s\"} ago)`),\n ),\n );\n // Loud, not dim: a cross-profile diff still prints \"0 new\", and that number\n // means something different from what the reader assumes.\n if (diff.profileMismatch) {\n lines.push(\n c(\n ANSI.yellow,\n `note: baseline was captured under profile \\`${diff.profileMismatch.baseline}\\`, ` +\n `this run used \\`${diff.profileMismatch.current}\\` — the two are not like-for-like.`,\n ),\n );\n }\n lines.push(\"\");\n\n if (diff.added.length > 0) {\n lines.push(c(ANSI.bold, \"New findings\"));\n lines.push(...listEntries(diff.added, \"+\", ANSI.red, c));\n lines.push(\"\");\n }\n\n if (diff.resolved.length > 0) {\n lines.push(c(ANSI.bold, \"Resolved\"));\n lines.push(...listEntries(diff.resolved, \"-\", ANSI.green, c));\n lines.push(\"\");\n }\n\n if (diff.added.length === 0 && diff.resolved.length === 0) {\n lines.push(\n c(\n ANSI.dim,\n debt > 0\n ? `Nothing moved since the baseline — ${debt} findings still open. Run without --regressions-only to see them.`\n : \"Nothing moved since the baseline.\",\n ),\n );\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Boot-and-audit support for `--start`.\n *\n * goflag was only ever able to audit a deployed site. That makes it a post-hoc\n * auditor, and post-hoc auditors do not get run: by the time a finding appears,\n * the regression already shipped. `--start` closes that gap — boot the app,\n * wait until it answers, audit `localhost`, tear it down — so the same checks\n * can gate a merge instead of documenting a mistake.\n *\n * Deliberately dumb about readiness: rather than parsing a framework's stdout\n * for \"ready in 1.2s\" (every framework words it differently, and the wording\n * changes between minor versions), it polls the URL goflag is about to audit.\n * If that URL answers, the audit can proceed — which is the only definition of\n * \"ready\" that actually matters here.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\n\nexport interface StartedServer {\n /** Terminate the process group and resolve once it is gone. */\n stop: () => Promise<void>;\n}\n\nexport interface StartServerOptions {\n /** Shell command to run, e.g. `pnpm start`. */\n command: string;\n /**\n * Directory to run it in. Defaults to the process's own, which is right for\n * the usual `cd my-site && npx goflag …` — but not for a monorepo audited\n * from its root, where the app lives a few directories down.\n */\n cwd?: string;\n /** URL polled until it answers; normally the audit entry point. */\n url: string;\n /** Give up after this long. Defaults to 60s. */\n timeoutMs?: number;\n /** Poll interval. Defaults to 250ms. */\n intervalMs?: number;\n /** Called with each stdout/stderr chunk, for `--verbose`. */\n onOutput?: (chunk: string) => void;\n /** Accept self-signed TLS while polling (a local https dev server). */\n allowInsecureTls?: boolean;\n}\n\n/** True once the URL returns any HTTP response at all. */\nasync function answers(url: string, allowInsecureTls: boolean): Promise<boolean> {\n const previous = process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n if (allowInsecureTls) process.env.NODE_TLS_REJECT_UNAUTHORIZED = \"0\";\n try {\n // Any status counts: a 404 on the entry path still proves the server is\n // up, and the audit itself is what judges the response.\n await fetch(url, { redirect: \"manual\" });\n return true;\n } catch {\n return false;\n } finally {\n if (allowInsecureTls) {\n if (previous === undefined) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;\n else process.env.NODE_TLS_REJECT_UNAUTHORIZED = previous;\n }\n }\n}\n\nfunction terminate(child: ChildProcess): Promise<void> {\n return new Promise((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) {\n resolve();\n return;\n }\n\n // Kill the whole group: `pnpm start` spawns the real server as a child,\n // and signalling only the wrapper orphans a process holding the port.\n const killGroup = (signal: NodeJS.Signals) => {\n try {\n if (child.pid) process.kill(-child.pid, signal);\n else child.kill(signal);\n } catch {\n // Already dead, or the group is gone — nothing left to do.\n }\n };\n\n const force = setTimeout(() => killGroup(\"SIGKILL\"), 5_000);\n child.once(\"exit\", () => {\n clearTimeout(force);\n resolve();\n });\n killGroup(\"SIGTERM\");\n });\n}\n\n/**\n * Spawn `command` and resolve once `url` answers.\n *\n * Rejects — after stopping the process — when the command exits early or the\n * timeout expires. Both are hard failures: auditing a server that never came\n * up would report a site-wide outage as a hundred SEO findings.\n */\nexport async function startServer(options: StartServerOptions): Promise<StartedServer> {\n const timeoutMs = options.timeoutMs ?? 60_000;\n const intervalMs = options.intervalMs ?? 250;\n const allowInsecureTls = options.allowInsecureTls === true;\n\n const cwd = options.cwd ?? process.cwd();\n const child = spawn(options.command, {\n cwd,\n shell: true,\n // Own process group, so `terminate` can take the whole tree down.\n detached: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n let exited: { code: number | null; signal: NodeJS.Signals | null } | undefined;\n child.once(\"exit\", (code, signal) => {\n exited = { code, signal };\n });\n\n const tail: string[] = [];\n const record = (chunk: Buffer) => {\n const text = chunk.toString();\n options.onOutput?.(text);\n // Keep a short tail so a boot failure can be reported with its own words\n // instead of a bare \"command exited\".\n tail.push(text);\n if (tail.length > 40) tail.shift();\n };\n child.stdout?.on(\"data\", record);\n child.stderr?.on(\"data\", record);\n\n const server: StartedServer = { stop: () => terminate(child) };\n const deadline = Date.now() + timeoutMs;\n\n for (;;) {\n if (exited) {\n await server.stop();\n const why = exited.signal ? `signal ${exited.signal}` : `exit code ${exited.code}`;\n // Naming the directory is most of the diagnosis: a command that runs\n // fine by hand and dies here has almost always been started somewhere\n // other than where its package.json lives.\n throw new Error(\n `--start command exited before ${options.url} answered (${why}).\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `${tail.join(\"\").trimEnd()}`,\n );\n }\n if (await answers(options.url, allowInsecureTls)) return server;\n if (Date.now() >= deadline) {\n await server.stop();\n throw new Error(\n `--start command did not serve ${options.url} within ${timeoutMs}ms.\\n` +\n ` command: ${options.command}\\n` +\n ` in: ${cwd}\\n` +\n `Raise --start-timeout, check the command listens on that URL, or point ` +\n `--start-cwd at the directory it should run in.`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n}\n","/**\n * CLI argument parsing — split out from `cli.ts` so it can be unit-tested\n * without importing the module's top-level `main()` side effect.\n *\n * `parseArgs` is a pure function of `argv` (plus the ambient TTY / NO_COLOR\n * signals used only for the default color choice). It throws a plain `Error`\n * with a user-facing message on malformed input; the CLI turns that into an\n * exit-code-2 with the help text.\n */\n\nimport { PROFILE_NAMES, PROFILES } from \"./lib/rules/profiles\";\nimport type { AuditOptions, FailOn } from \"./report/build\";\nimport type { LogMode } from \"./report/logger\";\n\n/** `--profile` names and what each one means, indented for the help block. */\nconst PROFILE_HELP = PROFILE_NAMES.map(\n (name) => ` ${name} — ${PROFILES[name]!.description}`,\n).join(\"\\n\");\n\nexport const HELP = `goflag — audit a site for broken links, missing translations, and SEO metadata\n\nUsage:\n goflag <url> [options]\n\nOptions:\n --json Print the JSON report to stdout (nothing else).\n --summary, -s Roll findings up (dedup by link/rule/code). Pairs\n with --json for a compact, agent-friendly payload.\n --report <file> Write the (full) JSON report to <file>.\n --depth <n> Crawl depth (0 = entry page only). Default: 2.\n --max-pages <n> Hard cap on pages crawled. Default: 200.\n --coverage <mode> How to choose which pages to audit.\n \"structural\" (default when a sitemap is found) keeps\n every standalone page and samples families of pages\n built from one template. \"all\" audits everything the\n sitemap lists, up to --max-pages.\n --include <glob> Only crawl paths matching <glob> (repeatable).\n --exclude <glob> Skip paths matching <glob> (repeatable).\n --locales <list> Comma-separated locales the site serves, e.g.\n \"fr,en,pt-br\". Authoritative: overrides what the\n sitemap and crawl suggest, and makes a locale the\n site does not serve yet show up as missing.\n --ignore-holes <glob> Route (locale-free) that is deliberately not\n translated everywhere, so its gaps are not reported\n as missing translations (repeatable).\n --no-sitemap Do not discover the sitemap; crawl from <url> only.\n Discovery is on by default because link-only crawling\n cannot find locales a site never links to.\n --profile <name> Policy overlay on the rule set — it changes how\n loudly a rule fires, never what it observes:\n${PROFILE_HELP}\n --conformance Report EVERY rule's status on every page (pass /\n fail / warn / na), not just the violations. Answers\n \"where do we stand against the catalog?\"; pairs with\n --json.\n --advisories Attach the prose rules — the judgment calls goflag\n refuses to fake — each with the observed facts an\n agent needs to judge it. Never affects the gate.\n --fail-on <level> Exit 1 at or above this severity: warning (default),\n error, or never.\n --regressions-only Weaken the gate: fail only on findings that are NEW\n relative to --baseline. Known findings stop blocking\n the build — a green run no longer means a clean site.\n Requires --baseline.\n --baseline <file> Stored report to compare against. Use with\n --regressions-only.\n --update-baseline Write this run to --baseline and exit 0, instead of\n judging against it. Use it to capture a baseline, or\n to accept findings you have decided to live with —\n it says what it accepted.\n --max-debt <n> Fail when the site carries more than <n> findings in\n total, whether new or known. Lower it as you fix, to\n stop a baseline from fossilising.\n --start <cmd> Boot <cmd>, wait for <url> to answer, audit, then\n stop it. Use to gate a merge on the built app before\n it ships.\n --start-cwd <dir> Directory to run --start in. Defaults to the current\n one; set it when auditing a monorepo package from the\n repository root.\n --start-timeout <ms> How long to wait for --start to answer. Default: 60000.\n --no-external Do not probe off-origin (external) links.\n --static Static HTML only; never launch headless Chromium.\n --allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).\n --timeout <ms> Per-request timeout in ms. Default: 8000.\n --verbose, -V Log every page as it is analyzed.\n --quiet, -q Suppress the live progress output.\n --no-color Disable coloured output.\n -h, --help Show this help.\n -v, --version Show the version.\n\nExit codes: 0 clean, 1 findings found, 2 fatal error.`;\n\nexport interface ParsedArgs {\n url?: string;\n json: boolean;\n summary: boolean;\n report?: string;\n color: boolean;\n help: boolean;\n version: boolean;\n /** Live-progress verbosity for the logger. */\n logMode: LogMode;\n /** Severity at or above which the process exits 1. */\n failOn: FailOn;\n /** Path to a stored report to compare against (`--baseline`). */\n baseline?: string;\n /** Gate on new findings only, letting known ones through. */\n regressionsOnly: boolean;\n /** Write the run to `--baseline` rather than judging against it. */\n updateBaseline: boolean;\n /** Hard ceiling on total findings, new or known. */\n maxDebt?: number;\n /** Command to boot before auditing, and stop afterwards (`--start`). */\n start?: string;\n /** How long to wait for `--start` to answer, in ms. */\n startTimeoutMs: number;\n /** Directory to run the `--start` command in. */\n startCwd?: string;\n options: AuditOptions;\n}\n\nconst FAIL_ON_LEVELS: readonly FailOn[] = [\"warning\", \"error\", \"never\"];\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const parsed: ParsedArgs = {\n json: false,\n summary: false,\n color: process.stdout.isTTY === true && !process.env.NO_COLOR,\n help: false,\n version: false,\n logMode: \"compact\",\n failOn: \"warning\",\n regressionsOnly: false,\n updateBaseline: false,\n startTimeoutMs: 60_000,\n options: { include: [], exclude: [] },\n };\n\n const next = (i: number, flag: string): string => {\n const value = argv[i + 1];\n if (value === undefined) throw new Error(`${flag} requires a value`);\n return value;\n };\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n switch (arg) {\n case \"-h\":\n case \"--help\":\n parsed.help = true;\n break;\n case \"-v\":\n case \"--version\":\n parsed.version = true;\n break;\n case \"--json\":\n parsed.json = true;\n break;\n case \"-s\":\n case \"--summary\":\n parsed.summary = true;\n break;\n case \"--no-color\":\n parsed.color = false;\n break;\n case \"-V\":\n case \"--verbose\":\n parsed.logMode = \"verbose\";\n break;\n case \"-q\":\n case \"--quiet\":\n parsed.logMode = \"quiet\";\n break;\n case \"--static\":\n parsed.options.static = true;\n break;\n case \"--allow-insecure-tls\":\n parsed.options.allowInsecureTls = true;\n break;\n case \"--no-external\":\n parsed.options.checkExternal = false;\n break;\n case \"--no-sitemap\":\n parsed.options.noSitemap = true;\n break;\n case \"--conformance\":\n parsed.options.conformance = true;\n break;\n case \"--advisories\":\n parsed.options.advisories = true;\n break;\n case \"--ignore-holes\":\n parsed.options.ignoreHoles = [...(parsed.options.ignoreHoles ?? []), next(i, arg)];\n i++;\n break;\n case \"--locales\": {\n // Split on commas so `--locales fr,en` and repeated flags both work.\n const value = next(i, arg);\n const tags = value\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n if (tags.length === 0) throw new Error(`${arg} expects at least one locale`);\n parsed.options.locales = [...(parsed.options.locales ?? []), ...tags];\n i++;\n break;\n }\n case \"--profile\": {\n const value = next(i, arg);\n // Validated here, not in `runAudit`: a mistyped policy should cost\n // an argument-parse error, not a crawl.\n if (!PROFILE_NAMES.includes(value)) {\n throw new Error(`${arg} expects one of: ${PROFILE_NAMES.join(\", \")}`);\n }\n parsed.options.profile = value;\n i++;\n break;\n }\n case \"--fail-on\": {\n const value = next(i, arg) as FailOn;\n if (!FAIL_ON_LEVELS.includes(value)) {\n throw new Error(`${arg} expects one of: ${FAIL_ON_LEVELS.join(\", \")}`);\n }\n parsed.failOn = value;\n i++;\n break;\n }\n case \"--start\":\n parsed.start = next(i, arg);\n i++;\n break;\n case \"--start-timeout\":\n parsed.startTimeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--start-cwd\":\n parsed.startCwd = next(i, arg);\n i++;\n break;\n case \"--report\":\n parsed.report = next(i, arg);\n i++;\n break;\n case \"--baseline\":\n parsed.baseline = next(i, arg);\n i++;\n break;\n case \"--update-baseline\":\n parsed.updateBaseline = true;\n break;\n case \"--regressions-only\":\n parsed.regressionsOnly = true;\n break;\n case \"--max-debt\":\n parsed.maxDebt = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--depth\":\n parsed.options.depth = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--coverage\": {\n const mode = next(i, arg);\n if (mode !== \"structural\" && mode !== \"all\") {\n throw new Error(`--coverage takes \"structural\" or \"all\", not ${JSON.stringify(mode)}`);\n }\n parsed.options.coverage = mode;\n i += 1;\n break;\n }\n case \"--max-pages\":\n parsed.options.maxPages = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--timeout\":\n parsed.options.timeoutMs = toInt(next(i, arg), arg);\n i++;\n break;\n case \"--include\":\n parsed.options.include!.push(next(i, arg));\n i++;\n break;\n case \"--exclude\":\n parsed.options.exclude!.push(next(i, arg));\n i++;\n break;\n default:\n if (arg && arg.startsWith(\"-\")) throw new Error(`unknown option: ${arg}`);\n if (arg && !parsed.url) parsed.url = arg;\n else if (arg) throw new Error(`unexpected argument: ${arg}`);\n }\n }\n\n // Each flag is meaningless without the other, and guessing which one the\n // caller meant would silently change how strict the build is.\n if (parsed.updateBaseline && !parsed.baseline) {\n throw new Error(\"--update-baseline needs a --baseline <file> to write to\");\n }\n // Capturing a baseline is not gating against one, so the explicit opt-in that\n // --regressions-only exists to force does not apply.\n if (parsed.baseline && !parsed.regressionsOnly && !parsed.updateBaseline) {\n throw new Error(\n \"--baseline weakens the gate, so it must be requested explicitly: add --regressions-only\",\n );\n }\n if (parsed.regressionsOnly && !parsed.baseline) {\n throw new Error(\"--regressions-only needs a --baseline <file> to compare against\");\n }\n\n return parsed;\n}\n\nfunction toInt(value: string, flag: string): number {\n const n = Number(value);\n if (!Number.isInteger(n) || n < 0) throw new Error(`${flag} expects a non-negative integer`);\n return n;\n}\n","/**\n * goflag CLI.\n *\n * goflag <url> [options]\n *\n * Crawls a site and reports broken links, missing translation pages, and\n * missing/misconfigured SEO metadata. The JSON report is the source of\n * truth; the terminal output is rendered from it.\n *\n * Exit codes:\n * 0 clean (green flag)\n * 1 findings present (yellow/red flag) — useful as a CI gate\n * 2 fatal (bad URL, unexpected error)\n */\n\nimport { runAudit, exitCode } from \"./report/build\";\nimport { diffReports, diffExitCode, totalFindings } from \"./report/diff\";\nimport { renderDiffTerminal } from \"./report/render-diff\";\nimport { startServer, type StartedServer } from \"./lib/runner/dev-server\";\nimport { renderTerminal } from \"./report/render-terminal\";\nimport { renderSummaryTerminal } from \"./report/render-summary\";\nimport { summarize } from \"./report/summarize\";\nimport { Logger } from \"./report/logger\";\nimport { HELP, parseArgs, type ParsedArgs } from \"./cli-args\";\n\nasync function readVersion(): Promise<string> {\n try {\n const { readFileSync } = await import(\"node:fs\");\n const { fileURLToPath } = await import(\"node:url\");\n const { dirname, join } = await import(\"node:path\");\n const here = dirname(fileURLToPath(import.meta.url));\n const pkg = JSON.parse(readFileSync(join(here, \"..\", \"package.json\"), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\n/**\n * Write JSON to a path, creating the directory it names.\n *\n * `writeFileSync` does not, and the one moment that matters is the first: a\n * repository adopting the gate runs `--update-baseline --baseline\n * .goflag/baseline.json` with no `.goflag/` yet, and the command that the\n * runbook documents as step one is the only one that fails. The audit had\n * already run at that point, so the failure also threw away several minutes of\n * crawling.\n */\nasync function writeJson(path: string, value: unknown): Promise<void> {\n const { mkdirSync, writeFileSync } = await import(\"node:fs\");\n const { dirname } = await import(\"node:path\");\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf8\");\n}\n\nasync function main(): Promise<number> {\n let args: ParsedArgs;\n try {\n args = parseArgs(process.argv.slice(2));\n } catch (err) {\n process.stderr.write(`goflag: ${(err as Error).message}\\n\\n${HELP}\\n`);\n return 2;\n }\n\n if (args.help) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (args.version) {\n process.stdout.write(`${await readVersion()}\\n`);\n return 0;\n }\n if (!args.url) {\n process.stderr.write(`goflag: missing <url>\\n\\n${HELP}\\n`);\n return 2;\n }\n\n // Progress always goes to stderr, so stdout (JSON / rendered report)\n // stays clean and pipeable. Colour follows the stderr TTY.\n const logger = new Logger({\n stream: process.stderr,\n mode: args.logMode,\n color: process.stderr.isTTY === true && !process.env.NO_COLOR,\n });\n\n // `--start` owns a child process for the whole audit; it must be torn down\n // on every exit path, including a failed audit, or CI leaks a held port.\n let server: StartedServer | undefined;\n let report;\n try {\n if (args.start) {\n logger.note(`goflag: starting \\`${args.start}\\` …`);\n server = await startServer({\n command: args.start,\n cwd: args.startCwd,\n url: args.url,\n timeoutMs: args.startTimeoutMs,\n allowInsecureTls: args.options.allowInsecureTls,\n onOutput: args.logMode === \"verbose\" ? (chunk) => process.stderr.write(chunk) : undefined,\n });\n }\n logger.note(`goflag: auditing ${args.url} …`);\n report = await runAudit(args.url, { ...args.options, onProgress: logger.onProgress });\n logger.stop();\n } catch (err) {\n logger.stop();\n process.stderr.write(`goflag: ${(err as Error).message}\\n`);\n return 2;\n } finally {\n await server?.stop();\n }\n\n // A sitemap that timed out is not a site without one. The crawl seeds from\n // the sitemap when there is one, so losing it drops the audit to link\n // following and it sees a fraction of the pages — measured on\n // openfinanceguide, 46 instead of 600, and 5 findings instead of 279.\n //\n // Reporting that as a result is how a gate goes red on nothing, or green on\n // 8% of a site. Neither is worth having, so this is fatal: the run could not\n // ask the question it was asked to ask.\n const sitemap = report.diagnostics.sitemap;\n if (sitemap?.unreachable) {\n process.stderr.write(\n `goflag: the sitemap could not be fetched — ${sitemap.unreachable}\\n` +\n `goflag: this run crawled links instead, so it saw a different site ` +\n `from one that reads the sitemap, and its findings are not comparable.\\n` +\n `goflag: retry, or pass --no-sitemap to audit by crawling on purpose.\\n`,\n );\n return 2;\n }\n\n // A baseline changes the question from \"is this site clean?\" to \"did this\n // change make it worse?\" — the only one a site with a backlog can answer.\n if (args.baseline) {\n const { existsSync, readFileSync } = await import(\"node:fs\");\n // Capturing a baseline for the first time is the one case where the file\n // is legitimately absent. Everywhere else a missing baseline is a typo,\n // and continuing would turn it into a green build.\n if (existsSync(args.baseline) || !args.updateBaseline) {\n try {\n const baseline = JSON.parse(readFileSync(args.baseline, \"utf8\"));\n report.diff = diffReports(baseline, report);\n } catch (err) {\n process.stderr.write(\n `goflag: could not read baseline ${args.baseline}: ${(err as Error).message}\\n`,\n );\n return 2;\n }\n }\n }\n\n // Writing the baseline is accepting everything in it. The one thing this\n // must not do is accept it quietly: a counter that drops without explanation\n // reads as \"the problem went away\".\n if (args.updateBaseline && args.baseline) {\n await writeJson(args.baseline, report);\n const total = totalFindings(report);\n if (report.diff) {\n const { added, resolved } = report.diff;\n process.stdout.write(\n `goflag: baseline updated — ${added.length} newly accepted, ${resolved.length} resolved, ` +\n `${total} findings now grandfathered in ${args.baseline}\\n`,\n );\n if (added.length > 0) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n }\n } else {\n process.stdout.write(\n `goflag: baseline captured — ${total} findings grandfathered in ${args.baseline}\\n`,\n );\n }\n process.stdout.write(\n `goflag: set --max-debt ${total} to stop that number growing, and lower it as you fix.\\n`,\n );\n return 0;\n }\n\n if (args.report) {\n // The report file is always the full report — the source of truth a\n // baseline/diff can rely on, regardless of the --summary view choice.\n await writeJson(args.report, report);\n process.stderr.write(`goflag: report written to ${args.report}\\n`);\n }\n\n // In baseline mode the diff *is* the answer. Printing the full report first\n // buries \"nothing changed\" under a hundred known findings — the reader stops\n // looking, which is precisely the failure the baseline exists to prevent.\n // The complete report stays available via --json and --report.\n if (report.diff && !args.json) {\n process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}\\n`);\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n }\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n if (args.summary) {\n const summary = summarize(report);\n process.stdout.write(\n args.json\n ? `${JSON.stringify(summary, null, 2)}\\n`\n : `${renderSummaryTerminal(summary, { color: args.color })}\\n`,\n );\n } else {\n process.stdout.write(\n args.json\n ? `${JSON.stringify(report, null, 2)}\\n`\n : `${renderTerminal(report, { color: args.color })}\\n`,\n );\n }\n\n const total = totalFindings(report);\n if (args.maxDebt !== undefined && total > args.maxDebt) {\n process.stderr.write(\n `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.\\n`,\n );\n return 1;\n }\n\n if (report.diff) {\n return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });\n }\n\n return exitCode(report, args.failOn);\n}\n\nmain()\n .then((code) => process.exit(code))\n .catch((err) => {\n process.stderr.write(`goflag: unexpected error: ${(err as Error).stack ?? err}\\n`);\n process.exit(2);\n });\n"],"mappings":"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","names":["total"]}
package/dist/index.d.ts CHANGED
@@ -768,12 +768,35 @@ interface GoflagReport {
768
768
  */
769
769
  duplicatePages?: number;
770
770
  /** Sitemap discovery outcome, when discovery ran. */
771
+ /** Which pages the run chose to audit, and what it therefore cannot promise. */
772
+ coverage?: {
773
+ mode: "structural" | "all";
774
+ /** URLs the sitemap listed. */
775
+ considered?: number;
776
+ /** URLs actually audited. */
777
+ selected?: number;
778
+ /** Families that were sampled rather than audited whole, largest first. */
779
+ families?: {
780
+ pattern: string;
781
+ size: number;
782
+ sampled: number;
783
+ }[];
784
+ };
771
785
  sitemap?: {
772
786
  found: boolean;
773
787
  sitemapUrl?: string;
774
788
  urlCount: number;
775
789
  /** URLs the sitemap declared that the crawl never reached. */
776
790
  uncrawled: number;
791
+ /**
792
+ * Why the sitemap could not be fetched, when the failure was the network.
793
+ *
794
+ * Present means this run audited a different site from the one a run with
795
+ * a working sitemap audits, because the crawl lost its seeds and fell back
796
+ * to following links. Comparing such a run to a baseline is comparing two
797
+ * different questions, so the CLI exits 2 rather than reporting.
798
+ */
799
+ unreachable?: string;
777
800
  };
778
801
  };
779
802
  }
@@ -813,6 +836,17 @@ interface AuditOptions {
813
836
  depth?: number;
814
837
  /** Hard cap on pages crawled. Defaults to 200. */
815
838
  maxPages?: number;
839
+ /**
840
+ * How to choose which pages to audit.
841
+ *
842
+ * `structural` keeps every page that stands alone and samples families of
843
+ * pages built from one template — `docs/coverage-plan.md`. `all` is the old
844
+ * behaviour: take what the sitemap lists in order, until `maxPages`.
845
+ *
846
+ * Defaults to `structural` when a sitemap was found, `all` otherwise: with
847
+ * no list of URLs up front there is no structure to select from.
848
+ */
849
+ coverage?: "structural" | "all";
816
850
  /** Glob include filter on the URL pathname. */
817
851
  include?: string[];
818
852
  /** Glob exclude filter on the URL pathname. */
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  runAudit,
12
12
  summarize,
13
13
  targetKey
14
- } from "./chunk-6WOWKLXY.js";
14
+ } from "./chunk-F74SRZVM.js";
15
15
  export {
16
16
  Logger,
17
17
  SAMPLE_LIMIT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goflag/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Audits a site for broken links, missing translation pages, a robots.txt that contradicts your pages, and missing or misconfigured SEO metadata. JSON-first.",
6
6
  "keywords": [