Skip to content

Automate the frontend third-party licence inventory - #390

Merged
lamemustafa merged 5 commits into
masterfrom
chore/frontend-license-generator
Sep 15, 2026
Merged

lamemustafa merged 5 commits into
masterfrom
chore/frontend-license-generator

Conversation

@lamemustafa

Copy link
Copy Markdown
Owner

Removes the last hand-maintained link in the dependency chain.

The choke point

The Rust inventory has been automated for some time (scripts/generate-rust-licenses.mjslicense:generate:rust). The frontend equivalent never existed, so THIRD_PARTY_LICENSES.txt was hand-maintained: every frontend dependency bump required a human to hand-edit version headers before check-dependency-inventory.mjs --frontend would pass.

That is exactly the tax issues #349/#350/#351 describe, and it was paid by hand twice today — once in #386's consolidated bump, once before that.

What this adds

scripts/generate-frontend-licenses.mjs reads pnpm licenses list --prod --json (behind the same npm_execpath guard the checker uses, so it must run through a pnpm script), resolves each package's own LICENSE file from its install path, groups packages sharing byte-identical licence text, and writes the file in the existing format — no redesign.

Wired as license:generate:frontend, mirroring the Rust wiring, plus a license:generate that runs both.

Regenerating found two real defects in the hand-maintained file

  1. lucide-react's header underline was 20 dashes under a 19-character header — stale from an earlier version bump. Precisely the class of drift this exists to eliminate.
  2. The react/react-dom/scheduler group cited facebook/react. react and react-dom both now declare react/react in their published package.json; only scheduler still says facebook. The generator takes the first-alphabetical member's URL and warns at generation time when a group disagrees, rather than silently picking one:
    react, react-dom, scheduler share license text but report different repository URLs (...); using react's: ...

Those are the only two differences from the committed file. Both are the generator being right and the hand-maintained file being stale.

Verification

  • Determinism: run twice, diff clean, md5 identical (603729bc… both runs). Also covered by an automated test.
  • Simulated bump — the actual point of the task: bumped lucide-react ^1.41.0 → ^1.46.0, ran pnpm install --lockfile-only, license:generate:frontend, then license:allpassed with zero hand-editing. Reverted afterwards.
  • The checker is not made vacuous: hand-corrupting the generated file (scheduler 0.27.09.9.9) still fails with frontend third-party inventory drift (missing: scheduler 0.27.0; stale: scheduler 9.9.9).
  • license:all passes: 5 frontend and 383 Rust locked components.
  • Full local suite: 155/155 (node --test scripts/*.test.mjs), including 8 new tests.
  • package.json is pinned; surface resealed in the documented order, confirming pass reports 0.

Fallbacks are loud, not silent

All 5 current production dependencies resolved cleanly from their own bundled LICENSE files. For future ones: an A OR B expression with no per-licence file falls back to the package's generic LICENSE with a console.warn; a compound AND expression, or a package with no matching licence file at all, makes the generator throw with a specific diagnostic. It never emits UNKNOWN for a package that actually has a licence.

Pre-existing gap flagged, not fixed

ci.yml's bundle path filter never included generate-rust-licenses.mjs, and now also omits generate-frontend-licenses.mjs — so a change to either generator does not trigger the bundle job. Noted in docs/proposed-license-automation.md along with proposed CI wiring for a Dependabot auto-regenerate step. Neither applied here; .github/ was out of scope for the agent that wrote it.

Refs #349, #350, #351

🤖 Generated with Claude Code

THIRD_PARTY_LICENSES.txt was the last hand-maintained link in the
dependency chain. The Rust inventory has had generate-rust-licenses.mjs
for some time; the frontend equivalent did not exist, so every frontend
bump required a human to hand-edit version headers before
check-dependency-inventory.mjs --frontend would pass. That tax is what
issues #349, #350 and #351 describe, and it was paid by hand twice today.

scripts/generate-frontend-licenses.mjs reads pnpm licenses list --prod
--json, resolves each package's own LICENSE file from its install path,
groups packages sharing byte-identical licence text, and writes the file
in the existing format. It is wired as license:generate:frontend, mirroring
the Rust wiring, plus a license:generate that runs both.

Regenerating revealed two defects in the hand-maintained file:

- lucide-react's header underline was 20 dashes under a 19-character
  header, left over from an earlier version bump. Precisely the class of
  drift this replaces.
- The react/react-dom/scheduler group cited facebook/react. react and
  react-dom now both declare react/react in their published package.json;
  only scheduler still says facebook. The generator takes the
  first-alphabetical member's URL and warns at generation time when a
  group disagrees, rather than silently choosing.

Determinism proven by running twice and comparing md5. A simulated
lucide-react bump regenerated and passed license:all with no hand-editing.
Corrupting the generated file still fails check-dependency-inventory with
a precise drift message, so the checker is not made vacuous.

package.json is pinned; the compatibility surface was resealed in the
documented order and a confirming rehash reports zero changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

t and others added 4 commits September 15, 2026 16:44
…nse-generator

# Conflicts:
#	THIRD_PARTY_LICENSES.txt
#	docs/tally/compatibility/compatibility-matrix.json
#	docs/tally/compatibility/compatibility-surface.json
The previous commit on this branch pushed unresolved conflict markers in
THIRD_PARTY_LICENSES.txt: the merge left that file conflicted alongside the
two compatibility manifests, and only the manifests were resolved before
staging.

Regenerating with the generator this branch adds resolves it outright --
which is the point of the change. license:all now passes on 5 frontend and
384 Rust locked components, and two consecutive generator runs produce a
byte-identical file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lamemustafa
lamemustafa merged commit 574d5eb into master Sep 15, 2026
12 checks passed
@lamemustafa
lamemustafa deleted the chore/frontend-license-generator branch September 15, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant