@goflag/cli 0.1.3 → 0.1.4
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 +49 -11
- package/dist/cli.js +49 -9
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **A lean CLI that flags the site problems humans can't catch at scale.**
|
|
4
4
|
> Point it at a URL and get three things: broken links, missing translation pages, and missing/misconfigured SEO metadata. JSON-first.
|
|
5
5
|
|
|
6
|
-
[](
|
|
6
|
+
[](./package.json)
|
|
7
7
|
[](#license)
|
|
8
8
|
|
|
9
9
|
Goflag crawls a site once and runs three focused static checks over it. It is deliberately small: no dashboard, no config system, no social-preview gallery. Just the findings, as a machine-readable report you can pipe, diff, or gate CI on.
|
|
@@ -23,12 +23,17 @@ SPA support is built in: pages that look client-rendered are re-rendered in head
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
packages/cli/ @goflag/cli — the CLI (installs the `goflag` command)
|
|
26
|
-
|
|
27
|
-
apps/website/ documentation and marketing site (not built yet)
|
|
26
|
+
tools/name-holder/ the bare `goflag` name on npm, a signpost to @goflag/cli
|
|
28
27
|
```
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
That is the whole tree. `@goflag/next` (the Next.js library) and the
|
|
30
|
+
documentation site are planned but deliberately absent: the workspace globs
|
|
31
|
+
(`packages/*`, `apps/*`) are ready for them, and empty stubs for things that do
|
|
32
|
+
not exist are how a repository accumulates code nobody calls.
|
|
33
|
+
|
|
34
|
+
When they land, they must not import from the CLI: the two products stay
|
|
35
|
+
independently useful, and an ESLint rule enforces it rather than trusting
|
|
36
|
+
memory.
|
|
32
37
|
|
|
33
38
|
## Quick start
|
|
34
39
|
|
|
@@ -38,8 +43,10 @@ Requires Node `>=20.11`.
|
|
|
38
43
|
npx @goflag/cli https://example.com
|
|
39
44
|
```
|
|
40
45
|
|
|
41
|
-
The package is `@goflag/cli`; the command it installs is `goflag`.
|
|
42
|
-
|
|
46
|
+
The package is `@goflag/cli`; the command it installs is `goflag`. Everything
|
|
47
|
+
under the brand lives in the `@goflag` scope. The bare `goflag` name on npm is
|
|
48
|
+
claimed as a deprecated signpost pointing here — it carries no code, so install
|
|
49
|
+
the scoped package. The reasoning is in
|
|
43
50
|
[docs/spec-and-lib-plan.md](docs/spec-and-lib-plan.md#une-marque-des-outils-nommés).
|
|
44
51
|
|
|
45
52
|
You get a coloured terminal report and an exit code:
|
|
@@ -75,6 +82,8 @@ The terminal view is just a render of that JSON.
|
|
|
75
82
|
--regressions-only Weaken the gate: fail only on NEW findings relative
|
|
76
83
|
to --baseline. Requires --baseline.
|
|
77
84
|
--baseline <file> Stored report to compare against.
|
|
85
|
+
--update-baseline Write this run to --baseline and exit 0, instead of
|
|
86
|
+
judging against it.
|
|
78
87
|
--max-debt <n> Fail when the site carries more than <n> findings in
|
|
79
88
|
total, new or known.
|
|
80
89
|
--start <cmd> Boot <cmd>, wait for <url>, audit, then stop it.
|
|
@@ -90,7 +99,7 @@ The terminal view is just a render of that JSON.
|
|
|
90
99
|
-v, --version Show the version.
|
|
91
100
|
```
|
|
92
101
|
|
|
93
|
-
Headless mode needs Chromium.
|
|
102
|
+
Headless mode needs Chromium. `playwright` is an optional peer dependency; if it's missing, install it with `npx playwright install chromium` (or just run with `--static`).
|
|
94
103
|
|
|
95
104
|
### Gate on regressions, not on perfection
|
|
96
105
|
|
|
@@ -99,7 +108,7 @@ yet — so it gets switched off, or ignored. Capture a baseline once and gate on
|
|
|
99
108
|
"did this change make it worse?" instead:
|
|
100
109
|
|
|
101
110
|
```sh
|
|
102
|
-
goflag https://example.com --
|
|
111
|
+
goflag https://example.com --baseline baseline.json --update-baseline # once
|
|
103
112
|
goflag https://example.com --regressions-only --baseline baseline.json
|
|
104
113
|
```
|
|
105
114
|
|
|
@@ -129,6 +138,28 @@ goflag https://example.com --regressions-only --baseline baseline.json --max-deb
|
|
|
129
138
|
Keeping `baseline.json` in the repository helps for the same reason: adding a
|
|
130
139
|
finding to it then shows up in a diff someone reviews.
|
|
131
140
|
|
|
141
|
+
Capture it with the run that will later judge it, minus one flag:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
goflag https://example.com --baseline baseline.json --update-baseline
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
That matters more than it looks. A baseline captured with different options than
|
|
148
|
+
the gate — a forgotten `--static`, another `--max-pages` — does not compare
|
|
149
|
+
cleanly, and the mismatch surfaces as findings that appear from nowhere. Using
|
|
150
|
+
the same command for both makes that impossible.
|
|
151
|
+
|
|
152
|
+
The same flag accepts findings you have decided to live with, and it says what
|
|
153
|
+
it accepted:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
goflag: baseline updated — 3 newly accepted, 0 resolved, 44 findings now
|
|
157
|
+
grandfathered in baseline.json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Refreshing a baseline is taking on debt. It prints the number so the commit that
|
|
161
|
+
does it can be read as what it is.
|
|
162
|
+
|
|
132
163
|
### Gate a merge before it ships
|
|
133
164
|
|
|
134
165
|
`--start` boots your app, waits for it to answer, audits it, and stops it — so
|
|
@@ -178,7 +209,7 @@ never means "nothing was wrong" by accident.
|
|
|
178
209
|
## Programmatic API
|
|
179
210
|
|
|
180
211
|
```ts
|
|
181
|
-
import { runAudit } from "goflag";
|
|
212
|
+
import { runAudit } from "@goflag/cli";
|
|
182
213
|
|
|
183
214
|
const report = await runAudit("https://example.com", { depth: 2 });
|
|
184
215
|
console.log(report.summary); // { brokenLinks, missingTranslations, seoIssues, verdict }
|
|
@@ -188,6 +219,9 @@ console.log(report.summary); // { brokenLinks, missingTranslations, seoIssues, v
|
|
|
188
219
|
|
|
189
220
|
## Develop locally
|
|
190
221
|
|
|
222
|
+
Requires pnpm `11.18.0` (pinned via `packageManager` — `corepack enable` picks
|
|
223
|
+
it up). Run these from the repository root:
|
|
224
|
+
|
|
191
225
|
```sh
|
|
192
226
|
pnpm install
|
|
193
227
|
pnpm dev https://example.com # run the CLI from source (tsx)
|
|
@@ -200,7 +234,11 @@ pnpm typecheck # tsc --noEmit
|
|
|
200
234
|
pnpm format # prettier
|
|
201
235
|
```
|
|
202
236
|
|
|
203
|
-
|
|
237
|
+
`lint`, `format` and `format:check` run once over the whole repository; the
|
|
238
|
+
rest fan out across workspace packages with `pnpm -r`. To work on one package,
|
|
239
|
+
filter: `pnpm --filter @goflag/cli test`.
|
|
240
|
+
|
|
241
|
+
The engine is framework-agnostic and lives in [`packages/cli/src/lib/core/`](packages/cli/src/lib/core) (crawl, fetch/extract, link audit, i18n) and [`packages/cli/src/lib/rules/`](packages/cli/src/lib/rules) (SEO checks). The CLI shell — orchestration, the `GoflagReport` schema, and rendering — lives in [`packages/cli/src/report/`](packages/cli/src/report) and [`packages/cli/src/cli.ts`](packages/cli/src/cli.ts).
|
|
204
242
|
|
|
205
243
|
## License
|
|
206
244
|
|
package/dist/cli.js
CHANGED
|
@@ -303,6 +303,10 @@ Options:
|
|
|
303
303
|
Requires --baseline.
|
|
304
304
|
--baseline <file> Stored report to compare against. Use with
|
|
305
305
|
--regressions-only.
|
|
306
|
+
--update-baseline Write this run to --baseline and exit 0, instead of
|
|
307
|
+
judging against it. Use it to capture a baseline, or
|
|
308
|
+
to accept findings you have decided to live with \u2014
|
|
309
|
+
it says what it accepted.
|
|
306
310
|
--max-debt <n> Fail when the site carries more than <n> findings in
|
|
307
311
|
total, whether new or known. Lower it as you fix, to
|
|
308
312
|
stop a baseline from fossilising.
|
|
@@ -335,6 +339,7 @@ function parseArgs(argv) {
|
|
|
335
339
|
logMode: "compact",
|
|
336
340
|
failOn: "warning",
|
|
337
341
|
regressionsOnly: false,
|
|
342
|
+
updateBaseline: false,
|
|
338
343
|
startTimeoutMs: 6e4,
|
|
339
344
|
options: { include: [], exclude: [] }
|
|
340
345
|
};
|
|
@@ -425,6 +430,9 @@ function parseArgs(argv) {
|
|
|
425
430
|
parsed.baseline = next(i, arg);
|
|
426
431
|
i++;
|
|
427
432
|
break;
|
|
433
|
+
case "--update-baseline":
|
|
434
|
+
parsed.updateBaseline = true;
|
|
435
|
+
break;
|
|
428
436
|
case "--regressions-only":
|
|
429
437
|
parsed.regressionsOnly = true;
|
|
430
438
|
break;
|
|
@@ -458,7 +466,10 @@ function parseArgs(argv) {
|
|
|
458
466
|
else if (arg) throw new Error(`unexpected argument: ${arg}`);
|
|
459
467
|
}
|
|
460
468
|
}
|
|
461
|
-
if (parsed.
|
|
469
|
+
if (parsed.updateBaseline && !parsed.baseline) {
|
|
470
|
+
throw new Error("--update-baseline needs a --baseline <file> to write to");
|
|
471
|
+
}
|
|
472
|
+
if (parsed.baseline && !parsed.regressionsOnly && !parsed.updateBaseline) {
|
|
462
473
|
throw new Error(
|
|
463
474
|
"--baseline weakens the gate, so it must be requested explicitly: add --regressions-only"
|
|
464
475
|
);
|
|
@@ -546,17 +557,46 @@ ${HELP}
|
|
|
546
557
|
await server?.stop();
|
|
547
558
|
}
|
|
548
559
|
if (args.baseline) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
560
|
+
const { existsSync, readFileSync } = await import("fs");
|
|
561
|
+
if (existsSync(args.baseline) || !args.updateBaseline) {
|
|
562
|
+
try {
|
|
563
|
+
const baseline = JSON.parse(readFileSync(args.baseline, "utf8"));
|
|
564
|
+
report.diff = diffReports(baseline, report);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
process.stderr.write(
|
|
567
|
+
`goflag: could not read baseline ${args.baseline}: ${err.message}
|
|
568
|
+
`
|
|
569
|
+
);
|
|
570
|
+
return 2;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (args.updateBaseline && args.baseline) {
|
|
575
|
+
const { writeFileSync } = await import("fs");
|
|
576
|
+
writeFileSync(args.baseline, `${JSON.stringify(report, null, 2)}
|
|
577
|
+
`, "utf8");
|
|
578
|
+
const total2 = totalFindings(report);
|
|
579
|
+
if (report.diff) {
|
|
580
|
+
const { added, resolved } = report.diff;
|
|
581
|
+
process.stdout.write(
|
|
582
|
+
`goflag: baseline updated \u2014 ${added.length} newly accepted, ${resolved.length} resolved, ${total2} findings now grandfathered in ${args.baseline}
|
|
583
|
+
`
|
|
584
|
+
);
|
|
585
|
+
if (added.length > 0) {
|
|
586
|
+
process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}
|
|
587
|
+
`);
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
process.stdout.write(
|
|
591
|
+
`goflag: baseline captured \u2014 ${total2} findings grandfathered in ${args.baseline}
|
|
556
592
|
`
|
|
557
593
|
);
|
|
558
|
-
return 2;
|
|
559
594
|
}
|
|
595
|
+
process.stdout.write(
|
|
596
|
+
`goflag: set --max-debt ${total2} to stop that number growing, and lower it as you fix.
|
|
597
|
+
`
|
|
598
|
+
);
|
|
599
|
+
return 0;
|
|
560
600
|
}
|
|
561
601
|
if (args.report) {
|
|
562
602
|
const { writeFileSync } = await import("fs");
|
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 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 and when the baseline was taken, echoed for the report header. */\n baseline: { url: string; finishedAt: 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 return {\n baseline: { url: baseline.url, finishedAt: baseline.finishedAt },\n added: sortEntries(added),\n resolved: sortEntries(resolved),\n unchanged,\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 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 type { AuditOptions, FailOn } from \"./report/build\";\nimport type { LogMode } from \"./report/logger\";\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 --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 --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 /** 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 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 \"--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 \"--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 \"--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.baseline && !parsed.regressionsOnly) {\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 try {\n const { readFileSync } = await import(\"node:fs\");\n const baseline = JSON.parse(readFileSync(args.baseline, \"utf8\"));\n report.diff = diffReports(baseline, report);\n } catch (err) {\n // Silently continuing would turn a typo'd path into a green build.\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 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 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 and when the baseline was taken, echoed for the report header. */\n baseline: { url: string; finishedAt: 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 return {\n baseline: { url: baseline.url, finishedAt: baseline.finishedAt },\n added: sortEntries(added),\n resolved: sortEntries(resolved),\n unchanged,\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 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 type { AuditOptions, FailOn } from \"./report/build\";\nimport type { LogMode } from \"./report/logger\";\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 --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 \"--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 \"--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"]}
|
package/package.json
CHANGED