@goflag/cli 0.2.12 → 0.2.13

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 CHANGED
@@ -5,6 +5,12 @@ A CLI that audits a site for broken links, missing translation pages, a robots.t
5
5
  [![node](https://img.shields.io/badge/node-%3E%3D22-339933?logo=nodedotjs&logoColor=white)](https://github.com/tancredesimonin/goflag/blob/main/package.json)
6
6
  [![license: MIT](https://img.shields.io/badge/license-MIT-green)](#license)
7
7
 
8
+ [![A goflag run against a site with findings: a red verdict over the crawl counts, a COVERAGE line saying how many pages of the sitemap were audited, the tally of broken links, missing translations and SEO issues, then the broken links listed under the page that references them.](https://goflag.tech/assets/hero.png)](https://goflag.tech/docs/report)
9
+
10
+ <sub>The first eleven lines of a real run. Rendered from the same transcript the
11
+ documentation site paints, which is compared to the renderers byte for byte —
12
+ so this picture cannot drift from what the command prints.</sub>
13
+
8
14
  Goflag crawls a site once and judges what it found. 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.
9
15
 
10
16
  It is built for the mistakes that are invisible while browsing and expensive in
@@ -61,7 +67,7 @@ fine one.
61
67
 
62
68
  ## Where it runs
63
69
 
64
- Two packages ship to npm from a tag, published by GitLab CI over OIDC with no stored credential: `@goflag/cli` (`v*`) and `@goflag/next` (`next-v*`). The documentation site `apps/website` runs at [goflag.tech](https://goflag.tech), with `develop.goflag.tech` alongside it, both deployed with Kamal onto the shared OVH host described in the `infrastructure` repository.
70
+ Three packages ship to npm from a tag, published by GitLab CI over OIDC with no stored credential: `@goflag/cli` (`v*`), `@goflag/next` (`next-v*`) and `@goflag/og` (`og-v*`). The documentation site `apps/website` runs at [goflag.tech](https://goflag.tech), with `develop.goflag.tech` alongside it, both deployed with Kamal onto the shared OVH host described in the `infrastructure` repository.
65
71
 
66
72
  ## Your first audit
67
73
 
@@ -469,6 +475,11 @@ same confidence would be six unearned claims, so the file says which is which.
469
475
  It never gates — it exits 0 unless the run itself failed — and it prints the
470
476
  path it wrote, so `open "$(goflag preview http://localhost:3000)"` opens it.
471
477
 
478
+ An example of what it writes — a real audit, frozen, so its footer names the date it was
479
+ taken — is served at <https://goflag.tech/assets/example-preview.html>. The URL is absolute
480
+ because `prepack` copies this file into the package and npm renders it, where a relative link
481
+ is dead.
482
+
472
483
  ### Multilingual sites
473
484
 
474
485
  Discovery is seeded from the sitemap, not just from links. That matters because
@@ -547,7 +558,7 @@ Three rules, whichever you use:
547
558
  stages: [build, deploy, audit]
548
559
 
549
560
  variables:
550
- GOFLAG_VERSION: "0.2.12"
561
+ GOFLAG_VERSION: "0.2.13"
551
562
 
552
563
  # Before the merge: build the branch, let goflag boot it, audit that.
553
564
  seo:mr:
@@ -599,7 +610,7 @@ jobs:
599
610
  - run: corepack enable && pnpm install --frozen-lockfile
600
611
  - run: pnpm build
601
612
  - run: |
602
- npx --yes @goflag/cli@0.2.12 http://localhost:3000 \
613
+ npx --yes @goflag/cli@0.2.13 http://localhost:3000 \
603
614
  --start "pnpm start" --static --no-external \
604
615
  --baseline .goflag/baseline.json --regressions-only --max-debt 41 \
605
616
  --report goflag-report.json
@@ -621,6 +632,25 @@ Audit external links on a schedule instead, where nobody is waiting.
621
632
 
622
633
  ### Reading a red build
623
634
 
635
+ A red build says what moved, not what is wrong. The diff lists rule ids and
636
+ absolute page URLs; the messages and the fixes are in the report:
637
+
638
+ <!-- goflag:transcript gate -->
639
+
640
+ ```plaintext
641
+ goflag --regressions-only
642
+ REGRESSION 1 new · 13 known findings NOT gating this build · 1 resolved
643
+ baseline https://example.com/ — taken 2026-07-21T09:14:02.881Z (14 days ago)
644
+
645
+ New findings
646
+ + error seo canonical.absolute on https://example.com/pricing
647
+
648
+ Resolved
649
+ - warn seo og.image.missing on https://example.com/about
650
+ ```
651
+
652
+ <!-- /goflag:transcript -->
653
+
624
654
  The report artefact is the thing to open. It is the same JSON as `--json`, and
625
655
  it is kept whether the job passed or failed, because it is most wanted when it
626
656
  is red.
@@ -683,7 +713,7 @@ finding unrepresentable. Full documentation at
683
713
  Two of this catalogue's rules had no remedy to point at. `og.image.missing`
684
714
  fired 24 times on one site and `og.image.alt` 46 times on another, and the fix
685
715
  for both is "either an asset or a route you have to write" — which is how a rule
686
- becomes permanent debt. [`@goflag/og`](packages/og) is that route, written once.
716
+ becomes permanent debt. [`@goflag/og`](https://github.com/tancredesimonin/goflag/tree/main/packages/og) is that route, written once.
687
717
 
688
718
  ```tsx
689
719
  // app/[locale]/opengraph-image.tsx
@@ -701,7 +731,7 @@ embeds satori — turns into a PNG at build time, so nothing installs a second
701
731
  renderer and a card can be unit-tested with no framework build. It also packs
702
732
  the `favicon.ico` **no Next convention emits**, guarded so that a generated file
703
733
  living in git is not dirtied by every commit. API reference in
704
- [`packages/og/README.md`](packages/og/README.md).
734
+ [`packages/og/README.md`](https://github.com/tancredesimonin/goflag/blob/main/packages/og/README.md).
705
735
 
706
736
  ## Develop locally
707
737
 
@@ -6768,6 +6768,57 @@ function translationsPanel(current, siblings) {
6768
6768
  </table></div>
6769
6769
  <p class="foot">Lengths are graphemes, which is what a card's size ladder counts. The ladder is the site's own \u2014 <code>@goflag/og</code> ships none \u2014 so this reports the length and not the step.</p></section>`;
6770
6770
  }
6771
+ function pathSegments(path) {
6772
+ const cut = path.indexOf("?");
6773
+ const pathname = cut === -1 ? path : path.slice(0, cut);
6774
+ const search = cut === -1 ? "" : path.slice(cut);
6775
+ const segments2 = pathname.split("/").filter(Boolean);
6776
+ if (search === "") return segments2;
6777
+ return segments2.length === 0 ? [search] : [...segments2.slice(0, -1), `${segments2.at(-1)}${search}`];
6778
+ }
6779
+ function buildRouteTree(paths) {
6780
+ const root = { segments: [], children: /* @__PURE__ */ new Map(), count: 0 };
6781
+ paths.forEach((path, index) => {
6782
+ let node = root;
6783
+ node.count++;
6784
+ for (const segment of pathSegments(path)) {
6785
+ const child = node.children.get(segment) ?? {
6786
+ segments: [segment],
6787
+ children: /* @__PURE__ */ new Map(),
6788
+ count: 0
6789
+ };
6790
+ node.children.set(segment, child);
6791
+ child.count++;
6792
+ node = child;
6793
+ }
6794
+ node.index ??= index;
6795
+ });
6796
+ return { ...root, children: foldChildren(root.children) };
6797
+ }
6798
+ function foldChildren(children) {
6799
+ return new Map([...children].map(([key, node]) => [key, foldChain(node)]));
6800
+ }
6801
+ function foldChain(node) {
6802
+ let current = node;
6803
+ while (current.index === void 0 && current.children.size === 1) {
6804
+ const only = [...current.children.values()][0];
6805
+ current = { ...only, segments: [...current.segments, ...only.segments] };
6806
+ }
6807
+ return { ...current, children: foldChildren(current.children) };
6808
+ }
6809
+ function routeList(node, open, className = "") {
6810
+ const items = [...node.children.values()].map((child) => {
6811
+ const label = esc(child.segments.map((segment) => `/${segment}`).join(""));
6812
+ if (child.children.size === 0) {
6813
+ return child.index === void 0 ? `<li class="leaf"><span>${label}</span></li>` : `<li class="leaf"><a href="#page-${child.index}">${label}</a></li>`;
6814
+ }
6815
+ const self = child.index === void 0 ? `<span class="go"></span>` : `<a class="go" href="#page-${child.index}" title="the page at this path">\u2197</a>`;
6816
+ return `<li><details${open ? " open" : ""}><summary><span class="seg">${label}</span><span class="n">${child.count}</span>${self}</summary>
6817
+ ${routeList(child, open)}</details></li>`;
6818
+ });
6819
+ return `<ul${className ? ` class="${className}"` : ""}>${items.join("\n")}</ul>`;
6820
+ }
6821
+ var TREE_OPEN_MAX = 20;
6771
6822
  function tagLabel(tag) {
6772
6823
  const key = tag.property ?? tag.name ?? (tag.httpEquiv ? `http-equiv=${tag.httpEquiv}` : void 0);
6773
6824
  if (key) return `${key}${tag.content ? ` = ${clip(tag.content, 120)}` : ""}`;
@@ -6837,9 +6888,21 @@ h1,h2,h3,.badge,.chip,.rid,.src,.from,.mono,code,pre,dt,.pin,.nav{font-family:va
6837
6888
  h1{font-size:22px;letter-spacing:-.02em;margin:0 0 6px}
6838
6889
  .lede{color:var(--ink2);margin:0;max-width:70ch}
6839
6890
  header.doc{padding:44px 0 24px;border-bottom:1px solid var(--line)}
6840
- .nav{display:flex;flex-wrap:wrap;gap:8px;margin:20px 0 0;padding:0;list-style:none;font-size:12px}
6841
- .nav a{display:inline-block;padding:4px 9px;border:1px solid var(--line2);border-radius:3px;color:var(--ink2);text-decoration:none}
6842
- .nav a:hover,.nav a:focus-visible{border-color:var(--brand);color:var(--brand)}
6891
+ .nav{margin:22px 0 0;max-width:620px}
6892
+ .navnote{margin:0 0 8px;font-family:var(--ui);font-size:12px;color:var(--ink3)}
6893
+ .tree,.tree ul{list-style:none;margin:0;padding:0;font-size:12px}
6894
+ .tree ul{margin-left:6px;padding-left:12px;border-left:1px solid var(--line)}
6895
+ .tree summary{display:flex;align-items:center;gap:10px;padding:3px 7px;border-radius:3px;color:var(--ink2);cursor:pointer;list-style:none}
6896
+ .tree summary::-webkit-details-marker{display:none}
6897
+ .tree summary::before{content:"\u25B8";flex:none;width:1ch;font-size:9px;color:var(--ink3)}
6898
+ .tree details[open]>summary::before{content:"\u25BE"}
6899
+ .tree summary:hover,.tree summary:focus-visible{background:var(--wash);color:var(--ink)}
6900
+ .tree .seg{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6901
+ .tree .n{margin-left:auto;flex:none;font-size:10px;color:var(--ink3);font-variant-numeric:tabular-nums}
6902
+ .tree .go{flex:none;width:1.2em;text-align:right;font-size:11px;text-decoration:none;opacity:.55}
6903
+ .tree .go:hover,.tree .go:focus-visible{opacity:1}
6904
+ .tree .leaf a,.tree .leaf span{display:block;padding:3px 7px 3px 24px;color:var(--ink2);text-decoration:none;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6905
+ .tree .leaf a:hover,.tree .leaf a:focus-visible{background:var(--wash);color:var(--brand)}
6843
6906
  .page{padding-top:52px}
6844
6907
  .pagehead h2{font-size:16px;margin:0 0 4px}
6845
6908
  .pagehead .url{margin:0;font-size:13px;color:var(--ink2);word-break:break-all}
@@ -6978,9 +7041,14 @@ function renderPreview(report, options = {}) {
6978
7041
  byRoute.get(splitRoute(pathOf(extraction.http.finalUrl)).route) ?? []
6979
7042
  )
6980
7043
  ).join("\n");
6981
- const nav = extractions.length > 1 ? `<ul class="nav">${extractions.map(
6982
- (extraction, index) => `<li><a href="#page-${index}">${esc(pathOf(extraction.http.finalUrl))}</a></li>`
6983
- ).join("")}</ul>` : "";
7044
+ const nav = extractions.length > 1 ? `<nav class="nav">
7045
+ <p class="navnote">${extractions.length} pages, by path. A folder folds; the \u2197 beside one opens the page at that path itself.</p>
7046
+ ${routeList(
7047
+ buildRouteTree(extractions.map((extraction) => pathOf(extraction.http.finalUrl))),
7048
+ extractions.length <= TREE_OPEN_MAX,
7049
+ "tree"
7050
+ )}
7051
+ </nav>` : "";
6984
7052
  return `<!doctype html>
6985
7053
  <html lang="en">
6986
7054
  <head>
@@ -7294,4 +7362,4 @@ export {
7294
7362
  summarize,
7295
7363
  Logger
7296
7364
  };
7297
- //# sourceMappingURL=chunk-QKXUKHXB.js.map
7365
+ //# sourceMappingURL=chunk-57NVXEJS.js.map