@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 +21 -0
- package/README.md +207 -0
- package/dist/chunk-75OFMTOC.js +3719 -0
- package/dist/chunk-75OFMTOC.js.map +1 -0
- package/dist/cli.js +612 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +589 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -0
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
|
+
[](./.nvmrc)
|
|
7
|
+
[](#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.
|