Files
msd-core/scripts/changeset
Tom Boucher 9fe9da9830 fix(#2488): strip leading terminators so changeset bullets survive re-parse (#2492)
* fix(#2488): strip leading terminators so changeset bullets survive re-parse

A fragment body beginning with a line terminator rendered as an empty
`- ` bullet followed by an orphaned paragraph. `parseChangelog` treats a
non-indented line as terminating a bullet, so `github-release-notes.cjs`
silently dropped the entry when re-parsing CHANGELOG.md to build the
GitHub Release body.

Two independent causes, both in scripts/changeset/parse.cjs:

1. `extractDocsExempt` stripped trailing terminators but not leading
   ones. `DOCS_EXEMPT_RE` is `^...$` under /m, so removing a first-line
   `<!-- docs-exempt -->` marker left the `\n` that `$` does not consume.

2. `parseFragment` preserved the post-frontmatter body verbatim, so a
   blank line between the closing `---` and the first content line
   produced the same leading `\n` with no marker involved.

8 of 256 pending fragments were affected, split 4/4 across the two
causes — including the OpenCode MCP binding, the pi extension, and the
EoS adapters, all of which would have vanished from the v1.8.0 release
notes.

Regression tests cover both causes in LF and CRLF form, plus an
end-to-end serializeChangelog -> parseChangelog round-trip that pins the
user-visible defect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#2488): regenerate golden install fixtures for parse.cjs

scripts/ ships in the npm package and the installer, so the golden
install-parity fixtures record a content hash for every shipped file.
Editing scripts/changeset/parse.cjs drifts that hash and fails all 18
per-runtime parity tests.

Regenerated via `npm run gen:golden`. The diff is exactly one line per
fixture — the scripts/changeset/parse.cjs hash — with no unrelated drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:32:51 -04:00
..

changeset/ — release-notes tooling

This directory holds the scripts that turn per-PR fragments in .changeset/ and git history into the project's CHANGELOG.md and GitHub release notes.

The entry point is cli.cjs. It exposes three subcommands:

Subcommand Purpose
render Render a single version's changelog section from consolidated data.
github-release-notes Build GitHub release-notes body for a ref range.
extract Pull existing CHANGELOG.md entries that fall in a version range.

The rest of this document specifies the extract contract, because it is the surface most likely to be called by external tooling (CI workflows, npm scripts, release automation) that needs a stable exit-code and output guarantee to code against.


cli.cjs extract

Extract the changelog entries for every release in a version range, reading from an existing CHANGELOG.md. The range is --from exclusive, --to inclusive.

node scripts/changeset/cli.cjs extract --from VERSION --to VERSION \
  [--changelog FILE] [--repo <dir>] [--json]

Flags

Flag Required Description
--from VERSION Yes Lower bound, exclusive — entries equal to --from are not returned.
--to VERSION Yes Upper bound, inclusive — entries equal to --to are returned.
--changelog FILE No Path to the changelog to read. Defaults to <repo>/CHANGELOG.md.
--repo <dir> No Repo root used to locate CHANGELOG.md when --changelog is omitted. Defaults to the current working directory.
--json No Emit the structured report as JSON instead of rendered markdown.

Version validation

Both --from and --to must be stable triplet semver — MAJOR.MINOR.PATCH, digits only.

  • A leading v is accepted and stripped: v1.42.0 is treated as 1.42.0.
  • Pre-release and build suffixes are rejected: 1.42.0-rc.1, 1.42.0+build, and partial versions like 1.42.x all fail validation and exit 1.

Strict validation is deliberate. Coercing a malformed bound such as 1.42.x to 1.42.0 would silently change which releases the range selects, so a malformed bound is rejected early with a structured error rather than guessed at.

Changelog entries that are themselves pre-release or non-semver (and the Unreleased section) are skipped during matching; a notice for each skipped entry is written to stderr.

Exit codes

extract resolves to one of three exit codes. The output shape depends on whether --json is passed.

Exit Meaning Default stdout --json stdout
0 One or more releases fall in the range. Rendered markdown for the matched releases. { "releases": [ ... ], "from": "...", "to": "..." }
1 Bad input: --from/--to is not stable semver, a required flag is missing, or the changelog file was not found. Nothing (a missing-flag error and usage go to stderr). { "error": "<message>", "releases": [] }
2 Bounds are valid but no release falls in the range. A no releases found in range notice on stderr. { "releases": [], "from": "...", "to": "..." }

Notes for callers:

  • Treat exit 2 as "empty range", not "failure". For a well-formed invocation it means the request was understood and simply matched nothing — do not surface it as an error. (At the argument-parsing layer, malformed argv such as an unknown flag also exits 2; pass well-formed arguments and this overlap does not arise.)
  • In default (text) mode, a failure is signalled by the exit code alone — exit 1 from invalid semver or a missing changelog writes nothing to stdout. Machine consumers should pass --json to receive the error field.

Output shape

With --json, the report is pretty-printed JSON. The releases array contains one object per matched release (version, date, and parsed sections); from and to echo the normalized bounds. On exit 1, releases is empty and an error string describes the failure.

Without --json, exit 0 prints the matched releases as markdown, ready to paste into release notes:

## [1.42.0] - 2026-01-15

### Added

- New `--json` flag on the extract command (#3796)

### Fixed

- Trailing-slash handling in config paths (#3651)

Examples

Extract everything released after 1.41.0 up to and including 1.42.0:

node scripts/changeset/cli.cjs extract --from 1.41.0 --to 1.42.0

The same range as structured JSON, reading an explicit changelog file:

node scripts/changeset/cli.cjs extract \
  --from v1.41.0 --to v1.42.0 \
  --changelog ./CHANGELOG.md --json

Handle the three outcomes in a shell consumer:

if out=$(node scripts/changeset/cli.cjs extract --from "$FROM" --to "$TO" --json); then
  echo "$out"            # exit 0 — releases found
else
  case $? in
    2) echo "no releases in range — nothing to publish" ;;  # not an error
    *) echo "extract failed: $out" >&2; exit 1 ;;            # exit 1 — bad input
  esac
fi