Skip to content

Patch releases with an empty changelog: relevance is path-based, the changelog is type-based #3023

Description

@ins0

Four of the fourteen post-1.0 releases carry no changelog entry at all — 1.0.4, 1.0.11, 1.0.12 and 1.0.14 all read **Note:** Version bump only for package @mittwald/flow-project. There are two separate causes behind that, and only one of them is a bug.

Why it happens

Two independent criteria decide, and they can never agree:

  • Whether a push releases is decided by PATHS. The decide job in publish.yml classifies the changed files with .github/scripts/release-relevance-lib.mjs. Its denylist knows only repo-root prefixes (.github/, apps/, docs/, dev/, …), so everything under packages/** is publish-relevant wholesale. The commit type plays no role.
  • What lands in the changelog is decided by TYPE. The conventionalcommits preset marks docs, style, chore, refactor, test, build and ci as hidden: true. Only feat, fix, perf and revert render. Nothing left to write → lerna emits "Version bump only".
  • The bump happens either way. lerna version --force-publish --conventional-commits: the preset's whatBump returns level 2 (patch) for any non-empty commit range unless bumpStrict is set, and --force-publish bypasses changed-package detection by design (Publish holes: versioned-but-unpublished releases break exact peer pins #2887).

Evidence

Version Trigger Reaches a tarball?
1.0.14 build(components) #3010 — only packages/components/.storybook/preview.tsx No. files: ["*.md", "dist"]; the Storybook config is never built
1.0.12 ci: #3006 — in remote-react-components: package.json (scripts only, --project=unitunit*), CONTRIBUTE.md, dev/cross-version/**, src/tests/visual/** No. files: ["dist"]
1.0.11 docs(ColumnLayout) #2954 — incl. packages/components/AGENTS.md Yes*.md ships. The release is correct by design; docs: is merely invisible
1.0.4 docs: upgrade fumadocs-mdx #2959 — an apps/docs dependency + the lockfile No — already fixed by #2984

Two problems, not one

  1. Needless releases (1.0.14, 1.0.12). The relevance gate is too coarse inside packages/**. .storybook/, package-local dev/, e2e/, src/tests/ and a scripts-only diff in a package manifest provably cannot affect a consumer. The scripts case stings twice: classifyRootManifestChange exists for exactly that reason (test(docs): check the documentation's internal links in CI #2970) but is applied to the root manifest only.
  2. Real releases with an empty changelog (1.0.11, and every chore(deps): bump). chore(deps): bump framer-motion … changes what consumers resolve — the changelog never says so. Suppressing those releases would be wrong; the changelog is what has to admit them.

The denylist direction stays as it is: unknown paths are relevant, so a forgotten docs path costs one needless version while a forgotten source path would swallow a real release. Only paths that are provably not consumer-visible get added.

What to check

  1. Extend the denylist with package-local non-shipping paths: packages/*/.storybook/**, packages/*/dev/**, packages/*/e2e/**, packages/*/src/tests/**, plus *.stories.tsx and *.test.*. IRRELEVANT_PREFIXES is matched with startsWith today, so per-segment or glob matching is new machinery — keep it minimal and cover it with tests.
  2. Apply the key-diff refinement to packages/*/package.json too: a scripts-only change cannot reach a consumer. classifyRootManifestChange is reusable, but the decide job then has to fetch before/after for every changed package manifest, not just the root one. Note that published tarballs do contain scripts — the argument is "no consumer effect", not "not in the tarball". Write that down where the code says it.
  3. Do not gate on commit type. chore(deps): and refactor: routinely change the tarball; a type gate would silently swallow those releases. This is why Docs- and CI-only merges publish a full release of all 12 packages #2931 chose paths.
  4. Decide what to do about problem 2. Either give chore/build/refactor a visible changelog section (changelogPreset in lerna.json accepts an object with types), or accept "Version bump only" as the honest signal for them and document it. Note the preset is pinned to ^9 for the reason in docs/release-workflow.md.
  5. Verify the next line behaves symmetrically — it runs through the same decide job.

Acceptance criteria

  • A merge touching only packages/*/.storybook/**, packages/*/dev/**, packages/*/e2e/**, packages/*/src/tests/**, or carrying a scripts-only package-manifest diff, produces no npm publish, no tag, no GitHub Release and no chore(release): commit.
  • A merge touching package source, locales, SCSS, generated code, a package's shipped *.md or any dependency block publishes exactly as today.
  • A mixed merge publishes.
  • workflow_dispatch still forces a publish — the manual escape hatch stays.
  • The skip is visible in the run (::notice::) and names the rule that fired.
  • release-relevance-lib.test.mjs covers every new path, incl. the mixed case and the scripts-only manifest.
  • docs/release-workflow.md updated: the denylist section names the new package-local exclusions and the reasoning.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    post-1.0Does not block the 1.0.0 cut — lands after 1.0.0toolingCI, config of build-tools... the painful things not related to the bare source code

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions