Commit Graph

5 Commits

Author SHA1 Message Date
James Anderson 8091a2ec80 fix(release): require package-specific publish tags (#2624) 2026-07-14 20:26:18 +01:00
James Anderson 73156b36e7 fix(release): support beta prereleases (#2511) 2026-07-03 23:40:40 +01:00
James Anderson dbd6c6ea9c feat(release): reclassify commits via SHA-named changeset overrides (#1859)
* feat(release): reclassify commits via SHA-named changeset overrides

Auto-changesets are regenerated from commit subjects every push and never
committed to main, so a mislabeled commit can't be fixed by editing them, and a
hand-authored changeset can only raise a bump (max), never lower it.

Add a per-commit override: commit `.changeset/<sha>.md` and the release tooling
treats that commit as the bump declared in its frontmatter (patch->fix,
minor->feat, major->feat!, empty->chore/dropped), driving both the semver bump
and the changelog section. The file is a real changeset, so changesets/action
consumes it for the bump and deletes it on release -- overrides never accumulate.

Both create-changeset.mts (bump) and version.mts (changelog grouping) honor it,
keyed off the SHA in the filename.

* chore: reclassify #1804 as a fix

Override `feat(interception): sibling-style interception routes (#1804)`
(b4c829d6) to release as a patch-level fix instead of a minor feature.

* feat(release): use SHA-named override changeset body as the changelog message

The override changeset's body now becomes the reclassified commit's changelog
entry -- a plain bullet replacing the commit subject's description (scope and
all) -- in addition to overriding the bump and section. Leave the body empty to
keep the original subject and only reclassify the type.

Adds changesetBodyMessage(), threads an optional message through
rewriteSubjectType() and applyOverrides(), and captures it in loadOverrides().

* chore: set #1804 override changelog message

Now that the override body drives the changelog, give it a proper Bug Fixes
entry instead of a meta rationale.
2026-06-08 22:27:29 +01:00
James Anderson 639a8922b8 chore(release): resolve changelog range from latest tag so new packages get entries (#1761)
version.mts computed the changelog/contributor commit range from a tag ref
derived from the package's pre-bump package.json version (`tagRefFor(name,
before[name])`). For a brand-new package that has never been released there is
no matching tag, so this produced a non-existent ref (e.g. `v0.0.1`);
`git log v0.0.1..HEAD` throws, the catch returns [], and the package gets an
empty `## <version>` changelog section with zero entries and zero contributors.

This happened for real in #1759: the new @vinext/cloudflare package got an
empty `## 1.0.0` heading while vinext (with a real tag) got a full changelog.

Reuse `releaseRangeStart(name)` from create-changeset.mts, which resolves the
range from the latest *existing* tag (scoped `<name>@<version>` or legacy global
`v<version>`) and falls back to the first commit when none exists. This makes
version.mts consistent with the changeset generator and supports new/untagged
packages. The now-unused `tagRefFor` import is dropped.

Extracts the pure tag-selection logic into `latestTagVersionFromTags` and adds
focused unit tests for the scoped/global/new-package/no-tag cases.
2026-06-05 14:18:49 +00:00
James Anderson 61d5fe00a6 feat(release): commit-driven auto-generated changesets (#1753)
* feat(release): commit-driven changesets with auto-generated changesets

Adopt stock Changesets for versioning/changelog/publishing, with the only
bespoke surface being a script that creates changesets automatically from
Conventional Commits. Auto-generated changeset files never live on `main` —
they are written to the CI working tree, consumed by changesets/action into a
rolling Version PR, and discarded. Manually authored changesets still work.

- scripts/create-changeset.mjs: derive per-package bumps from Conventional
  Commits (paths -> package, type -> bump). Includes the version-vs-tag guard
  so a merged Version PR publishes instead of re-opening a PR.
- scripts/version.mjs: `changeset version` + append a `## Contributors` list to
  each bumped package's CHANGELOG.md (idempotent, pure rewrite unit-tested).
- .github/workflows/release-pr.yml: version-only changesets/action (no publish).
- .github/workflows/publish.yml: guarded OIDC publish (preserves
  `vp pm publish --provenance`); version now comes from `changeset version`.
- Reconcile packages/vinext version (0.0.5 -> 0.0.55) so the guard is coherent.

Unit tests for both scripts pass (35). The release orchestration (Version PR
creation, OIDC publish, gh contributor resolution) can only be validated in a
live CI run.

* refactor(release): collapse to a single changesets workflow

Let changesets/action own as much as possible. Delete the separate publish.yml
(guard job, manual bump, manual OIDC/tag/release/notify steps) and the
release-pr.yml split. One workflow now:

- create-changeset.mjs writes auto changesets to the working tree (its
  version-vs-tag guard yields nothing right after a Version PR merges).
- changesets/action maintains the Version PR and, when no changesets remain,
  publishes via `changeset publish` with OIDC trusted publishing + provenance,
  and creates the git tag + GitHub Release.

Removes the unused release:version script. Contributors list still handled by
scripts/version.mjs as the action's version command.

* refactor(release): convert release scripts to .mts, drop .d.mts files

Replace the .mjs + hand-written .d.mts declaration pairs with real TypeScript
(.mts) source. Node >=24 (the setup default) runs .mts directly via native type
stripping; .mts is unambiguously ESM so it needs no "type": "module" and emits
no MODULE_TYPELESS warning. The cross-import uses an explicit .mts specifier
(Node requires it), permitted in tsc via allowImportingTsExtensions (safe: the
project is noEmit).

- scripts/create-changeset.{mjs,d.mts} -> scripts/create-changeset.mts
- scripts/version.{mjs,d.mts}          -> scripts/version.mts
- tsconfig: allowImportingTsExtensions
- release.yml: run node scripts/*.mts

vp check (format + lint + types) clean; 35/35 unit tests pass.

* refactor(release): trim release scripts (~690→~470 source lines)

Cut comment bloat, remove dead code, tighten without dropping behavior:
- Condense verbose JSDoc/@param blocks to one-line purpose comments; keep the
  load-bearing "why" (correctness-rule header, insertContributors idempotency).
- Delete unused `newestChangelogVersion` (+ its tests) — exported/tested but
  never called.
- Tighten run()/insertContributors/latestTagVersion without behavior change.

Multi-package machinery and the bottom `## Contributors` list are retained per
requirements. vp check clean; 33 unit tests pass.

* feat(release): grouped conventional changelog + filter bot contributors

The default changesets changelog groups by bump level (### Minor Changes) and
renders our changeset summary as a nested bullet dump. Replace it with a real
conventional-commits changelog: version.mts now regroups each release's commits
into ### Features / ### Bug Fixes / ### Performance sections (scope bolded, type
prefix dropped) and rewrites the newest CHANGELOG section, then appends the
## Contributors list with [bot] accounts filtered out.

- create-changeset.mts: extract conventionalParts + collectReleaseCommits +
  releaseRangeStart and export the commit-walk so version.mts reuses it (no
  duplicate git logic).
- version.mts: groupedChangelogBody + rewriteReleaseSection (idempotent: only
  `## <digit>` is a section boundary); dedupeSortLogins drops `[bot]` logins.
- Tests updated for the new pure builders.

vp check clean; 33 unit tests pass. Verified end-to-end with a local dry run.

* refactor(release): simplify per PR review

- discoverPublishablePackages: scan only packages/* (publishable packages don't
  live elsewhere), drop the apps/examples/benchmarks scan.
- Delete isReleaseCommit: the "chore: version packages" release commit is a
  `chore`, already excluded by parseBumpFromSubject returning null. No need to
  hardcode release-message formats.

vp check clean; 31 unit tests pass.

* docs(release): clarify why the version-vs-tag guard exists

Per PR review: the guard was confusing. Document inline that it only suppresses
the post-merge/pre-publish window (version bumped, tag not yet created), which
would otherwise re-open a Version PR instead of publishing. Keeping the
never-on-main design per review decision.

* feat(release): sub-group changelog areas and humanize area names

Within each type section (### Features / ### Bug Fixes / …), areas (commit
scopes) with more than 3 items now get their own `#### <Area>` sub-group with
the scope prefix dropped; smaller areas and scopeless commits fall under
`#### Other`. When no area qualifies the list stays flat. Area names are
humanized (app-router → "App Router", css → "CSS", ppr → "PPR", i18n → "i18n")
in headings and in the bold prefixes.

Also: groupedChangelogBody now renders only known release types (GROUPS) — it
no longer leaks non-release types into an "Other Changes" bucket.

vp check clean; 32 unit tests pass. Verified with a local dry run.

* feat(release): rename changelog Other sub-group to Misc

* feat(release): sub-group areas with 3+ items (was >3)

* fix(release): address PR review (stale refs, contributor API, doc)

- Fix stale references to deleted files: .mjs → .mts and release-pr.yml →
  release.yml in .changeset/README.md and the release.yml header comment.
- resolveContributors: one paginated `gh api compare` call instead of one
  `gh api commits/<sha>` per commit (N round-trips → 1). This also removes
  version.mts's local git() helper, so no more `fatal:` probe noise from it.

No behavior change to the changelog/contributor output (verified via dry run:
same 4 contributors resolved). vp check clean; 32 unit tests pass.

The bot's "no git tags" bootstrap concern does not apply: tags v0.0.10..v0.0.55
exist and CI checks out with fetch-depth: 0, so the guard resolves 0.0.55.

* fix(release): run pinned changeset CLI; drop dead affectedPackages branch

Per second bonk review:
- Invoke the pinned, installed @changesets/cli via `vp exec changeset` instead
  of `vp dlx @changesets/cli` (which fetches a floating latest at run time) — in
  release.yml publish and version.mts. Removes the reproducibility gap vs the
  lockfile-pinned 2.31.0.
- affectedPackages: remove the unreachable `dir === "."` / empty-prefix branch
  now that discovery only returns `packages/*` keys.

vp check clean; 32 unit tests pass.

* fix(release): only emit valid GitHub logins in Contributors; doc fix

Per third bonk review:
- resolveContributors: `.author.login // empty` (was `// .commit.author.name`),
  so commits with an unlinked email contribute no entry instead of a raw git
  display name. dedupeSortLogins now keeps only `[a-zA-Z0-9-]+` shapes, which
  also subsumes the previous `[bot]` filter — no more broken `- @Full Name`
  mentions.
- .changeset/README.md: suggest `vp exec changeset` instead of the floating
  `vp dlx @changesets/cli` for manual changeset authoring.

vp check clean; 32 unit tests pass; dry run resolves the same valid handles.
2026-06-05 13:34:34 +00:00