fix(ci): stop publishing on package-local non-shipping paths - #3080
Conversation
Coverage Report for ./packages/components/
File CoverageNo changed files found. |
🚀 Preview DeploymentPreview environments are ready:
Images:
|
Relevance was path-based but only knew repo-ROOT prefixes, so everything under `packages/**` counted as publish-relevant. #3010 cut 1.0.14 from `packages/components/.storybook/preview.tsx` alone; #3006 cut 1.0.12 from a scripts-only manifest, a `CONTRIBUTE.md`, `dev/cross-version/**` and `src/tests/visual/**`. Neither can reach a consumer. Two changes to the classifier: - Package-local denylist entries, segment-exact under `packages/<name>/`: `.storybook/**`, `e2e/**`, `src/tests/**`, `dev/cross-version/**`, `dev/vitest/**`, `CONTRIBUTE.md`, plus `*.stories.tsx` and `*.test.*` anywhere. `classifyPath` replaces the bare `startsWith` and returns the rule that decided, which the skip notice now names. - The key-diff refinement applies to every `packages/*/package.json`, not just the root one: a `scripts`-only diff cannot reach a consumer. `classifyRootManifestChange` becomes `classifyManifestChange` and the `decide` job fetches before/after for each changed manifest into `MANIFEST_SNAPSHOT_DIR`. `packages/*/dev/**` is deliberately NOT denylisted, although #3023 proposed it. In `components` and `codemods` that directory IS the build: `dev/vite/*` holds the plugins `vite.build.config.ts` imports, `dev/createDocPropertiesJson.ts` writes the shipped `dist/assets/doc-properties.json`, `dev/remote-components-generator/**` generates `view.ts` and `src/auto-generated/**`, and `codemods`' build script is `tsx dev/generateCli.ts && …`. Only the two test-harness subtrees are safe. Relevance stays path-based — no gate on commit type. `chore(deps):` and `refactor:` change what consumers resolve. For the empty changelog on real releases, `lerna.json` now configures `changelogPreset` as an object and unhides every type. A release exists only because something can reach a consumer, so it deserves a line; `chore(deps): bump …` and a `docs:` commit on a shipped `AGENTS.md` (#2954, 1.0.11) are exactly the cases a path gate must not suppress. `changelog-preset.test.mjs` asserts every type renders. No bump semantics change: without `bumpStrict` the preset's `whatBump` returns patch for any non-empty range regardless. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
baa548d to
28fdb39
Compare
|
Superseded by #3098 and closing — this branch's commit is contained in it verbatim, so nothing here is lost. #3098 builds the next step on top: the PR title decides whether a merge releases and this classifier becomes its verifier, on the PR (a new blocking It also adds 🤖 Closed by Claude Code |
Release relevance is decided by PATH, the changelog by TYPE, and the two never agreed. This fixes both halves.
Problem 1 — needless releases
The denylist knew only repo-ROOT prefixes, matched with
startsWith, so everything underpackages/**was publish-relevant wholesale. #3010 cut 1.0.14 frompackages/components/.storybook/preview.tsxalone. #3006 cut 1.0.12 from a scripts-only package manifest, a package-localCONTRIBUTE.md,dev/cross-version/**andsrc/tests/visual/**. None of it reaches a consumer.Package-local denylist entries, segment-exact under
packages/<name>/—e2e-helpers/is note2e/:.storybook/**,e2e/**,src/tests/**,dev/cross-version/**,dev/vitest/**CONTRIBUTE.md(no package lists it infiles)*.stories.tsxand*.test.*anywhere —.test.is not always the last extension (*.browser.test.remote.tsx)isPublishRelevantis now a wrapper aroundclassifyPath, which also returns the rule that decided. That is what the skip notice prints.The key-diff refinement now applies to every
packages/*/package.json, not just the root one — ascripts-only diff cannot reach a consumer.classifyRootManifestChange→classifyManifestChange(one extrapathargument for the reason), and thedecidejob fetches before/after for each changed root/packages/*manifest intoMANIFEST_SNAPSHOT_DIR.apps/*manifests are skipped — they are already irrelevant by path.Published tarballs do contain
scripts. The argument is "no consumer effect", not "not in the tarball" — nothing a consumer installs, imports or runs reads a package'stest:unit. Same for the story and test.d.tsthatunplugin-dtsemits: they ship, and noexportspath reaches them. Written into the code where the rules live.packages/*/dev/**is deliberately NOT denylistedThe issue proposed it; the repo says no. In
componentsandcodemodsthat directory is the build:dev/vite/*— the PostCSS/rollup pluginsvite.build.config.tsimports; a change there moves the emitted CSSdev/createDocPropertiesJson.ts— writes the shippeddist/assets/doc-properties.jsondev/remote-components-generator/**— generatesview.tsandsrc/auto-generated/**packages/codemods' wholebuildscript istsx dev/generateCli.ts && tsc … && tsx dev/buildTransforms.tsTwo of those produce dist-only artifacts, so such a change arrives with no committed generated file beside it — a blanket
dev/**entry would swallow that release silently, the one failure mode the denylist direction exists to prevent. Only the two provably test-only subtrees (dev/cross-version/,dev/vitest/) are listed, which is enough for the 1.0.12 evidence. Four counter-examples are pinned by tests.Relevance stays path-based: no gate on commit type.
chore(deps):andrefactor:routinely change what consumers resolve.Problem 2 — real releases with an empty changelog
Decision: unhide every type.
lerna.json'schangelogPresetbecomes an object and respells the fulltypeslist with everyhidden: truedropped.Why not "accept Version bump only and document it": relevance is already decided by path, so after this PR a release exists only because something can reach a consumer. If a commit was worth a release it is worth a line. Suppressing those releases would be wrong —
chore(deps): bump framer-motion …changes what consumers resolve, and adocs:commit on a shippedAGENTS.mdreally does change the tarball (#2954 → 1.0.11). Unhiding per type (chore/build/refactoronly) would leavetest:,style:anddocs:able to release into an empty changelog; unhiding all leaves no residual case.Cost is bounded: a section renders only when it has commits, and the types that previously released while hidden are now largely skipped by problem 1.
No bump semantics change: without
bumpStrictthe preset'swhatBumpreturns patch for any non-empty commit range regardless ofhidden. The preset stays pinned to^9per docs/release-workflow.md —10.xtrips Lerna-Lite 5's legacy-writer path. Lerna-Lite passes the object straight to the preset factory (GetChangelogConfig.getChangelogConfigreadschangelogPreset.nameand forwards the object aspresetConfig), so the auto-prefix toconventional-changelog-conventionalcommitsstill happens.Verification
node --test .github/scripts/*.test.mjs— 63 pass; this is the commandtest.ymlruns, andpublish.yml'sdecidejob self-tests the classifier the same way. 17 new/changed relevance cases cover every new path, the mixed case, the scripts-only manifest at root and package level, and the segment-boundary negatives.packages/*/package.jsonand assertsfileslists nothing butdistand shipped.md. That is the invariant the package-local denylist rests on; if a package starts shippingsrcorCONTRIBUTE.md, it fails.changelog-preset.test.mjsdrives the real preset'swriter.transformper type. Reverted to the old string preset it fails 4/4, so it is not vacuous.decidejob'srunblock: YAML parsed,bash -nclean, and the fetch loop exercised offline — slugs match the guard'spath.replaceAll("/", "__"),apps/docs/package.jsonis not fetched, and a push with no manifest iterates zero times underset -euo pipefail.pnpm lintclean (0 errors; the 71 pre-existingreact-hooks/exhaustive-depswarnings are untouched).workflow_dispatchis untouched: thegithub.event_name != "push"branch still returnspublish=truebefore any classification.decidejob, sonextbehaves identically — onnextthe classified set is what thechore(sync):forward-merge carried over. Symmetry verified by reading, not by a run.Not verifiable locally: the actual
gh apicontents fetch and the rendered::notice::in a real run, and that a merge produces no tag/GitHub Release. Both need a push to a release line.SKIP_INSTALL_SIMPLE_GIT_HOOKSalready sits inpublish.yml'senvand stays. No nx wiring needed —.github/scripts/**is outside the graph, andtest.ymlpicks the new test up through its*.test.mjsglob.fixes #3023
🤖 Generated with Claude Code