Overview
The changelog contract for the AbsoluteJS packages.
@absolutejs/changelogv0.7.1betaDev ToolsThe changelog contract for the AbsoluteJS packages. Changes are written as typed entries the compiler checks, the release gate reconciles them against the package's own published types so a change nobody wrote down cannot ship, and migrations are data rather than prose so an upgrade can be applied instead of read.
bun add @absolutejs/changelogThe changelog contract for the AbsoluteJS packages.
A changelog is worth having when it can be trusted and acted on. Prose changelogs manage neither: they rot because writing them is a discipline, and they cannot be acted on because "refactored the server options" is a true sentence that does not say which export moved or what to write instead.
This package makes both possible.
Entries are typed. A change is written as TypeScript, so the compiler
insists that a breaking change names the exports it breaks and says how to migrate. The entries that cost somebody an afternoon are the ones that cannot be filed empty.
The release gate checks them against the package. Before a release goes
out, the exports that vanished or changed shape are compared with the exports the entries name. Forget an entry and the release stops. Nobody has to remember.
Migrations are data. A rename and a move are the overwhelming majority
of real breaking changes, and both are mechanical. Written as data they can be applied — by a CLI, an editor, or a hosted upgrade button — without running a line of code that came from a registry.
adopt creates changelog/unreleased/, keeps whatever CHANGELOG.md already said as changelog/history.md, adds changelog.json and CHANGELOG.md to the package's files, and makes sure your tsconfig compiles the entries — that last one matters, because an entry nothing compiles is an entry nobody checks.
Then add the gate to the release chain:
Put it after build: the check compares the types you are about to publish against the ones you published last time, and it needs dist to exist.
One file per change, so two branches adding one do not meet over the same line:
Which writes changelog/unreleased/start-is-now-listen.ts:
The import is type-only, so an entry has nothing to resolve at run time and the gate works in a checkout with no dependencies installed.
Checked where you type it
An entry can be written against the package's own API, and add scaffolds it that way when it finds one:
Suggested rather than required, for two reasons: a removed entry names something that has just stopped existing, and typeof Api cannot see type-only exports at all. The gate is the certain half — it reads the published .d.ts, which carries them.
The kinds
breaking and removed cost a consumer work, and the type refuses them without symbols and a migration. added, changed, deprecated, fixed, security and internal do not.
The migrations
Shape — What it means — Applied
rename: { from, to } — An export kept its meaning and changed its name — yes
moved: { symbol, from, to } — An export moved to another entry point — yes
resubpath: { from, to } — A whole entry point moved — yes
Works out the version from the entries — a breaking change moves the minor below 1.0.0 and the major above it, and a version already on a prerelease line moves along that line — then writes changelog.json and CHANGELOG.md, bumps package.json, and deletes the entries it consumed.
--as major|minor|patch|prerelease overrides the inference, --version x.y.z overrides it entirely, and --dry shows the release without writing anything.
every entry parses, and the disruptive ones carry what they must;
CHANGELOG.md is what the entries say it should be — it is generated, and
editing it is how the two copies drift;
the version in package.json and the newest release agree;
package.json still ships both documents, and nothing is sitting in the
entry directory that no release will read;
the entries name every export that moved since the newest published
version — not the one in package.json, which between release and publish is a version nobody can fetch;
and every migration describes what actually happened: a rename whose
destination this version does not export, a rename whose source is still exported, a move to an entry point the package does not have, a removal of something still there.
That last one is what makes an applicable migration safe to apply. A migration that reads correctly and rewrites working code into something that does not compile is the whole risk of automating an upgrade, and it fails the release instead.
--offline skips the two that talk to the registry.
adopt also writes prepublishOnly, so the gate runs however a publish was started — a release script, a bare npm publish, a CI job. A rule that can be walked around eventually is.
Anything deciding whether an upgrade is safe reads the published document:
For a package that has not adopted this yet, publishedSurface and diffSurface compare the published types of two versions instead — less than a changelog, and much more than nothing.
MIT.
Outcomes
The changelog contract for the AbsoluteJS packages.
adopt creates changelog/unreleased/, keeps whatever CHANGELOG.md already said as changelog/history.md, adds changelog.json and CHANGELOG.md to the package's files, and makes sure your tsconfig compiles the entries — that last one matters, because an entry nothing compiles is an entry nobody checks.
One file per change, so two branches adding one do not meet over the same line:
Hardening checklist
Follow in order
Working example for Adopting it.
bun add -d @absolutejs/changelog
bunx absolute-changelog adoptadopt creates changelog/unreleased/, keeps whatever CHANGELOG.md already said as changelog/history.md, adds changelog.json and CHANGELOG.md to the package's files, and makes sure your tsconfig compiles the entries — that last one matters, because an entry nothing compiles is an entry nobody checks.
"check:package": "bun run typecheck && bun run build && bun run test && absolute-changelog check"Supported entry points declared by this project’s package manifest. Internal dist paths are not part of the package contract.
Scripts declared by this project’s package manifest.
Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.