@absolutejs/changelog 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@absolutejs/changelog`.
4
+
5
+ This file is generated by `absolute-changelog` from the entries in
6
+ `changelog/`. Edit an entry, not this file — and add new ones under
7
+ `changelog/unreleased/`.
8
+
9
+ ## 0.1.0 — 2026-09-10
10
+
11
+ ### Added
12
+
13
+ - **The changelog contract: typed entries, a gate that checks them against the package's own types, and migrations as data**
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Kahn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @absolutejs/changelog
2
+
3
+ The changelog contract for the AbsoluteJS packages.
4
+
5
+ A changelog is worth having when it can be trusted and acted on. Prose
6
+ changelogs manage neither: they rot because writing them is a discipline, and
7
+ they cannot be acted on because "refactored the server options" is a true
8
+ sentence that does not say which export moved or what to write instead.
9
+
10
+ This package makes both possible.
11
+
12
+ - **Entries are typed.** A change is written as TypeScript, so the compiler
13
+ insists that a breaking change names the exports it breaks and says how to
14
+ migrate. The entries that cost somebody an afternoon are the ones that
15
+ cannot be filed empty.
16
+ - **The release gate checks them against the package.** Before a release goes
17
+ out, the exports that vanished or changed shape are compared with the
18
+ exports the entries name. Forget an entry and the release stops. Nobody has
19
+ to remember.
20
+ - **Migrations are data.** A rename and a move are the overwhelming majority
21
+ of real breaking changes, and both are mechanical. Written as data they can
22
+ be applied — by a CLI, an editor, or a hosted upgrade button — without
23
+ running a line of code that came from a registry.
24
+
25
+ ## Adopting it
26
+
27
+ ```sh
28
+ bun add -d @absolutejs/changelog
29
+ bunx absolute-changelog adopt
30
+ ```
31
+
32
+ `adopt` creates `changelog/unreleased/`, keeps whatever `CHANGELOG.md` already
33
+ said as `changelog/history.md`, adds `changelog.json` and `CHANGELOG.md` to the
34
+ package's `files`, and makes sure your tsconfig compiles the entries — that
35
+ last one matters, because an entry nothing compiles is an entry nobody checks.
36
+
37
+ Then add the gate to the release chain:
38
+
39
+ ```json
40
+ "check:package": "bun run typecheck && bun run build && bun run test && absolute-changelog check"
41
+ ```
42
+
43
+ Put it after `build`: the check compares the types you are about to publish
44
+ against the ones you published last time, and it needs `dist` to exist.
45
+
46
+ ## Writing a change
47
+
48
+ One file per change, so two branches adding one do not meet over the same
49
+ line:
50
+
51
+ ```sh
52
+ bunx absolute-changelog add --kind added --summary "listen accepts a unix socket path"
53
+
54
+ bunx absolute-changelog add \
55
+ --kind breaking \
56
+ --summary "start is now listen" \
57
+ --symbol start \
58
+ --instruction "Import listen instead of start." \
59
+ --rename-to listen
60
+ ```
61
+
62
+ Which writes `changelog/unreleased/start-is-now-listen.ts`:
63
+
64
+ ```ts
65
+ import type { Change } from "@absolutejs/changelog";
66
+
67
+ const entry: Change = {
68
+ kind: "breaking",
69
+ migration: {
70
+ instruction: "Import listen instead of start.",
71
+ rename: { from: "start", to: "listen" },
72
+ },
73
+ summary: "start is now listen",
74
+ symbols: ["start"],
75
+ };
76
+
77
+ export default entry;
78
+ ```
79
+
80
+ The import is type-only, so an entry has nothing to resolve at run time and
81
+ the gate works in a checkout with no dependencies installed.
82
+
83
+ ### The kinds
84
+
85
+ `breaking` and `removed` cost a consumer work, and the type refuses them
86
+ without `symbols` and a `migration`. `added`, `changed`, `deprecated`,
87
+ `fixed`, `security` and `internal` do not.
88
+
89
+ ### The migrations
90
+
91
+ | Shape | What it means | Applied |
92
+ | ----------------------------- | ----------------------------------------------- | ------- |
93
+ | `rename: { from, to }` | An export kept its meaning and changed its name | yes |
94
+ | `moved: { symbol, from, to }` | An export moved to another entry point | yes |
95
+ | `resubpath: { from, to }` | A whole entry point moved | yes |
96
+ | `manual: true` | Somebody has to read the call | no |
97
+
98
+ `manual` is not a failure. A changed meaning is not a rewrite anybody should
99
+ automate, and pretending otherwise is worse than saying so.
100
+
101
+ ## Releasing
102
+
103
+ ```sh
104
+ bunx absolute-changelog release
105
+ ```
106
+
107
+ Works out the version from the entries — a breaking change moves the minor
108
+ below `1.0.0` and the major above it, and a version already on a prerelease
109
+ line moves along that line — then writes `changelog.json` and `CHANGELOG.md`,
110
+ bumps `package.json`, and deletes the entries it consumed.
111
+
112
+ `--as major|minor|patch|prerelease` overrides the inference, `--version x.y.z`
113
+ overrides it entirely, and `--dry` shows the release without writing anything.
114
+
115
+ ## The gate
116
+
117
+ ```sh
118
+ bunx absolute-changelog check
119
+ ```
120
+
121
+ - every entry parses, and the disruptive ones carry what they must;
122
+ - `CHANGELOG.md` is what the entries say it should be — it is generated, and
123
+ editing it is how the two copies drift;
124
+ - the version in `package.json` and the newest release agree;
125
+ - the entries name every export that moved since the last published version.
126
+
127
+ `--offline` skips the last one, which is the only one that talks to the
128
+ registry.
129
+
130
+ ## Reading somebody else's
131
+
132
+ Anything deciding whether an upgrade is safe reads the published document:
133
+
134
+ ```ts
135
+ import {
136
+ applyMigrations,
137
+ migrationsBetween,
138
+ publishedChangelog,
139
+ } from "@absolutejs/changelog";
140
+
141
+ const changelog = await publishedChangelog("@absolutejs/example", "2.1.0");
142
+ const migrations = changelog
143
+ ? migrationsBetween(changelog, "1.4.0", "2.1.0")
144
+ : [];
145
+
146
+ const upgraded = applyMigrations(source, {
147
+ migrations: migrations.map((at) => at.migration),
148
+ packageName: "@absolutejs/example",
149
+ });
150
+ ```
151
+
152
+ For a package that has not adopted this yet, `publishedSurface` and
153
+ `diffSurface` compare the published types of two versions instead — less than
154
+ a changelog, and much more than nothing.
155
+
156
+ ## Licence
157
+
158
+ MIT.
package/changelog.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "contract": 1,
3
+ "name": "@absolutejs/changelog",
4
+ "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "kind": "added",
9
+ "summary": "The changelog contract: typed entries, a gate that checks them against the package's own types, and migrations as data"
10
+ }
11
+ ],
12
+ "date": "2026-09-10",
13
+ "version": "0.1.0"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,34 @@
1
+ import { type Migration } from "./types";
2
+ export type MigrationResult = {
3
+ /** Whether anything in this file changed. */
4
+ changed: boolean;
5
+ /** Anything the reader of the diff should know. */
6
+ notes: string[];
7
+ source: string;
8
+ };
9
+ /**
10
+ * Apply one migration to one file.
11
+ *
12
+ * A file the migration does not touch comes back unchanged, which is the
13
+ * ordinary case: a project imports a dozen names from a package that moved a
14
+ * hundred.
15
+ */
16
+ export declare const applyMigration: (source: string, input: {
17
+ migration: Migration;
18
+ packageName: string;
19
+ }) => {
20
+ changed: boolean;
21
+ notes: string[];
22
+ source: string;
23
+ };
24
+ /**
25
+ * Apply migrations in order.
26
+ *
27
+ * Order matters: a symbol renamed in one release and moved in the next has to
28
+ * be renamed first, or the move goes looking for a name that is no longer
29
+ * there.
30
+ */
31
+ export declare const applyMigrations: (source: string, input: {
32
+ migrations: readonly Migration[];
33
+ packageName: string;
34
+ }) => MigrationResult;
@@ -0,0 +1,79 @@
1
+ import type { Change, Migration } from "./types";
2
+ /**
3
+ * Record one change.
4
+ *
5
+ * Written as its own file under `changelog/unreleased/`, so two branches each
6
+ * adding a change do not meet over the same line of the same file. The file
7
+ * name is yours; something short and about the change reads best in a diff.
8
+ *
9
+ * ```ts
10
+ * import { change } from '@absolutejs/changelog';
11
+ *
12
+ * export default change({
13
+ * kind: 'breaking',
14
+ * summary: 'serve() takes an options object',
15
+ * symbols: ['serve'],
16
+ * migration: {
17
+ * instruction: 'Pass { port } instead of a bare port.',
18
+ * manual: true
19
+ * }
20
+ * });
21
+ * ```
22
+ */
23
+ export declare const change: <const T extends Change>(entry: T) => T;
24
+ /** A migration nobody should automate, said as such rather than left off. */
25
+ export declare const manual: (instruction: string) => Migration;
26
+ /**
27
+ * Record an export moving between entry points.
28
+ *
29
+ * The second most common, and the one people most often discover by way of a
30
+ * failed build with no explanation in it.
31
+ */
32
+ export declare const moved: (input: {
33
+ symbol: string;
34
+ from: string;
35
+ to: string;
36
+ detail?: string;
37
+ reference?: string;
38
+ }) => {
39
+ readonly summary: `\`${string}\` moved to \`${string}\``;
40
+ readonly symbols: readonly [string];
41
+ readonly reference?: string | undefined;
42
+ readonly kind: "breaking";
43
+ readonly migration: {
44
+ readonly instruction: `Import \`${string}\` from \`${string}\`.`;
45
+ readonly moved: {
46
+ readonly from: string;
47
+ readonly symbol: string;
48
+ readonly to: string;
49
+ };
50
+ };
51
+ readonly detail?: string | undefined;
52
+ };
53
+ /**
54
+ * Record a rename.
55
+ *
56
+ * The most common breaking change there is, and the one worth having a
57
+ * shorthand for -- both because it is written often and because writing it
58
+ * this way is what makes it applicable rather than readable.
59
+ */
60
+ export declare const renamed: (input: {
61
+ from: string;
62
+ to: string;
63
+ /** Anything a reader needs beyond "it is called this now". */
64
+ detail?: string;
65
+ reference?: string;
66
+ }) => {
67
+ readonly summary: `\`${string}\` is now \`${string}\``;
68
+ readonly symbols: readonly [string];
69
+ readonly reference?: string | undefined;
70
+ readonly kind: "breaking";
71
+ readonly migration: {
72
+ readonly instruction: `Import \`${string}\` instead of \`${string}\`.`;
73
+ readonly rename: {
74
+ readonly from: string;
75
+ readonly to: string;
76
+ };
77
+ };
78
+ readonly detail?: string | undefined;
79
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};