@aklinker1/zero-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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/changelog-section.d.mts +7 -0
- package/dist/changelog-section.mjs +1 -0
- package/dist/conventional-commit.d.mts +28 -0
- package/dist/conventional-commit.mjs +1 -0
- package/dist/create-github-release.d.mts +15 -0
- package/dist/create-github-release.mjs +61 -0
- package/dist/detect-version-bump.d.mts +13 -0
- package/dist/detect-version-bump.mjs +43 -0
- package/dist/find-previous-tag-Cj2oZAty.mjs +27 -0
- package/dist/find-previous-tag.d.mts +10 -0
- package/dist/find-previous-tag.mjs +2 -0
- package/dist/get-current-version.d.mts +4 -0
- package/dist/get-current-version.mjs +24 -0
- package/dist/get-github-release.d.mts +15 -0
- package/dist/get-github-release.mjs +11 -0
- package/dist/get-github-repo.d.mts +4 -0
- package/dist/get-github-repo.mjs +20 -0
- package/dist/get-release-notes.d.mts +6 -0
- package/dist/get-release-notes.mjs +37 -0
- package/dist/git-commit.d.mts +13 -0
- package/dist/git-commit.mjs +1 -0
- package/dist/list-commits-since-DycWgHTi.mjs +44 -0
- package/dist/list-commits-since.d.mts +11 -0
- package/dist/list-commits-since.mjs +2 -0
- package/dist/parse-changelog.d.mts +6 -0
- package/dist/parse-changelog.mjs +23 -0
- package/dist/parse-commit.d.mts +8 -0
- package/dist/parse-commit.mjs +34 -0
- package/dist/parse-commits.d.mts +8 -0
- package/dist/parse-commits.mjs +8 -0
- package/dist/release.d.mts +384 -0
- package/dist/release.mjs +125 -0
- package/dist/semver-type--Q1lYCiZ.d.mts +11 -0
- package/dist/semver-type-map.d.mts +8 -0
- package/dist/semver-type-map.mjs +1 -0
- package/dist/semver-type.d.mts +2 -0
- package/dist/semver-type.mjs +1 -0
- package/dist/semver-types/aklinker1.d.mts +6 -0
- package/dist/semver-types/aklinker1.mjs +38 -0
- package/dist/semver.d.mts +37 -0
- package/dist/semver.mjs +142 -0
- package/dist/serialize-changelog.d.mts +6 -0
- package/dist/serialize-changelog.mjs +28 -0
- package/dist/summarize-unreleased-commits.d.mts +50 -0
- package/dist/summarize-unreleased-commits.mjs +30 -0
- package/dist/sync-release.d.mts +4 -0
- package/dist/sync-release.mjs +6 -0
- package/dist/sync-releases.d.mts +4 -0
- package/dist/sync-releases.mjs +6 -0
- package/dist/update-version-files.d.mts +4 -0
- package/dist/update-version-files.mjs +26 -0
- package/dist/utils-BO6byvK5.mjs +22 -0
- package/dist/version-regex-C82OGsTC.mjs +23 -0
- package/dist/wait-for-child-process-lyAoE4WE.mjs +25 -0
- package/package.json +134 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron
|
|
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
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# `@aklinker1/zero-changelog`
|
|
4
|
+
|
|
5
|
+
[](https://jsr.io/@aklinker1/zero-changelog)
|
|
6
|
+
[](https://www.npmjs.com/package/@aklinker1/zero-changelog)
|
|
7
|
+
[](https://jsr.io/@aklinker1/zero-changelog/doc)
|
|
8
|
+
[](https://pkg-size.dev/@aklinker1%2Fzero-changelog)
|
|
9
|
+
|
|
10
|
+
Zero-dependency, conventional commit release and changelog generator with monorepo support
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
bun add @aklinker1/zero-changelog
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- 📦 Zero dependencies, [e18e](https://e18e.dev) first
|
|
21
|
+
- 🏗️ Monorepo & sub-directory support
|
|
22
|
+
- 🛠️ Github Action, JS API, CLI
|
|
23
|
+
- 🚀 Runs custom publish scripts
|
|
24
|
+
- 📊 Summarize unreleased changes
|
|
25
|
+
|
|
26
|
+
## Options
|
|
27
|
+
|
|
28
|
+
Refer to the API reference:
|
|
29
|
+
|
|
30
|
+
- [Release](https://jsr.io/@aklinker1/zero-ioc/doc/release/~/ReleaseOptions)
|
|
31
|
+
- [Sync Releases](https://jsr.io/@aklinker1/zero-ioc/doc/sync-releases/~/SyncReleasesOptions)
|
|
32
|
+
- [Summarize Unreleased Commits](https://jsr.io/@aklinker1/zero-ioc/doc/summarize-unreleased-commits/~/SummarizeUnreleasedCommitsOptions)
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### GitHub Action
|
|
37
|
+
|
|
38
|
+
```yml
|
|
39
|
+
- uses: aklinker1/zero-changelog/actions/release@1.0.0
|
|
40
|
+
with:
|
|
41
|
+
# options...
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### JS API
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { release } from "@aklinker1/zero-changelog/release";
|
|
48
|
+
|
|
49
|
+
await release({
|
|
50
|
+
// options...
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### CLI
|
|
55
|
+
|
|
56
|
+
> TODO: CLI not implemented yet.
|
|
57
|
+
|
|
58
|
+
Run any command with `--help` to see available options.
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
bun zero-changelog --help
|
|
62
|
+
bun zero-changelog release --help
|
|
63
|
+
bun zero-changelog sync-releases --help
|
|
64
|
+
bun zero-changelog summarize-unreleased-changes --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Semver
|
|
68
|
+
|
|
69
|
+
`@aklinker1/zero-changelog` does not implement the entire Semver spec. It only supports the
|
|
70
|
+
following types of versions:
|
|
71
|
+
|
|
72
|
+
- ✅ **Unstable**: `0.[0-9]+.[0-9]+`
|
|
73
|
+
- Examples: `0.0.0`, `0.12.1`, `0.1.9`, `0.20.17`
|
|
74
|
+
|
|
75
|
+
- ✅ **Prerelease**: `[1-9][0-9]*.0.0-\w+.[0-9]+`
|
|
76
|
+
- Examples: `1.0.0-alpha.1`, `2.0.0-beta.3`, `3.0.0-rc.10`
|
|
77
|
+
|
|
78
|
+
- ✅ **Stable**: `[1-9][0-9]*.[0-9]+.[0-9]+`
|
|
79
|
+
- Examples: `1.1.0`, `2.0.1`, `3.0.0`
|
|
80
|
+
|
|
81
|
+
It does NOT support:
|
|
82
|
+
|
|
83
|
+
- ❌ **Truncated versions** with one or two digits:
|
|
84
|
+
- Examples: `1.0`, `2`
|
|
85
|
+
- ❌ **Unstable prereleases**:
|
|
86
|
+
- Examples: `0.2.0-alpha.1`
|
|
87
|
+
- ❌ **Non X.0.0 prereleases**:
|
|
88
|
+
- Examples: `1.1.0-rc.1`, `1.0.5-alpha.1`
|
|
89
|
+
- ❌ **Other prerelease formats**:
|
|
90
|
+
- Examples: `0.2.0-0.3.2`, `0.2.0-1`
|
|
91
|
+
|
|
92
|
+
This allows the package to have 0 dependencies.
|
|
93
|
+
|
|
94
|
+
### Relative Version Bumping
|
|
95
|
+
|
|
96
|
+
When bumping the version by `major`, `minor`, or `patch`, either by setting the `bump` option or
|
|
97
|
+
auto-detecting the type based on commits, there are some special cases where the version may not be
|
|
98
|
+
bumped as expected:
|
|
99
|
+
|
|
100
|
+
1. **Unstable**: The type of semver change will be lowered by 1, so `major` → `minor`, `minor`
|
|
101
|
+
→ `patch`, `patch` → `patch`.
|
|
102
|
+
- Examples:
|
|
103
|
+
- `0.1.0` → `major` → `0.2.0`
|
|
104
|
+
- `0.1.0` → `minor` → `0.1.1`
|
|
105
|
+
- `0.1.0` → `patch` → `0.1.1`
|
|
106
|
+
|
|
107
|
+
2. **Prereleases**: The final integer will always be updated.
|
|
108
|
+
- Examples:
|
|
109
|
+
- `1.0.0-alpha.1` → `major` → `1.0.0-alpha.2`
|
|
110
|
+
- `1.0.0-alpha.1` → `minor` → `1.0.0-alpha.2`
|
|
111
|
+
- `1.0.0-alpha.1` → `patch` → `1.0.0-alpha.2`
|
|
112
|
+
|
|
113
|
+
So if you want to switch between versions types (stable ↔ unstable ↔ pre-release), like `0.7.5`
|
|
114
|
+
→ `1.0.0` or `2.0.0-rc.4` → `2.0.0`, you need to provide the next version number for the
|
|
115
|
+
`bump` option instead of using `major`/`minor`/`patch`:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
await release({
|
|
119
|
+
bump: "2.0.0",
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Otherwise stable versions will remain stable, unstable will remain unstable, and prereleases will
|
|
124
|
+
remain prereleases.
|
|
125
|
+
|
|
126
|
+
## Plan
|
|
127
|
+
|
|
128
|
+
Release:
|
|
129
|
+
|
|
130
|
+
1. List git commits
|
|
131
|
+
2. Detect type of version bump
|
|
132
|
+
3. Generate changelog
|
|
133
|
+
4. Bump version in version files
|
|
134
|
+
5. Update changelog
|
|
135
|
+
6. Git commit & tag
|
|
136
|
+
7. Git push
|
|
137
|
+
8. Create release
|
|
138
|
+
|
|
139
|
+
Sync Releases:
|
|
140
|
+
|
|
141
|
+
1. Parse changelog
|
|
142
|
+
2. Update each release to match changelog
|
|
143
|
+
|
|
144
|
+
Summarize Unreleased Changes:
|
|
145
|
+
|
|
146
|
+
1. List commits in each directory since the last commit
|
|
147
|
+
2. Print and return the summary
|
|
148
|
+
|
|
149
|
+
## TODO
|
|
150
|
+
|
|
151
|
+
- [x] Structure for package + action
|
|
152
|
+
- [x] Implement tests
|
|
153
|
+
- [x] Implement git logic
|
|
154
|
+
- [ ] Implement sync releases
|
|
155
|
+
- [x] Glob patterns for github action
|
|
156
|
+
- [x] Implement unreleased commit summary
|
|
157
|
+
- [ ] CLI?
|
|
158
|
+
- [x] Implement alpha/beta version bumping
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/conventional-commit.d.ts
|
|
2
|
+
type ConventionalCommit = {
|
|
3
|
+
type: string;
|
|
4
|
+
scope?: string;
|
|
5
|
+
description: string;
|
|
6
|
+
body?: string;
|
|
7
|
+
isBreaking: boolean;
|
|
8
|
+
footers: Array<{
|
|
9
|
+
/**
|
|
10
|
+
* Lowercase footer key.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* "Co-authored-by: Aaron <aaron@example.com>"; // key: "co-authored-by"
|
|
14
|
+
*/
|
|
15
|
+
key: string;
|
|
16
|
+
/**
|
|
17
|
+
* @example
|
|
18
|
+
* "Co-authored-by: Aaron <aaron@example.com>"; // value: "Aaron <aaron@example.com>"
|
|
19
|
+
*/
|
|
20
|
+
value: string;
|
|
21
|
+
}>;
|
|
22
|
+
authors: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
email: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ConventionalCommit };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/create-github-release.d.ts
|
|
2
|
+
type CreateGithubReleaseOptions = {
|
|
3
|
+
repo: `${string}/${string}`;
|
|
4
|
+
token: string;
|
|
5
|
+
dryRun: boolean;
|
|
6
|
+
tag: string;
|
|
7
|
+
name: string;
|
|
8
|
+
body: string;
|
|
9
|
+
artifacts?: string[];
|
|
10
|
+
latest: boolean;
|
|
11
|
+
prerelease: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare function createGithubRelease(options: CreateGithubReleaseOptions): Promise<void>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createGithubRelease };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { styleText } from "node:util";
|
|
3
|
+
//#region src/create-github-release.ts
|
|
4
|
+
async function createGithubRelease(options) {
|
|
5
|
+
console.log("Creating GitHub release...");
|
|
6
|
+
if (options.dryRun) console.log(" -> Skipping, dry run");
|
|
7
|
+
else throw Error("TODO");
|
|
8
|
+
for (const artifact of options.artifacts ?? []) {
|
|
9
|
+
console.log(`Uploading ${styleText("cyan", artifact)}...`);
|
|
10
|
+
if (options.dryRun) console.log(" -> Skipping, dry run");
|
|
11
|
+
else {
|
|
12
|
+
const artifactStreams = options.artifacts?.length ? await getArtifactStreams(options.artifacts) : void 0;
|
|
13
|
+
await createRelease(options);
|
|
14
|
+
if (artifactStreams) await uploadArtifacts(options, artifactStreams);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function getArtifactStreams(artifacts) {
|
|
19
|
+
return Promise.all(artifacts.map((artifact) => createReadStream(artifact)));
|
|
20
|
+
}
|
|
21
|
+
async function createRelease(options) {
|
|
22
|
+
const res = await fetch(`https://api.github.com/repos/${options.repo}/releases`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: {
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
Authorization: `Bearer ${options.token}`
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
tag_name: options.tag,
|
|
30
|
+
name: options.name,
|
|
31
|
+
body: options.body,
|
|
32
|
+
make_latest: options.latest,
|
|
33
|
+
prerelease: options.prerelease
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
if (res.ok) return;
|
|
37
|
+
console.log("Response status: " + res.status);
|
|
38
|
+
console.log("Response body: " + await res.text());
|
|
39
|
+
throw Error(`Failed to create GitHub release`);
|
|
40
|
+
}
|
|
41
|
+
async function uploadArtifacts(options, artifactStreams) {
|
|
42
|
+
for (const [i, stream] of artifactStreams.entries()) {
|
|
43
|
+
const artifact = options.artifacts[i];
|
|
44
|
+
console.log(" -> Uploading ", artifact);
|
|
45
|
+
const res = await fetch(`https://uploads.github.com/repos/${options.repo}/releases/tags/${options.tag}/assets`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/octet-stream",
|
|
49
|
+
Authorization: `Bearer ${options.token}`,
|
|
50
|
+
"Content-Disposition": `attachment; filename="${artifact}"`
|
|
51
|
+
},
|
|
52
|
+
body: stream
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
console.error(`Failed to upload`, artifact, res);
|
|
56
|
+
console.log(await res.text());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createGithubRelease };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ConventionalCommit } from "./conventional-commit.mjs";
|
|
2
|
+
import { RelativeBump } from "./semver.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/detect-version-bump.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Given the current version and a list of commits, detect the type of relative version bump to use.
|
|
7
|
+
*
|
|
8
|
+
* @param currentVersion Used to determine the if prerelease logic should be applied.
|
|
9
|
+
* @param conventionalCommits List of commits to generate the version bump from.
|
|
10
|
+
*/
|
|
11
|
+
declare function detectVersionBump(conventionalCommits: ConventionalCommit[], throwOnNoChanges: boolean): RelativeBump;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { detectVersionBump };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import types from "./semver-types/aklinker1.mjs";
|
|
2
|
+
//#region src/detect-version-bump.ts
|
|
3
|
+
const NONE = 0;
|
|
4
|
+
const PATCH = 1;
|
|
5
|
+
const MINOR = 2;
|
|
6
|
+
const PRIORITY_MAP = {
|
|
7
|
+
none: NONE,
|
|
8
|
+
patch: PATCH,
|
|
9
|
+
minor: MINOR
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Given the current version and a list of commits, detect the type of relative version bump to use.
|
|
13
|
+
*
|
|
14
|
+
* @param currentVersion Used to determine the if prerelease logic should be applied.
|
|
15
|
+
* @param conventionalCommits List of commits to generate the version bump from.
|
|
16
|
+
*/
|
|
17
|
+
function detectVersionBump(conventionalCommits, throwOnNoChanges) {
|
|
18
|
+
console.log("Detecting version bump based on changes...");
|
|
19
|
+
let priority = NONE;
|
|
20
|
+
for (const commit of conventionalCommits) {
|
|
21
|
+
if (commit.isBreaking) {
|
|
22
|
+
console.log(" -> major");
|
|
23
|
+
return "major";
|
|
24
|
+
}
|
|
25
|
+
const bumpBy = types[commit.type]?.bump ?? "none";
|
|
26
|
+
priority = Math.max(priority, PRIORITY_MAP[bumpBy]);
|
|
27
|
+
}
|
|
28
|
+
switch (priority) {
|
|
29
|
+
case MINOR:
|
|
30
|
+
console.log(" -> minor");
|
|
31
|
+
return "minor";
|
|
32
|
+
case PATCH:
|
|
33
|
+
console.log(" -> patch");
|
|
34
|
+
return "patch";
|
|
35
|
+
case NONE:
|
|
36
|
+
if (throwOnNoChanges) throw Error("No semver changes detected");
|
|
37
|
+
console.log(" -> patch");
|
|
38
|
+
return "patch";
|
|
39
|
+
default: throw Error("Unknown semver bump value: " + priority);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { detectVersionBump };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { t as waitForChildProcess } from "./wait-for-child-process-lyAoE4WE.mjs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
//#region src/internal/run-git-tag.ts
|
|
4
|
+
async function runGitTag(prefix, cwd = process.cwd()) {
|
|
5
|
+
const { stdout } = await waitForChildProcess(spawn("git", [
|
|
6
|
+
"--no-pager",
|
|
7
|
+
"tag",
|
|
8
|
+
"-l",
|
|
9
|
+
`${prefix}*`,
|
|
10
|
+
"--sort=-version:refname"
|
|
11
|
+
], { cwd }));
|
|
12
|
+
return stdout;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/find-previous-tag.ts
|
|
16
|
+
/**
|
|
17
|
+
* Finds the previous tag that starts with a prefix. If there are no previous tags starting with the
|
|
18
|
+
* prefix, this returns `undefined`.
|
|
19
|
+
*
|
|
20
|
+
* @param tagPrefix The prefix to look for.
|
|
21
|
+
*/
|
|
22
|
+
async function findPreviousTag(tagPrefix) {
|
|
23
|
+
console.log(`Finding previous tag matching: "${tagPrefix}*"`);
|
|
24
|
+
return (await runGitTag(tagPrefix)).trim().split("\n")[0] || void 0;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { findPreviousTag as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/find-previous-tag.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Finds the previous tag that starts with a prefix. If there are no previous tags starting with the
|
|
4
|
+
* prefix, this returns `undefined`.
|
|
5
|
+
*
|
|
6
|
+
* @param tagPrefix The prefix to look for.
|
|
7
|
+
*/
|
|
8
|
+
declare function findPreviousTag(tagPrefix: string): Promise<string | undefined>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { findPreviousTag };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { t as getVersionRegexFor } from "./version-regex-C82OGsTC.mjs";
|
|
2
|
+
import { styleText } from "node:util";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
5
|
+
//#region src/get-current-version.ts
|
|
6
|
+
async function getCurrentVersion(path, versionFiles) {
|
|
7
|
+
console.log("Getting current version...");
|
|
8
|
+
for (const versionFile of versionFiles) try {
|
|
9
|
+
const file = join(path, versionFile);
|
|
10
|
+
const text = await readFile(file, "utf8");
|
|
11
|
+
const regex = getVersionRegexFor(file);
|
|
12
|
+
const version = text.match(regex)?.groups?.version;
|
|
13
|
+
if (version) {
|
|
14
|
+
console.log(`Found current version (${version})) in ${styleText("cyan", relative(process.cwd(), file))}`);
|
|
15
|
+
return version;
|
|
16
|
+
}
|
|
17
|
+
console.log(` -> Not found in ${styleText("cyan", relative(process.cwd(), file))}`);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err.code !== "ENOENT") throw err;
|
|
20
|
+
}
|
|
21
|
+
throw Error("Version not found");
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { getCurrentVersion };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/get-github-release.d.ts
|
|
2
|
+
type GithubRelease = {
|
|
3
|
+
tag: string;
|
|
4
|
+
title: string;
|
|
5
|
+
body: string;
|
|
6
|
+
draft: boolean;
|
|
7
|
+
prerelease: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare function getGithubRelease(options: {
|
|
10
|
+
repo: string;
|
|
11
|
+
tag: string;
|
|
12
|
+
token: string;
|
|
13
|
+
}): Promise<GithubRelease>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { GithubRelease, getGithubRelease };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/get-github-release.ts
|
|
2
|
+
async function getGithubRelease(options) {
|
|
3
|
+
const res = await fetch(`https://api.github.com/repos/${options.repo}/releases/tags/${options.tag}`, { headers: { Authorization: `Bearer ${options.token}` } });
|
|
4
|
+
if (res.ok) return await res.json();
|
|
5
|
+
console.log("Response status:", res.status);
|
|
6
|
+
console.log("Response body:");
|
|
7
|
+
console.log(await res.text());
|
|
8
|
+
throw Error("Failed to fetch github release");
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getGithubRelease };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { t as waitForChildProcess } from "./wait-for-child-process-lyAoE4WE.mjs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
//#region src/get-github-repo.ts
|
|
4
|
+
const REMOTE_REGEX = [/^git@github\.com:(?<owner>\S+)\/(?<repo>\S+)\.git$/, /^https:\/\/github\.com\/(?<owner>\S+)\/(?<repo>\S+)\.git$/];
|
|
5
|
+
async function getGithubRepo() {
|
|
6
|
+
console.log("Getting current github repo...");
|
|
7
|
+
const { stdout } = await waitForChildProcess(spawn("git", [
|
|
8
|
+
"config",
|
|
9
|
+
"--get",
|
|
10
|
+
"remote.origin.url"
|
|
11
|
+
]));
|
|
12
|
+
const url = stdout.trim();
|
|
13
|
+
for (const regex of REMOTE_REGEX) {
|
|
14
|
+
const groups = url.match(regex)?.groups;
|
|
15
|
+
if (groups) return `${groups.owner}/${groups.repo}`;
|
|
16
|
+
}
|
|
17
|
+
throw Error(`Could not find github repo from the origin remote's URL: ${url}`);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { getGithubRepo };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConventionalCommit } from "./conventional-commit.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/get-release-notes.d.ts
|
|
4
|
+
declare function getReleaseNotes(conventionalCommits: ConventionalCommit[], since: string | undefined, tag: string, repo: string): string;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { getReleaseNotes };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import types from "./semver-types/aklinker1.mjs";
|
|
2
|
+
//#region src/get-release-notes.ts
|
|
3
|
+
function getReleaseNotes(conventionalCommits, since, tag, repo) {
|
|
4
|
+
const commitsByType = conventionalCommits.reduce((acc, commit) => {
|
|
5
|
+
acc[commit.type] ??= [];
|
|
6
|
+
acc[commit.type].push(commit);
|
|
7
|
+
return acc;
|
|
8
|
+
}, {});
|
|
9
|
+
const lines = [];
|
|
10
|
+
if (since) lines.push(`[compare changes](https://github.com/${repo}/compare/${since}...${tag})`, "");
|
|
11
|
+
for (const [type, details] of Object.entries(types)) {
|
|
12
|
+
const commits = commitsByType[type];
|
|
13
|
+
if (!commits?.length) continue;
|
|
14
|
+
lines.push(`### ${details.title}`, "");
|
|
15
|
+
for (const commit of commits) {
|
|
16
|
+
const scope = commit.scope ? `**${commit.scope}**: ` : "";
|
|
17
|
+
const breaking = commit.isBreaking ? "⚠️ " : "";
|
|
18
|
+
lines.push(`- ${breaking}${scope}${commit.description}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push("");
|
|
21
|
+
}
|
|
22
|
+
const authors = conventionalCommits.flatMap((commit) => commit.authors);
|
|
23
|
+
if (authors.length) {
|
|
24
|
+
const emailNameMap = authors.reduce((acc, author) => {
|
|
25
|
+
acc[author.name] ??= author.email;
|
|
26
|
+
return acc;
|
|
27
|
+
}, {});
|
|
28
|
+
const emails = new Set(conventionalCommits.flatMap((commit) => commit.authors).map((author) => author.email));
|
|
29
|
+
const sortedEmails = Array.from(emails).toSorted((a, b) => b.localeCompare(a));
|
|
30
|
+
lines.push("### ❤️ Contributors", "");
|
|
31
|
+
for (const email of sortedEmails) lines.push(`- ${emailNameMap[email]} <${email}>`);
|
|
32
|
+
lines.push("");
|
|
33
|
+
}
|
|
34
|
+
return lines.join("\n");
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { getReleaseNotes };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { t as waitForChildProcess } from "./wait-for-child-process-lyAoE4WE.mjs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
//#region src/internal/run-git-log.ts
|
|
4
|
+
async function runGitLog(paths, fromRef, toRef = "HEAD", cwd = process.cwd()) {
|
|
5
|
+
const args = [
|
|
6
|
+
"--no-pager",
|
|
7
|
+
"log",
|
|
8
|
+
...fromRef ? [`${fromRef}..${toRef}`] : [],
|
|
9
|
+
"--date=iso-strict",
|
|
10
|
+
"--no-color",
|
|
11
|
+
"--no-decorate",
|
|
12
|
+
"--no-merges",
|
|
13
|
+
`--format=%H%x1f%an%x1f%ae%x1f%ad%x1f%s%x1f%b%x1e`,
|
|
14
|
+
"--",
|
|
15
|
+
...paths
|
|
16
|
+
];
|
|
17
|
+
console.log(args);
|
|
18
|
+
const { stdout } = await waitForChildProcess(spawn("git", args, { cwd }));
|
|
19
|
+
return stdout;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/list-commits-since.ts
|
|
23
|
+
/** List the commits since a ref in specific dirs. */
|
|
24
|
+
async function listCommitsSince(options) {
|
|
25
|
+
console.log(`Listing commits ${options.since ? "since " + options.since : "for all time"} in:`, options.dirs);
|
|
26
|
+
return parseGitLog(await runGitLog(options.dirs, options.since));
|
|
27
|
+
}
|
|
28
|
+
function parseGitLog(log) {
|
|
29
|
+
return log.split("").map((r) => r.trim()).filter(Boolean).map((record) => {
|
|
30
|
+
const [hash, authorName, authorEmail, date, subject, body] = record.split("");
|
|
31
|
+
return {
|
|
32
|
+
hash,
|
|
33
|
+
author: {
|
|
34
|
+
name: authorName,
|
|
35
|
+
email: authorEmail
|
|
36
|
+
},
|
|
37
|
+
date: new Date(date),
|
|
38
|
+
subject,
|
|
39
|
+
body: body?.trim() || void 0
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { parseGitLog as n, listCommitsSince as t };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GitCommit } from "./git-commit.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/list-commits-since.d.ts
|
|
4
|
+
/** List the commits since a ref in specific dirs. */
|
|
5
|
+
declare function listCommitsSince(options: {
|
|
6
|
+
dirs: string[];
|
|
7
|
+
since: string | undefined;
|
|
8
|
+
}): Promise<GitCommit[]>;
|
|
9
|
+
declare function parseGitLog(log: string): GitCommit[];
|
|
10
|
+
//#endregion
|
|
11
|
+
export { listCommitsSince, parseGitLog };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/parse-changelog.ts
|
|
2
|
+
function parseChangelog(changelog) {
|
|
3
|
+
const lines = changelog.split(/\n\r?/);
|
|
4
|
+
let results = [];
|
|
5
|
+
let current;
|
|
6
|
+
for (const line of lines) if (line.startsWith("## ")) {
|
|
7
|
+
if (current) results.push({
|
|
8
|
+
header: current.header,
|
|
9
|
+
body: current.lines.join("\n").trim()
|
|
10
|
+
});
|
|
11
|
+
current = {
|
|
12
|
+
header: line.slice(3),
|
|
13
|
+
lines: []
|
|
14
|
+
};
|
|
15
|
+
} else if (current) current.lines.push(line);
|
|
16
|
+
if (current) results.push({
|
|
17
|
+
header: current.header,
|
|
18
|
+
body: current.lines.join("\n").trim()
|
|
19
|
+
});
|
|
20
|
+
return results;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { parseChangelog };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ConventionalCommit } from "./conventional-commit.mjs";
|
|
2
|
+
import { GitCommit } from "./git-commit.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/parse-commit.d.ts
|
|
5
|
+
/** Convert a commit to a {@link ConventionalCommit}, returning `undefined` for unknown formats */
|
|
6
|
+
declare function parseCommit(commit: GitCommit): ConventionalCommit | undefined;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { parseCommit };
|