@goflag/cli 0.2.5 → 0.2.8
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 +99 -14
- package/dist/{chunk-5FO2Q3II.js → chunk-SKX7UCGC.js} +318 -25
- package/dist/chunk-SKX7UCGC.js.map +1 -0
- package/dist/cli.js +1018 -217
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +64 -4
- package/dist/index.js +1 -1
- package/flags.json +302 -0
- package/package.json +7 -2
- package/rules.json +517 -0
- package/dist/chunk-5FO2Q3II.js.map +0 -1
package/README.md
CHANGED
|
@@ -16,9 +16,14 @@ on the site. A human cannot check those on 400 pages. That is the whole job.
|
|
|
16
16
|
## What it checks
|
|
17
17
|
|
|
18
18
|
- **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.
|
|
19
|
-
- **Missing translation pages** — builds a `route × locale` matrix
|
|
19
|
+
- **Missing translation pages** — builds a `route × locale` matrix 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 locale tags naming a language, script or region that does not exist. Rows are keyed by pathname, unless the site declares its translation clusters — either with `xhtml:link` in the sitemap, or with reciprocal `hreflang` between two crawled pages' `<head>`. Then those declarations decide which URLs are one page, whatever their slugs, and the run reports `diagnostics.declaredClusters`. The sitemap source survives `--coverage structural`, where the two locales of a slug-translating family are rarely both sampled; the `<head>` source needs both pages in hand and is what fixes the common site that declares properly there and nothing in its sitemap.
|
|
20
20
|
- **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.
|
|
21
21
|
- **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.
|
|
22
|
+
- **Unreachable pages** — a page the crawl reached that answered non-2xx, or that never answered at all (timeout, reset, DNS failure — recorded as `status: 0`), is reported in `unreachablePages`. goflag asks a second time, from the back of the queue, before calling a page unreachable. Each one is an `error`-severity finding: it turns the verdict red, counts toward `--max-debt`, and fails the build even under `--fail-on error`. A page that silently dropped out of the audited set is what poisons a baseline, so it is reported rather than warned about.
|
|
23
|
+
|
|
24
|
+
**Which pages get audited.** When goflag finds a sitemap it does not audit every URL in it. It groups URLs that share a path shape — and therefore a template — into families, audits every page that stands alone, and samples three pages per family. A cap answers "how many" and never "which": on a site of thousands of pages built from thirty templates, the first 200 pages a crawl reaches are four templates out of thirty.
|
|
25
|
+
|
|
26
|
+
The trade is stated in every report rather than hidden. Template rules (`canonical.*`, `hreflang.*`, `og.image.missing`) are conclusive on a sampled family; copy rules (`title.length`, `description.length`), broken links and the translation matrix are only conclusive on the pages that were drawn. The terminal prints a `COVERAGE` line, and `diagnostics.coverage` carries `mode`, `considered`, `selected` and every sampled family with its ratio. Pass `--coverage all` to audit what the sitemap lists in order, up to `--max-pages`, which is the pre-0.2.3 behaviour.
|
|
22
27
|
|
|
23
28
|
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`.
|
|
24
29
|
|
|
@@ -125,16 +130,69 @@ npx @goflag/cli https://example.com --report report.json # write JSON to a
|
|
|
125
130
|
|
|
126
131
|
The terminal view is just a render of that JSON.
|
|
127
132
|
|
|
133
|
+
### The catalogue is data too
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npx @goflag/cli rules > rules.json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`rules` answers a question about goflag rather than about a site: no URL, no
|
|
140
|
+
crawl, no network. Every entry carries its scope, severity and summary; the
|
|
141
|
+
eleven page rules and four prose rules also carry a rigor, the documents they
|
|
142
|
+
cite and — where a remedy is a line of code — a fix snippet. The three site
|
|
143
|
+
rules carry none of those three: `SiteRule` has no rigor field yet, and the
|
|
144
|
+
catalogue emits `rigor: null` with an empty `sources` rather than inventing an
|
|
145
|
+
authority nobody assigned. Write your consumer against that. The same document
|
|
146
|
+
ships inside the package as `rules.json`, if you would rather read it than run
|
|
147
|
+
anything.
|
|
148
|
+
|
|
149
|
+
The flag list below is data too:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
npx @goflag/cli flags > flags.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Same idea, one step further: `flags` prints the very table `goflag --help` is
|
|
156
|
+
rendered from and the argument parser dispatches on, so a flag cannot exist in
|
|
157
|
+
one and not the other. Each entry carries its long and short forms, its argument
|
|
158
|
+
placeholder, its default, the group it belongs to and any flag it requires. It
|
|
159
|
+
ships inside the package as `flags.json` as well.
|
|
160
|
+
|
|
161
|
+
It deliberately omits the message a finding prints — that is built at audit time
|
|
162
|
+
from what the page actually says, so a static copy would be a sample rather than
|
|
163
|
+
a fact, and a sample presented as the truth is how this project's own
|
|
164
|
+
documentation site came to quote a message the engine had stopped printing.
|
|
165
|
+
|
|
128
166
|
### Options
|
|
129
167
|
|
|
130
168
|
```
|
|
131
169
|
--json Print the JSON report to stdout (nothing else).
|
|
170
|
+
--summary, -s Roll findings up (dedup by link/rule/code). Pairs with
|
|
171
|
+
--json for a compact payload; --report always writes
|
|
172
|
+
the full report regardless.
|
|
132
173
|
--report <file> Write the JSON report to <file>.
|
|
133
|
-
--depth <n>
|
|
134
|
-
|
|
174
|
+
--depth <n> How far to follow links out of each page (0 = follow
|
|
175
|
+
none). Sitemap URLs are seeded regardless, so --depth 0
|
|
176
|
+
alone is not "entry page only" — add --no-sitemap.
|
|
177
|
+
Default: 2.
|
|
178
|
+
--max-pages <n> Page budget for the crawl. Default: 200. A hard cap
|
|
179
|
+
under --coverage all; under structural coverage the
|
|
180
|
+
selection wins and the budget is max(<n>, selected + 5),
|
|
181
|
+
so a lower value cannot cut the run short.
|
|
182
|
+
--coverage <mode> How pages are chosen: "structural" (default when a
|
|
183
|
+
sitemap is found) keeps every standalone page and
|
|
184
|
+
samples three pages per family of template-generated
|
|
185
|
+
pages; "all" audits what the sitemap lists, in order,
|
|
186
|
+
up to --max-pages.
|
|
135
187
|
--include <glob> Only crawl paths matching <glob> (repeatable).
|
|
136
188
|
--exclude <glob> Skip paths matching <glob> (repeatable).
|
|
137
189
|
--locales <list> Comma-separated locales the site serves ("fr,en,pt-br").
|
|
190
|
+
Unioned with the prefixes your sitemap shows, never
|
|
191
|
+
substituted for them, so a locale the sitemap serves
|
|
192
|
+
stays on the axis whether or not you list it. Also what
|
|
193
|
+
folds /en/… and /fr/… into one route family for
|
|
194
|
+
structural coverage — pass it on any locale-prefixed
|
|
195
|
+
site.
|
|
138
196
|
--ignore-holes <glob> Route deliberately not translated everywhere
|
|
139
197
|
(repeatable).
|
|
140
198
|
--no-sitemap Do not discover the sitemap; crawl from <url> only.
|
|
@@ -160,7 +218,11 @@ The terminal view is just a render of that JSON.
|
|
|
160
218
|
--no-external Do not probe off-origin (external) links.
|
|
161
219
|
--static Static HTML only; never launch headless Chromium.
|
|
162
220
|
--allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).
|
|
163
|
-
--timeout <ms> Per-request timeout in ms
|
|
221
|
+
--timeout <ms> Per-request timeout in ms, for page fetches and link
|
|
222
|
+
probes alike. Unset, the defaults differ: 8000 for link
|
|
223
|
+
probes, 15000 for page fetches.
|
|
224
|
+
--verbose, -V Log every page as it is analyzed.
|
|
225
|
+
--quiet, -q Suppress the live progress output.
|
|
164
226
|
--no-color Disable coloured output.
|
|
165
227
|
-h, --help Show help.
|
|
166
228
|
-v, --version Show the version.
|
|
@@ -351,9 +413,16 @@ goflag https://example.com --locales fr,en,pt-br
|
|
|
351
413
|
|
|
352
414
|
When a site publishes no usable sitemap and you pass no `--locales`, goflag
|
|
353
415
|
does **not** guess the locale axis — it reports the prefixes it saw with the
|
|
354
|
-
evidence for each and turns the
|
|
355
|
-
|
|
356
|
-
|
|
416
|
+
evidence for each and turns the axis-dependent checks off (holes,
|
|
417
|
+
`hreflang.missing`, `hreflang.sitemap-mismatch`; the reciprocity checks read the
|
|
418
|
+
markup and keep running). Guessing from path shape alone once turned `/cv` (a CV
|
|
419
|
+
page served in French) into a locale, because `cv` is a registered ISO 639-1
|
|
420
|
+
code, and produced 31 findings that were never real.
|
|
421
|
+
|
|
422
|
+
Pass `--locales` even when the sitemap is fine. It is the axis goflag folds when
|
|
423
|
+
it groups URLs into route families, so without it a site with many top-level
|
|
424
|
+
sections under `/en` can have those sections sampled as one family instead of
|
|
425
|
+
audited.
|
|
357
426
|
|
|
358
427
|
Some pages are meant to exist in one locale and not another — a
|
|
359
428
|
jurisdiction-specific legal notice, a post written for one market. A site has
|
|
@@ -367,6 +436,17 @@ goflag https://example.com --ignore-holes /legal --ignore-holes "/blog/**"
|
|
|
367
436
|
Suppressed gaps are counted in `diagnostics.ignoredHoles`, so a quiet report
|
|
368
437
|
never means "nothing was wrong" by accident.
|
|
369
438
|
|
|
439
|
+
The opposite failure is quieter still. A cell in the matrix can be filled by an
|
|
440
|
+
`hreflang` on another page and by nothing else — not crawled, not listed in your
|
|
441
|
+
sitemap — and a filled cell reads as a translation that exists. A page
|
|
442
|
+
advertising a translation the site does not serve therefore plugs the very gap
|
|
443
|
+
it should reveal. Those cells are counted in
|
|
444
|
+
`diagnostics.unverifiedAlternates`, and goflag deliberately does **not** act on
|
|
445
|
+
them: refusing to believe an alternate the sitemap omits would invent holes on
|
|
446
|
+
every site using `@goflag/next`'s `sitemap: false` on purpose. Read that number
|
|
447
|
+
next to `missingTranslations` — a `0` beside a non-zero count is the one place
|
|
448
|
+
this report can be quietly wrong.
|
|
449
|
+
|
|
370
450
|
## In CI
|
|
371
451
|
|
|
372
452
|
Two moments are worth auditing, and they answer different questions.
|
|
@@ -398,7 +478,7 @@ Three rules, whichever you use:
|
|
|
398
478
|
stages: [build, deploy, audit]
|
|
399
479
|
|
|
400
480
|
variables:
|
|
401
|
-
GOFLAG_VERSION: "0.
|
|
481
|
+
GOFLAG_VERSION: "0.2.8"
|
|
402
482
|
|
|
403
483
|
# Before the merge: build the branch, let goflag boot it, audit that.
|
|
404
484
|
seo:mr:
|
|
@@ -412,6 +492,7 @@ seo:mr:
|
|
|
412
492
|
- npx --yes "@goflag/cli@$GOFLAG_VERSION" http://localhost:3000
|
|
413
493
|
--start "pnpm start" --static --no-external
|
|
414
494
|
--baseline .goflag/baseline.json --regressions-only --max-debt 41
|
|
495
|
+
--report goflag-report.json
|
|
415
496
|
artifacts:
|
|
416
497
|
when: always
|
|
417
498
|
paths: [goflag-report.json]
|
|
@@ -449,9 +530,10 @@ jobs:
|
|
|
449
530
|
- run: corepack enable && pnpm install --frozen-lockfile
|
|
450
531
|
- run: pnpm build
|
|
451
532
|
- run: |
|
|
452
|
-
npx --yes @goflag/cli@0.
|
|
533
|
+
npx --yes @goflag/cli@0.2.8 http://localhost:3000 \
|
|
453
534
|
--start "pnpm start" --static --no-external \
|
|
454
|
-
--baseline .goflag/baseline.json --regressions-only --max-debt 41
|
|
535
|
+
--baseline .goflag/baseline.json --regressions-only --max-debt 41 \
|
|
536
|
+
--report goflag-report.json
|
|
455
537
|
- uses: actions/upload-artifact@v4
|
|
456
538
|
if: always()
|
|
457
539
|
with:
|
|
@@ -484,7 +566,8 @@ you what it accepted, and that number belongs in the commit message.
|
|
|
484
566
|
import { runAudit } from "@goflag/cli";
|
|
485
567
|
|
|
486
568
|
const report = await runAudit("https://example.com", { depth: 2 });
|
|
487
|
-
console.log(report.summary);
|
|
569
|
+
console.log(report.summary);
|
|
570
|
+
// { brokenLinks, missingTranslations, seoIssues, siteIssues, unreachablePages, verdict }
|
|
488
571
|
```
|
|
489
572
|
|
|
490
573
|
`runAudit` returns the same `GoflagReport` object the CLI emits.
|
|
@@ -543,9 +626,11 @@ pnpm typecheck # tsc --noEmit
|
|
|
543
626
|
pnpm format # prettier
|
|
544
627
|
```
|
|
545
628
|
|
|
546
|
-
`
|
|
547
|
-
|
|
548
|
-
|
|
629
|
+
`format` and `format:check` run once over the whole repository. `lint` runs
|
|
630
|
+
eslint once at the root and then fans out to any package that defines its own
|
|
631
|
+
`lint` script — `apps/website` does, with the Next config — and the rest fan out
|
|
632
|
+
across workspace packages with `pnpm -r`. To work on one package, filter:
|
|
633
|
+
`pnpm --filter @goflag/cli test`.
|
|
549
634
|
|
|
550
635
|
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).
|
|
551
636
|
|