release-suite 1.0.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 +4 -1
- package/.github/workflows/publish-on-merge.yml +3 -10
- package/CHANGELOG.md +14 -0
- package/bin/generate-changelog.js +40 -24
- package/docs/ci.md +7 -11
- package/lib/constants.js +28 -0
- package/lib/git.js +58 -2
- package/lib/versioning.js +7 -8
- package/package.json +1 -1
|
@@ -82,6 +82,10 @@ jobs:
|
|
|
82
82
|
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
83
83
|
fi
|
|
84
84
|
|
|
85
|
+
- name: Stage dist if exists
|
|
86
|
+
if: steps.dist.outputs.exists == 'true'
|
|
87
|
+
run: git add dist
|
|
88
|
+
|
|
85
89
|
# Automatically create branches, commits, and PRs with peter-evans
|
|
86
90
|
- name: Create Release PR
|
|
87
91
|
if: steps.compute.outputs.status == '0' && steps.compute.outputs.version != ''
|
|
@@ -104,4 +108,3 @@ jobs:
|
|
|
104
108
|
add-paths: |
|
|
105
109
|
package.json
|
|
106
110
|
CHANGELOG.md
|
|
107
|
-
${{ steps.dist.outputs.exists == 'true' && 'dist/**' || '' }}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
name: Publish Release
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
5
|
-
types: [closed]
|
|
4
|
+
push:
|
|
6
5
|
branches:
|
|
7
6
|
- main
|
|
8
7
|
|
|
@@ -19,15 +18,9 @@ concurrency:
|
|
|
19
18
|
jobs:
|
|
20
19
|
publish:
|
|
21
20
|
# Only runs when:
|
|
22
|
-
# - The
|
|
23
|
-
# - The PR has the "release" label
|
|
24
|
-
# - The PR title starts with ":bricks: chore(release):"
|
|
25
|
-
# - The PR was created by a bot
|
|
21
|
+
# - The commit message starts with ":bricks: chore(release):"
|
|
26
22
|
if: >
|
|
27
|
-
github.event.
|
|
28
|
-
startsWith(github.event.pull_request.title, ':bricks: chore(release):') &&
|
|
29
|
-
contains(join(github.event.pull_request.labels.*.name, ','), 'release') &&
|
|
30
|
-
github.event.pull_request.user.type == 'Bot'
|
|
23
|
+
startsWith(github.event.head_commit.message, ':bricks: chore(release):')
|
|
31
24
|
runs-on: ubuntu-latest
|
|
32
25
|
steps:
|
|
33
26
|
- name: Checkout repository
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,9 @@ import { execSync } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { COMMIT_RE, COMMIT_EMOJI_RE } from "../lib/constants.js";
|
|
7
|
+
import { normalizeCommits, parseCommit } from "../lib/git.js";
|
|
8
|
+
import { normalizeSubject } from "../lib/versioning.js";
|
|
6
9
|
import { computeVersion } from "./compute-version.js";
|
|
7
10
|
|
|
8
11
|
function run(cmd, cwd = process.cwd()) {
|
|
@@ -37,26 +40,22 @@ function getAllTags(cwd = process.cwd()) {
|
|
|
37
40
|
|
|
38
41
|
function getCommitsBetween(from, to, cwd = process.cwd()) {
|
|
39
42
|
const range = from ? `${from}..${to}` : to;
|
|
43
|
+
|
|
40
44
|
try {
|
|
41
|
-
return run(
|
|
42
|
-
|
|
45
|
+
return run(
|
|
46
|
+
`git log ${range} --pretty=format:%H%x1f%s%x1f%B%x1e`,
|
|
47
|
+
cwd
|
|
48
|
+
)
|
|
49
|
+
.split("\x1e")
|
|
50
|
+
.map(c => c.trim())
|
|
43
51
|
.filter(Boolean);
|
|
44
52
|
} catch {
|
|
45
53
|
return [];
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
|
|
49
|
-
function parseCommit(line) {
|
|
50
|
-
const [hash, subject = "", body = ""] = line.split("\x1f");
|
|
51
|
-
return { hash, subject: subject.trim(), body: body.trim() };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
57
|
function cleanSubject(subject) {
|
|
55
|
-
|
|
56
|
-
s = s.replace(
|
|
57
|
-
/^(feat|fix|refactor|docs|chore|style|test|build|perf|ci|raw|cleanup|remove)(\(.+\))?(!)?:\s*/i,
|
|
58
|
-
""
|
|
59
|
-
);
|
|
58
|
+
const s = normalizeSubject(subject).replace(COMMIT_RE, "");
|
|
60
59
|
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
|
|
61
60
|
}
|
|
62
61
|
|
|
@@ -78,9 +77,6 @@ function categorize(commits) {
|
|
|
78
77
|
remove: [],
|
|
79
78
|
};
|
|
80
79
|
|
|
81
|
-
const reType =
|
|
82
|
-
/^(:\S+: )?(feat|fix|refactor|docs|chore|style|test|build|perf|ci|raw|cleanup|remove)(\(.+\))?(!)?:/i;
|
|
83
|
-
|
|
84
80
|
for (const c of commits) {
|
|
85
81
|
const { subject, body } = c;
|
|
86
82
|
|
|
@@ -89,7 +85,7 @@ function categorize(commits) {
|
|
|
89
85
|
continue;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
const match = subject.match(
|
|
88
|
+
const match = subject.match(COMMIT_EMOJI_RE);
|
|
93
89
|
const desc = cleanSubject(subject);
|
|
94
90
|
|
|
95
91
|
if (!match) {
|
|
@@ -98,7 +94,14 @@ function categorize(commits) {
|
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
const type = match[2].toLowerCase();
|
|
101
|
-
|
|
97
|
+
const finalDesc = desc || cleanSubject(subject) || subject;
|
|
98
|
+
|
|
99
|
+
if (finalDesc && finalDesc.trim()) {
|
|
100
|
+
(buckets[type] || buckets.chore).push({
|
|
101
|
+
desc: finalDesc.trim(),
|
|
102
|
+
hash: c.hash
|
|
103
|
+
});
|
|
104
|
+
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
return buckets;
|
|
@@ -128,12 +131,18 @@ function buildSection(version, buckets) {
|
|
|
128
131
|
let hasContent = false;
|
|
129
132
|
|
|
130
133
|
for (const [key, title] of sections) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
const items = buckets[key]
|
|
135
|
+
.map(c => c.desc && c.desc.trim())
|
|
136
|
+
.filter(Boolean);
|
|
137
|
+
|
|
138
|
+
if (!items.length) continue;
|
|
139
|
+
|
|
140
|
+
hasContent = true;
|
|
141
|
+
out.push(`${title}\n`);
|
|
142
|
+
for (const desc of items) {
|
|
143
|
+
out.push(`- ${desc}`);
|
|
136
144
|
}
|
|
145
|
+
out.push("");
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
if (!hasContent) out.push("_No changes._\n");
|
|
@@ -165,7 +174,10 @@ export function generateChangelog({ isPreview = process.env.PREVIEW_MODE === "tr
|
|
|
165
174
|
|
|
166
175
|
// Always generate the upcoming version (preview & release)
|
|
167
176
|
if (!changelogHasVersion(CHANGELOG_FILE, nextVersion, cwd)) {
|
|
168
|
-
const commits =
|
|
177
|
+
const commits = normalizeCommits(
|
|
178
|
+
getCommitsBetween(lastTag, "HEAD", cwd).map(parseCommit)
|
|
179
|
+
);
|
|
180
|
+
|
|
169
181
|
|
|
170
182
|
if (commits.length) {
|
|
171
183
|
const buckets = categorize(commits);
|
|
@@ -180,7 +192,11 @@ export function generateChangelog({ isPreview = process.env.PREVIEW_MODE === "tr
|
|
|
180
192
|
|
|
181
193
|
if (changelogHasVersion(CHANGELOG_FILE, tag, cwd)) continue;
|
|
182
194
|
|
|
183
|
-
const commits =
|
|
195
|
+
const commits = normalizeCommits(
|
|
196
|
+
getCommitsBetween(previous, tag, cwd)
|
|
197
|
+
.map(parseCommit)
|
|
198
|
+
);
|
|
199
|
+
|
|
184
200
|
if (!commits.length) continue;
|
|
185
201
|
|
|
186
202
|
const buckets = categorize(commits);
|
package/docs/ci.md
CHANGED
|
@@ -87,6 +87,10 @@ jobs:
|
|
|
87
87
|
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
88
88
|
fi
|
|
89
89
|
|
|
90
|
+
- name: Stage dist if exists
|
|
91
|
+
if: steps.dist.outputs.exists == 'true'
|
|
92
|
+
run: git add dist
|
|
93
|
+
|
|
90
94
|
# Automatically create branches, commits, and PRs with peter-evans
|
|
91
95
|
- name: Create Release PR
|
|
92
96
|
if: steps.compute.outputs.status == '0' && steps.compute.outputs.version != ''
|
|
@@ -109,7 +113,6 @@ jobs:
|
|
|
109
113
|
add-paths: |
|
|
110
114
|
package.json
|
|
111
115
|
CHANGELOG.md
|
|
112
|
-
${{ steps.dist.outputs.exists == 'true' && 'dist/**' || '' }}
|
|
113
116
|
|
|
114
117
|
```
|
|
115
118
|
|
|
@@ -119,8 +122,7 @@ jobs:
|
|
|
119
122
|
name: Publish Release
|
|
120
123
|
|
|
121
124
|
on:
|
|
122
|
-
|
|
123
|
-
types: [closed]
|
|
125
|
+
push:
|
|
124
126
|
branches:
|
|
125
127
|
- main
|
|
126
128
|
|
|
@@ -137,15 +139,9 @@ concurrency:
|
|
|
137
139
|
jobs:
|
|
138
140
|
publish:
|
|
139
141
|
# Only runs when:
|
|
140
|
-
# - The
|
|
141
|
-
# - The PR has the "release" label
|
|
142
|
-
# - The PR title starts with ":bricks: chore(release):"
|
|
143
|
-
# - The PR was created by a bot
|
|
142
|
+
# - The commit message starts with ":bricks: chore(release):"
|
|
144
143
|
if: >
|
|
145
|
-
github.event.
|
|
146
|
-
startsWith(github.event.pull_request.title, ':bricks: chore(release):') &&
|
|
147
|
-
contains(join(github.event.pull_request.labels.*.name, ','), 'release') &&
|
|
148
|
-
github.event.pull_request.user.type == 'Bot'
|
|
144
|
+
startsWith(github.event.head_commit.message, ':bricks: chore(release):')
|
|
149
145
|
runs-on: ubuntu-latest
|
|
150
146
|
steps:
|
|
151
147
|
- name: Checkout repository
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Constants used across the release-suite package
|
|
2
|
+
|
|
3
|
+
// Commit types recognized in Conventional Commits
|
|
4
|
+
export const COMMIT_TYPES = [
|
|
5
|
+
'feat',
|
|
6
|
+
'fix',
|
|
7
|
+
'refactor',
|
|
8
|
+
'docs',
|
|
9
|
+
'chore',
|
|
10
|
+
'style',
|
|
11
|
+
'test',
|
|
12
|
+
'build',
|
|
13
|
+
'perf',
|
|
14
|
+
'ci',
|
|
15
|
+
'raw',
|
|
16
|
+
'cleanup',
|
|
17
|
+
'remove',
|
|
18
|
+
].join('|');
|
|
19
|
+
|
|
20
|
+
export const COMMIT_RE = new RegExp(
|
|
21
|
+
`^(${COMMIT_TYPES})(\\(.+\\))?(!)?:\\s*`,
|
|
22
|
+
'i'
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const COMMIT_EMOJI_RE = new RegExp(
|
|
26
|
+
`^(:\\S+: )?(${COMMIT_TYPES})(\\(.+\\))?(!)?:`,
|
|
27
|
+
'i'
|
|
28
|
+
);
|
package/lib/git.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { COMMIT_RE } from "./constants.js";
|
|
1
2
|
import { run } from "./utils.js";
|
|
3
|
+
import { normalizeSubject } from "./versioning.js";
|
|
2
4
|
|
|
3
5
|
/* ===========================
|
|
4
6
|
* Git helpers
|
|
@@ -69,5 +71,59 @@ export function getCommits(range, cwd) {
|
|
|
69
71
|
*/
|
|
70
72
|
export function parseCommit(line) {
|
|
71
73
|
const [hash, subject = "", body = ""] = line.split("\x1f");
|
|
72
|
-
return {
|
|
73
|
-
|
|
74
|
+
return {
|
|
75
|
+
hash: hash.trim(),
|
|
76
|
+
subject: subject.trim(),
|
|
77
|
+
body: body.trim()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isSquashCommit(commit) {
|
|
82
|
+
return /\n\s*[*-]\s+/.test(commit.body);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function extractCommitsFromSquash(commit) {
|
|
86
|
+
const lines = commit.body
|
|
87
|
+
.split("\n")
|
|
88
|
+
.map(l => l.trim())
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
|
|
91
|
+
const firstBulletIndex = lines.findIndex(
|
|
92
|
+
l => l.startsWith("* ") || l.startsWith("- ")
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (firstBulletIndex === -1) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return lines
|
|
100
|
+
.slice(firstBulletIndex)
|
|
101
|
+
.filter(l => l.startsWith("* ") || l.startsWith("- "))
|
|
102
|
+
.map(l => normalizeSubject(l))
|
|
103
|
+
.filter(subject => COMMIT_RE.test(subject))
|
|
104
|
+
.map(subject => ({
|
|
105
|
+
hash: commit.hash,
|
|
106
|
+
subject,
|
|
107
|
+
body: ""
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function normalizeCommits(commits) {
|
|
113
|
+
const normalized = [];
|
|
114
|
+
|
|
115
|
+
for (const commit of commits) {
|
|
116
|
+
if (isSquashCommit(commit)) {
|
|
117
|
+
const extracted = extractCommitsFromSquash(commit);
|
|
118
|
+
if (extracted.length) {
|
|
119
|
+
normalized.push(...extracted);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// fallback: normal commit
|
|
125
|
+
normalized.push(commit);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return normalized;
|
|
129
|
+
}
|
package/lib/versioning.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { COMMIT_RE } from "./constants.js";
|
|
2
|
+
|
|
1
3
|
/* ===========================
|
|
2
4
|
* Semver detection
|
|
3
5
|
* =========================== */
|
|
4
6
|
|
|
5
|
-
const COMMIT_RE =
|
|
6
|
-
/^(feat|fix|refactor|docs|chore|style|test|build|perf|ci|cleanup|remove)(\(.+\))?(!)?:/i;
|
|
7
|
-
|
|
8
7
|
/**
|
|
9
8
|
* Normalize a subject string by removing leading emoji and trimming whitespace.
|
|
10
9
|
*
|
|
@@ -25,12 +24,12 @@ const COMMIT_RE =
|
|
|
25
24
|
* @example
|
|
26
25
|
* normalizeSubject(' :a::b:Multiple emojis at start ') // 'Multiple emojis at start'
|
|
27
26
|
*/
|
|
28
|
-
function normalizeSubject(subject) {
|
|
27
|
+
export function normalizeSubject(subject = "") {
|
|
29
28
|
return subject
|
|
30
|
-
//
|
|
31
|
-
.replace(/^:\
|
|
32
|
-
//
|
|
33
|
-
.replace(/^[\u{1F300}-\u{1FAFF}]+\s
|
|
29
|
+
.replace(/^[-*]\s*/, "") // leading list markers
|
|
30
|
+
.replace(/^:\w+:\s*/, "") // :emoji:
|
|
31
|
+
.replace(/^[^\w]+/, "") // leading symbols
|
|
32
|
+
.replace(/^(?:[\u{1F300}-\u{1FAFF}]+\s*)+/u, "") // leading Unicode emoji
|
|
34
33
|
.trim();
|
|
35
34
|
}
|
|
36
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "release-suite",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Semantic versioning tools for Git-based projects, providing automated version computation, changelog generation and release notes creation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|