@goflag/cli 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tancrède Simonin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # Goflag
2
+
3
+ > **A lean CLI that flags the site problems humans can't catch at scale.**
4
+ > Point it at a URL and get three things: broken links, missing translation pages, and missing/misconfigured SEO metadata. JSON-first.
5
+
6
+ [![node](https://img.shields.io/badge/node-%3E%3D20.11-339933?logo=nodedotjs&logoColor=white)](./.nvmrc)
7
+ [![license: MIT](https://img.shields.io/badge/license-MIT-green)](#license)
8
+
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.
10
+
11
+ ## What it checks
12
+
13
+ - **Broken links** — scrapes every crawled page, dedupes targets globally (a footer link on 500 pages is probed once), and checks each with `HEAD`→`GET` fallback, redirect-chain/loop detection, soft-404 and anti-bot (403/429) triage. Broken links are mapped back to the pages that reference them.
14
+ - **Missing translation pages** — builds a `route × locale` matrix from the crawl and flags every route that exists in one locale but is missing in another, plus hreflang reciprocity gaps (`A → B` with no `B → A`), missing `x-default`, and invalid locale tags.
15
+ - **robots.txt policy** — flags a site that forbids crawling while its pages ask to be indexed. The two declarations cannot both hold, and robots.txt wins, so the meta tag is never even read.
16
+ - **SEO metadata** — lints each page's `<head>` for the handful of mistakes that actually hurt in search and social and are invisible in a browser: missing/oversized `<title>` and description, missing/relative canonical, missing `og:title`/`og:description`/`og:image`, missing viewport, and contradictory `robots`/`googlebot`/`X-Robots-Tag` directives.
17
+
18
+ Pages that declare a `<link rel="canonical">` pointing at another crawled page are excluded from the checks — the site has said they are duplicates, so linting them would multiply every finding by the number of filtered variants. The count is reported as `diagnostics.duplicatePages`.
19
+
20
+ SPA support is built in: pages that look client-rendered are re-rendered in headless Chromium automatically (pass `--static` to skip it).
21
+
22
+ ## Repository layout
23
+
24
+ ```
25
+ packages/cli/ @goflag/cli — the CLI (installs the `goflag` command)
26
+ packages/next/ @goflag/next — the Next.js library (not built yet)
27
+ apps/website/ documentation and marketing site (not built yet)
28
+ ```
29
+
30
+ The library and the apps must not import from the CLI: the two products stay
31
+ independently useful, and a lint rule enforces it rather than trusting memory.
32
+
33
+ ## Quick start
34
+
35
+ Requires Node `>=20.11`.
36
+
37
+ ```sh
38
+ npx @goflag/cli https://example.com
39
+ ```
40
+
41
+ The package is `@goflag/cli`; the command it installs is `goflag`. The bare
42
+ `goflag` name on npm is not claimed yet — see the note in
43
+ [docs/spec-and-lib-plan.md](docs/spec-and-lib-plan.md#une-marque-des-outils-nommés).
44
+
45
+ You get a coloured terminal report and an exit code:
46
+
47
+ - `0` — clean (green flag)
48
+ - `1` — findings present (yellow/red flag) — use this as a CI gate
49
+ - `2` — fatal error (bad URL, unexpected failure)
50
+
51
+ ### JSON is the source of truth
52
+
53
+ ```sh
54
+ npx @goflag/cli https://example.com --json > report.json # print JSON to stdout
55
+ npx @goflag/cli https://example.com --report report.json # write JSON to a file
56
+ ```
57
+
58
+ The terminal view is just a render of that JSON.
59
+
60
+ ### Options
61
+
62
+ ```
63
+ --json Print the JSON report to stdout (nothing else).
64
+ --report <file> Write the JSON report to <file>.
65
+ --depth <n> Crawl depth (0 = entry page only). Default: 2.
66
+ --max-pages <n> Hard cap on pages crawled. Default: 200.
67
+ --include <glob> Only crawl paths matching <glob> (repeatable).
68
+ --exclude <glob> Skip paths matching <glob> (repeatable).
69
+ --locales <list> Comma-separated locales the site serves ("fr,en,pt-br").
70
+ --ignore-holes <glob> Route deliberately not translated everywhere
71
+ (repeatable).
72
+ --no-sitemap Do not discover the sitemap; crawl from <url> only.
73
+ --fail-on <level> Exit 1 at or above this severity: warning (default),
74
+ error, or never.
75
+ --regressions-only Weaken the gate: fail only on NEW findings relative
76
+ to --baseline. Requires --baseline.
77
+ --baseline <file> Stored report to compare against.
78
+ --max-debt <n> Fail when the site carries more than <n> findings in
79
+ total, new or known.
80
+ --start <cmd> Boot <cmd>, wait for <url>, audit, then stop it.
81
+ --start-cwd <dir> Directory to run --start in. Defaults to the current
82
+ one.
83
+ --start-timeout <ms> How long to wait for --start. Default: 60000.
84
+ --no-external Do not probe off-origin (external) links.
85
+ --static Static HTML only; never launch headless Chromium.
86
+ --allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).
87
+ --timeout <ms> Per-request timeout in ms. Default: 8000.
88
+ --no-color Disable coloured output.
89
+ -h, --help Show help.
90
+ -v, --version Show the version.
91
+ ```
92
+
93
+ Headless mode needs Chromium. It ships as an optional dependency; if it's missing, install it with `npx playwright install chromium` (or just run with `--static`).
94
+
95
+ ### Gate on regressions, not on perfection
96
+
97
+ A plain run fails on any finding, which is unusable on a site that is not clean
98
+ yet — so it gets switched off, or ignored. Capture a baseline once and gate on
99
+ "did this change make it worse?" instead:
100
+
101
+ ```sh
102
+ goflag https://example.com --report baseline.json --json # once
103
+ goflag https://example.com --regressions-only --baseline baseline.json
104
+ ```
105
+
106
+ Findings are matched by fingerprint, and page URLs are normalised to
107
+ origin-independent routes — so a baseline captured against production compares
108
+ cleanly with a run against `localhost`.
109
+
110
+ **This mode passes builds on sites with known defects.** That is what it is
111
+ for, and why it has to be asked for by name: `--baseline` alone is an error.
112
+ The output never says "clean" and never goes green while findings are
113
+ outstanding — it says how many are being let through:
114
+
115
+ ```
116
+ REGRESSION GATE 0 new · 108 known findings NOT gating this build
117
+ baseline https://example.com — taken 2026-07-29 (3 days ago)
118
+ ```
119
+
120
+ The risk it carries is the one every suppression file carries: the backlog
121
+ fossilises behind a passing build. `--max-debt` is the counterweight — a
122
+ ceiling on total findings that you lower as you fix, so the number has to go
123
+ down rather than merely not go up:
124
+
125
+ ```sh
126
+ goflag https://example.com --regressions-only --baseline baseline.json --max-debt 108
127
+ ```
128
+
129
+ Keeping `baseline.json` in the repository helps for the same reason: adding a
130
+ finding to it then shows up in a diff someone reviews.
131
+
132
+ ### Gate a merge before it ships
133
+
134
+ `--start` boots your app, waits for it to answer, audits it, and stops it — so
135
+ the same checks that would have caught a regression in production run on the
136
+ branch instead:
137
+
138
+ ```sh
139
+ goflag http://localhost:3000 --start "pnpm start" --fail-on error
140
+ ```
141
+
142
+ The command runs in the current directory, so run goflag from the project it
143
+ should boot. In a monorepo audited from the root, point it at the package:
144
+
145
+ ```sh
146
+ goflag http://localhost:3000 --start "pnpm start" --start-cwd apps/web
147
+ ```
148
+
149
+ ### Multilingual sites
150
+
151
+ Discovery is seeded from the sitemap, not just from links. That matters because
152
+ a site with no `hreflang` gives a link crawler nothing to follow into its other
153
+ locales — so it would look monolingual, and every hreflang check would pass
154
+ vacuously. When a site has no usable sitemap, name the locales yourself:
155
+
156
+ ```sh
157
+ goflag https://example.com --locales fr,en,pt-br
158
+ ```
159
+
160
+ When a site publishes no usable sitemap and you pass no `--locales`, goflag
161
+ does **not** guess the locale axis — it reports the prefixes it saw with the
162
+ evidence for each and turns the i18n checks off. Guessing from path shape alone
163
+ once turned `/cv` (a CV page served in French) into a locale, because `cv` is a
164
+ registered ISO 639-1 code, and produced 31 findings that were never real.
165
+
166
+ Some pages are meant to exist in one locale and not another — a
167
+ jurisdiction-specific legal notice, a post written for one market. A site has
168
+ no way to say "this page is absent on purpose", so goflag cannot tell a
169
+ deliberate gap from a forgotten translation; only you can. Declare them:
170
+
171
+ ```sh
172
+ goflag https://example.com --ignore-holes /legal --ignore-holes "/blog/**"
173
+ ```
174
+
175
+ Suppressed gaps are counted in `diagnostics.ignoredHoles`, so a quiet report
176
+ never means "nothing was wrong" by accident.
177
+
178
+ ## Programmatic API
179
+
180
+ ```ts
181
+ import { runAudit } from "goflag";
182
+
183
+ const report = await runAudit("https://example.com", { depth: 2 });
184
+ console.log(report.summary); // { brokenLinks, missingTranslations, seoIssues, verdict }
185
+ ```
186
+
187
+ `runAudit` returns the same `GoflagReport` object the CLI emits.
188
+
189
+ ## Develop locally
190
+
191
+ ```sh
192
+ pnpm install
193
+ pnpm dev https://example.com # run the CLI from source (tsx)
194
+ pnpm build # bundle to dist/ (tsup)
195
+ pnpm test # vitest (unit + integration)
196
+ pnpm test:unit # unit only (no network / Chromium)
197
+ pnpm test:integration # fixture-server + headless Chromium
198
+ pnpm lint # eslint
199
+ pnpm typecheck # tsc --noEmit
200
+ pnpm format # prettier
201
+ ```
202
+
203
+ The engine is framework-agnostic and lives in [`src/lib/core/`](src/lib/core) (crawl, fetch/extract, link audit, i18n) and [`src/lib/rules/`](src/lib/rules) (SEO checks). The CLI shell — orchestration, the `GoflagReport` schema, and rendering — lives in [`src/report/`](src/report) and [`src/cli.ts`](src/cli.ts).
204
+
205
+ ## License
206
+
207
+ MIT.