release-suite 0.1.0 โ 1.0.1
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/.github/workflows/create-release-pr.yml +33 -17
- package/.github/workflows/publish-on-merge.yml +23 -13
- package/CHANGELOG.md +52 -5
- package/README.md +195 -183
- package/bin/compute-version.js +173 -132
- package/bin/create-tag.js +26 -14
- package/bin/generate-changelog.js +229 -192
- package/bin/generate-release-notes.js +135 -123
- package/bin/preview.js +47 -47
- package/docs/api.md +28 -0
- package/docs/ci.md +206 -0
- package/docs/compute-version.md +204 -0
- package/eslint.config.js +89 -47
- package/lib/constants.js +28 -0
- package/lib/git.js +129 -0
- package/lib/utils.js +45 -0
- package/lib/versioning.js +109 -0
- package/package.json +62 -62
package/bin/compute-version.js
CHANGED
|
@@ -1,132 +1,173 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { getLastTag, getCommits, parseCommit } from "../lib/git.js";
|
|
4
|
+
import { readPackageVersion } from "../lib/utils.js";
|
|
5
|
+
import { bumpVersion, detectBumpType } from "../lib/versioning.js";
|
|
6
|
+
|
|
7
|
+
/* ===========================
|
|
8
|
+
* Core API (Programmatic)
|
|
9
|
+
* =========================== */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Analyze commits since the last tag (or since package.json version) and compute whether a release
|
|
13
|
+
* is needed and what the next semantic version should be.
|
|
14
|
+
*
|
|
15
|
+
* The function:
|
|
16
|
+
* - Determines a base version from the latest git tag (if any) or the package.json version.
|
|
17
|
+
* - Collects commits in the range (lastTag..HEAD or HEAD) and parses them.
|
|
18
|
+
* - Detects a bump type ('major' | 'minor' | 'patch') from the commits using conventional-commit style rules.
|
|
19
|
+
* - Returns either a release plan (nextVersion + bump) or a reason why no release is required.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} [options] - Options object.
|
|
22
|
+
* @param {string} [options.cwd=process.cwd()] - Working directory to run git/package lookups in.
|
|
23
|
+
*
|
|
24
|
+
* @returns {{
|
|
25
|
+
* hasRelease: boolean,
|
|
26
|
+
* // Present when hasRelease === false:
|
|
27
|
+
* reason?: 'no-commits' | 'no-bump-detected',
|
|
28
|
+
* // Always present:
|
|
29
|
+
* baseVersion: string,
|
|
30
|
+
* commitsAnalyzed: number,
|
|
31
|
+
* // Present when hasRelease === true:
|
|
32
|
+
* nextVersion?: string,
|
|
33
|
+
* bump?: 'major' | 'minor' | 'patch'
|
|
34
|
+
* }}
|
|
35
|
+
*
|
|
36
|
+
* Examples:
|
|
37
|
+
* // No commits since last tag
|
|
38
|
+
* // { hasRelease: false, reason: 'no-commits', baseVersion: '1.2.3', commitsAnalyzed: 0 }
|
|
39
|
+
*
|
|
40
|
+
* // Commits analyzed but none imply a version bump
|
|
41
|
+
* // { hasRelease: false, reason: 'no-bump-detected', baseVersion: '1.2.3', commitsAnalyzed: 5 }
|
|
42
|
+
*
|
|
43
|
+
* // Bump detected (e.g. minor)
|
|
44
|
+
* // { hasRelease: true, baseVersion: '1.2.3', nextVersion: '1.3.0', bump: 'minor', commitsAnalyzed: 4 }
|
|
45
|
+
*
|
|
46
|
+
* @throws {Error} If reading package version or git data fails (propagates errors from helper utilities).
|
|
47
|
+
*/
|
|
48
|
+
export function computeVersion({ cwd = process.cwd() } = {}) {
|
|
49
|
+
const pkgVersion = readPackageVersion(cwd);
|
|
50
|
+
const lastTag = getLastTag(cwd);
|
|
51
|
+
const baseVersion = lastTag ?? pkgVersion;
|
|
52
|
+
|
|
53
|
+
const range = lastTag ? `${lastTag}..HEAD` : "HEAD";
|
|
54
|
+
const commits = getCommits(range, cwd).map(parseCommit);
|
|
55
|
+
|
|
56
|
+
if (commits.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
hasRelease: false,
|
|
59
|
+
reason: "no-commits",
|
|
60
|
+
baseVersion,
|
|
61
|
+
commitsAnalyzed: 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let bump = null;
|
|
66
|
+
|
|
67
|
+
for (const commit of commits) {
|
|
68
|
+
const type = detectBumpType(commit);
|
|
69
|
+
|
|
70
|
+
if (type === "major") {
|
|
71
|
+
bump = "major";
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
if (type === "minor" && bump !== "major") bump = "minor";
|
|
75
|
+
if (type === "patch" && !bump) bump = "patch";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!bump) {
|
|
79
|
+
return {
|
|
80
|
+
hasRelease: false,
|
|
81
|
+
reason: "no-bump-detected",
|
|
82
|
+
baseVersion,
|
|
83
|
+
commitsAnalyzed: commits.length,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
hasRelease: true,
|
|
89
|
+
baseVersion,
|
|
90
|
+
nextVersion: bumpVersion(baseVersion, bump),
|
|
91
|
+
bump,
|
|
92
|
+
commitsAnalyzed: commits.length,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ===========================
|
|
97
|
+
* CLI
|
|
98
|
+
* =========================== */
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parses an array of command-line arguments and returns which known flags are present.
|
|
102
|
+
*
|
|
103
|
+
* Recognized flags:
|
|
104
|
+
* - "--ci"
|
|
105
|
+
* - "--json"
|
|
106
|
+
* - "--preview"
|
|
107
|
+
*
|
|
108
|
+
* @param {string[]} argv - Array of command-line arguments (e.g. process.argv.slice(2)).
|
|
109
|
+
* @returns {{ci: boolean, json: boolean, preview: boolean}} An object with boolean properties indicating presence of each flag.
|
|
110
|
+
*/
|
|
111
|
+
function parseFlags(argv) {
|
|
112
|
+
return {
|
|
113
|
+
ci: argv.includes("--ci"),
|
|
114
|
+
json: argv.includes("--json"),
|
|
115
|
+
preview: argv.includes("--preview"),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Main CLI entrypoint that computes the next release version, prints the result,
|
|
121
|
+
* and exits the process according to a predetermined contract.
|
|
122
|
+
*
|
|
123
|
+
* Behavior:
|
|
124
|
+
* - Reads CLI flags via parseFlags(process.argv.slice(2)).
|
|
125
|
+
* - Calls computeVersion() to obtain an object describing the base version,
|
|
126
|
+
* whether a release should be generated, the next version, and a reason code.
|
|
127
|
+
* - If the parsed flags include `json`, writes the full computeVersion result as
|
|
128
|
+
* pretty-printed JSON to stdout.
|
|
129
|
+
* - Otherwise, if a release was generated (`result.hasRelease`), writes
|
|
130
|
+
* `result.nextVersion` to stdout.
|
|
131
|
+
* - If no release was generated, writes an explanatory error to stderr that
|
|
132
|
+
* includes `result.reason` and `result.baseVersion`.
|
|
133
|
+
*
|
|
134
|
+
* Exit codes (contract):
|
|
135
|
+
* 0 -> release generated
|
|
136
|
+
* 10 -> no bump detected
|
|
137
|
+
* 2 -> no commits
|
|
138
|
+
* 1 -> unexpected error
|
|
139
|
+
*
|
|
140
|
+
* Side effects:
|
|
141
|
+
* - Prints to stdout/stderr.
|
|
142
|
+
* - Terminates the Node.js process via process.exit(...) using the codes above.
|
|
143
|
+
*
|
|
144
|
+
* @function main
|
|
145
|
+
* @returns {void} This function does not return; it exits the process.
|
|
146
|
+
* @see parseFlags
|
|
147
|
+
* @see computeVersion
|
|
148
|
+
*/
|
|
149
|
+
function main() {
|
|
150
|
+
const flags = parseFlags(process.argv.slice(2));
|
|
151
|
+
const result = computeVersion();
|
|
152
|
+
|
|
153
|
+
if (flags.json) {
|
|
154
|
+
console.log(JSON.stringify(result, null, 2));
|
|
155
|
+
} else if (result.hasRelease) {
|
|
156
|
+
console.log(result.nextVersion);
|
|
157
|
+
} else {
|
|
158
|
+
console.error(
|
|
159
|
+
`No release generated (${result.reason}). Base version: ${result.baseVersion}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (result.hasRelease) process.exit(0);
|
|
164
|
+
if (result.reason === "no-bump-detected") process.exit(10);
|
|
165
|
+
if (result.reason === "no-commits") process.exit(2);
|
|
166
|
+
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
171
|
+
if (process.argv[1] === __filename) {
|
|
172
|
+
main();
|
|
173
|
+
}
|
package/bin/create-tag.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import { computeVersion } from "./compute-version.js";
|
|
4
5
|
|
|
5
|
-
function run(cmd,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
6
|
+
function run(cmd, captureOutput = true) {
|
|
7
|
+
if (captureOutput) {
|
|
8
|
+
const output = execSync(cmd, { encoding: "utf8", stdio: "pipe" });
|
|
9
|
+
return output ? output.trim() : "";
|
|
10
|
+
} else {
|
|
11
|
+
execSync(cmd, { stdio: "inherit" });
|
|
12
|
+
}
|
|
13
|
+
return "";
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
const args = process.argv.slice(2);
|
|
@@ -18,7 +22,8 @@ let version;
|
|
|
18
22
|
if (USE_COMPUTED) {
|
|
19
23
|
console.log("๐ข Computing version dynamically...");
|
|
20
24
|
try {
|
|
21
|
-
|
|
25
|
+
const obj = computeVersion({ cwd: process.cwd() });
|
|
26
|
+
version = obj.nextVersion;
|
|
22
27
|
} catch {
|
|
23
28
|
console.error("โ Failed to compute version.");
|
|
24
29
|
process.exit(1);
|
|
@@ -26,7 +31,7 @@ if (USE_COMPUTED) {
|
|
|
26
31
|
|
|
27
32
|
if (!version) {
|
|
28
33
|
console.log("โน No version bump detected. Skipping tag creation.");
|
|
29
|
-
process.exit(
|
|
34
|
+
process.exit(10);
|
|
30
35
|
}
|
|
31
36
|
} else {
|
|
32
37
|
console.log("๐ฆ Using version from package.json...");
|
|
@@ -40,7 +45,6 @@ if (USE_COMPUTED) {
|
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
const tag = version;
|
|
43
|
-
|
|
44
48
|
console.log(`๐ Release version: ${tag}`);
|
|
45
49
|
|
|
46
50
|
// check if tag exists
|
|
@@ -52,14 +56,22 @@ try {
|
|
|
52
56
|
// OK
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
const tagMessage = `Release ${tag}`;
|
|
60
|
+
|
|
55
61
|
if (DRY_RUN) {
|
|
56
62
|
console.log("๐งช Dry-run mode enabled.");
|
|
57
|
-
console.log(`Would create
|
|
63
|
+
console.log(`Would create annotated tag: ${tag}`);
|
|
64
|
+
console.log(`Message: "${tagMessage}"`);
|
|
58
65
|
console.log(`VERSION=${tag}`);
|
|
59
|
-
process.exit(
|
|
66
|
+
process.exit(5);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
run(`git
|
|
64
|
-
|
|
65
|
-
console.log(
|
|
69
|
+
try {
|
|
70
|
+
run(`git tag -a ${tag} -m "${tagMessage}"`, false);
|
|
71
|
+
run(`git push origin ${tag}`, false);
|
|
72
|
+
console.log(JSON.stringify({ tag, tagMessage }, null, 2));
|
|
73
|
+
process.exit(0);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error("โ Failed to create or push tag.", err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|