release-suite 0.1.0 → 1.0.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/.github/workflows/create-release-pr.yml +30 -17
- package/.github/workflows/publish-on-merge.yml +26 -9
- package/CHANGELOG.md +38 -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 +213 -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 +210 -0
- package/docs/compute-version.md +204 -0
- package/eslint.config.js +89 -47
- package/lib/git.js +73 -0
- package/lib/utils.js +45 -0
- package/lib/versioning.js +110 -0
- package/package.json +62 -62
|
@@ -1,123 +1,135 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
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
|
-
lastTag = "";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const run = (cmd, cwd = process.cwd()) => execSync(cmd, { encoding: "utf8", cwd }).trim();
|
|
8
|
+
|
|
9
|
+
function ensureGhCLI() {
|
|
10
|
+
try {
|
|
11
|
+
run("gh --version");
|
|
12
|
+
} catch {
|
|
13
|
+
throw new Error("GitHub CLI (gh) is required but not installed.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getDefaultBranch(cwd = process.cwd()) {
|
|
18
|
+
try {
|
|
19
|
+
const out = run("git remote show origin", cwd);
|
|
20
|
+
const m = out.match(/HEAD branch: (\S+)/);
|
|
21
|
+
return (m && m[1]) || "main";
|
|
22
|
+
} catch {
|
|
23
|
+
return "main";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeRepoURL(url) {
|
|
28
|
+
if (!url) return null;
|
|
29
|
+
url = url.replace(/\.git$/, "");
|
|
30
|
+
if (url.startsWith("git@")) {
|
|
31
|
+
const m = url.match(/^git@(.*?):(.*)$/);
|
|
32
|
+
if (m) return `https://${m[1]}/${m[2]}`;
|
|
33
|
+
}
|
|
34
|
+
return url;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function generateReleaseNotes({ isPreview = process.env.PREVIEW_MODE === "true", cwd = process.cwd() } = {}) {
|
|
38
|
+
if (!isPreview) {
|
|
39
|
+
try {
|
|
40
|
+
ensureGhCLI();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error(`❌ ${err.message}`);
|
|
43
|
+
console.error("❌ GitHub CLI (gh) is required but not installed.");
|
|
44
|
+
console.error(" Install: https://cli.github.com/");
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
console.log("ℹ Preview mode: GH CLI not required.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
52
|
+
const version = pkg.version;
|
|
53
|
+
const repoURL = normalizeRepoURL(pkg.repository?.url);
|
|
54
|
+
|
|
55
|
+
let lastTag = "";
|
|
56
|
+
try {
|
|
57
|
+
lastTag = run("git describe --tags --abbrev=0", cwd);
|
|
58
|
+
} catch {
|
|
59
|
+
console.log("⚠ No previous tags found — first release?");
|
|
60
|
+
lastTag = "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log("Last tag:", lastTag || "(none)");
|
|
64
|
+
|
|
65
|
+
const baseBranch = getDefaultBranch(cwd);
|
|
66
|
+
|
|
67
|
+
let prList = [];
|
|
68
|
+
|
|
69
|
+
if (!isPreview) {
|
|
70
|
+
let prQuery =
|
|
71
|
+
`gh pr list --state merged --base ${baseBranch} ` +
|
|
72
|
+
`--json number,title,author,url`;
|
|
73
|
+
|
|
74
|
+
if (lastTag) prQuery += ` --search "merged:>${lastTag}"`;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
prList = JSON.parse(run(prQuery, cwd));
|
|
78
|
+
} catch {
|
|
79
|
+
prList = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let notes = `# What's Changed\n\n`;
|
|
84
|
+
|
|
85
|
+
if (!prList.length) {
|
|
86
|
+
notes += "_No changes since last release._\n\n";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const pr of prList) {
|
|
90
|
+
notes += `- ${pr.title} by @${pr.author.login} in ${pr.url}\n`;
|
|
91
|
+
|
|
92
|
+
let messages = [];
|
|
93
|
+
if (!isPreview) {
|
|
94
|
+
try {
|
|
95
|
+
messages = run(
|
|
96
|
+
`gh pr view ${pr.number} --json commits --jq '.commits[].messageHeadline'`,
|
|
97
|
+
cwd
|
|
98
|
+
)
|
|
99
|
+
.split("\n")
|
|
100
|
+
.filter(Boolean);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(`⚠ Could not fetch commits for PR #${pr.number}: ${err && err.message ? err.message : err}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const msg of messages) {
|
|
107
|
+
notes += ` - ${msg}\n`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
notes += "\n";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const compareLink = repoURL
|
|
114
|
+
? lastTag
|
|
115
|
+
? `${repoURL}/compare/${lastTag}...${version}`
|
|
116
|
+
: repoURL
|
|
117
|
+
: "";
|
|
118
|
+
|
|
119
|
+
if (compareLink) {
|
|
120
|
+
notes += `**Full Changelog**: ${compareLink}\n`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const target = path.join(cwd, isPreview ? "RELEASE_NOTES.preview.md" : "RELEASE_NOTES.md");
|
|
124
|
+
fs.writeFileSync(target, notes, "utf8");
|
|
125
|
+
|
|
126
|
+
console.log(isPreview ? "✔ Generated RELEASE_NOTES.preview.md" : "✔ Generated RELEASE_NOTES.md");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function main() {
|
|
130
|
+
const isPreview = process.env.PREVIEW_MODE === "true";
|
|
131
|
+
generateReleaseNotes({ isPreview });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
135
|
+
if (process.argv[1] === __filename) main();
|
package/bin/preview.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const filesMap = {
|
|
10
|
-
changelog: "CHANGELOG.preview.md",
|
|
11
|
-
notes: "RELEASE_NOTES.preview.md",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const action = process.argv[2];
|
|
15
|
-
|
|
16
|
-
if (!["create", "remove"].includes(action)) {
|
|
17
|
-
console.log("Usage: preview.js [create|remove]");
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (action === "create") {
|
|
22
|
-
console.log("🔧 Generating preview files...");
|
|
23
|
-
|
|
24
|
-
const versionOutput =
|
|
25
|
-
|
|
26
|
-
if (versionOutput) {
|
|
27
|
-
console.log("🔖 Computed version:");
|
|
28
|
-
console.log(versionOutput);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log("✅ Preview ready:");
|
|
35
|
-
console.log(" -", filesMap.changelog);
|
|
36
|
-
console.log(" -", filesMap.notes);
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (action === "remove") {
|
|
41
|
-
console.log("🧹 Removing preview files...");
|
|
42
|
-
for (const f of Object.values(filesMap)) {
|
|
43
|
-
if (fs.existsSync(f)) fs.unlinkSync(f);
|
|
44
|
-
}
|
|
45
|
-
console.log("✔ Preview cleared.");
|
|
46
|
-
process.exit(0);
|
|
47
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { computeVersion } from "./compute-version.js";
|
|
4
|
+
import { generateChangelog } from "./generate-changelog.js";
|
|
5
|
+
import { generateReleaseNotes } from "./generate-release-notes.js";
|
|
6
|
+
|
|
7
|
+
process.env.PREVIEW_MODE = "true";
|
|
8
|
+
|
|
9
|
+
const filesMap = {
|
|
10
|
+
changelog: "CHANGELOG.preview.md",
|
|
11
|
+
notes: "RELEASE_NOTES.preview.md",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const action = process.argv[2];
|
|
15
|
+
|
|
16
|
+
if (!["create", "remove"].includes(action)) {
|
|
17
|
+
console.log("Usage: preview.js [create|remove]");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (action === "create") {
|
|
22
|
+
console.log("🔧 Generating preview files...");
|
|
23
|
+
|
|
24
|
+
const versionOutput = computeVersion({ isPreview: true });
|
|
25
|
+
|
|
26
|
+
if (versionOutput) {
|
|
27
|
+
console.log("🔖 Computed version:");
|
|
28
|
+
console.log(versionOutput);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
generateChangelog({ isPreview: true });
|
|
32
|
+
generateReleaseNotes({ isPreview: true });
|
|
33
|
+
|
|
34
|
+
console.log("✅ Preview ready:");
|
|
35
|
+
console.log(" -", filesMap.changelog);
|
|
36
|
+
console.log(" -", filesMap.notes);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (action === "remove") {
|
|
41
|
+
console.log("🧹 Removing preview files...");
|
|
42
|
+
for (const f of Object.values(filesMap)) {
|
|
43
|
+
if (fs.existsSync(f)) fs.unlinkSync(f);
|
|
44
|
+
}
|
|
45
|
+
console.log("✔ Preview cleared.");
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# API Documentation
|
|
2
|
+
|
|
3
|
+
Each `bin/*.js` script now also exposes a programmatic API so you can call
|
|
4
|
+
the core logic directly from Node without spawning child processes. This
|
|
5
|
+
is useful for integration tests, tooling, or when you need to orchestrate
|
|
6
|
+
the actions from another script.
|
|
7
|
+
|
|
8
|
+
Examples:
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import { computeVersion } from "release-suite/bin/compute-version.js";
|
|
12
|
+
import { generateChangelog } from "release-suite/bin/generate-changelog.js";
|
|
13
|
+
import { generateReleaseNotes } from "release-suite/bin/generate-release-notes.js";
|
|
14
|
+
|
|
15
|
+
const result = computeVersion({ cwd: process.cwd() });
|
|
16
|
+
await generateChangelog({ isPreview: true, cwd: process.cwd() });
|
|
17
|
+
await generateReleaseNotes({ isPreview: true, cwd: process.cwd() });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Notes:
|
|
21
|
+
|
|
22
|
+
- `cwd` controls the directory where git/package.json operations run (pass your consumer project's root).
|
|
23
|
+
- `isPreview: true` writes preview files (`CHANGELOG.preview.md`, `RELEASE_NOTES.preview.md`) and relaxes some external requirements (e.g., `gh`).
|
|
24
|
+
|
|
25
|
+
## computeVersion()
|
|
26
|
+
|
|
27
|
+
> ℹ️ `computeVersion()` follows a strict, immutable contract.
|
|
28
|
+
> See [`compute-version.md`](compute-version.md).
|
package/docs/ci.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# CI/CD Examples
|
|
2
|
+
|
|
3
|
+
## `create-release-pr.yml`
|
|
4
|
+
|
|
5
|
+
```yml
|
|
6
|
+
name: Create Release PR
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
pull_request:
|
|
10
|
+
types: [closed]
|
|
11
|
+
branches:
|
|
12
|
+
- main
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
pull-requests: write
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: release-pr-${{ github.ref }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
release-pr:
|
|
24
|
+
# Only runs when:
|
|
25
|
+
# - The PR has been merged
|
|
26
|
+
# - The PR does NOT have the "release" label
|
|
27
|
+
if: >
|
|
28
|
+
github.event.pull_request.merged == true &&
|
|
29
|
+
!contains(join(github.event.pull_request.labels.*.name, ','), 'release')
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
|
|
37
|
+
# Avoids loops: ignores commits created by bots
|
|
38
|
+
- name: Skip if last commit is from bot
|
|
39
|
+
run: |
|
|
40
|
+
last_author="$(git log -1 --pretty=format:'%an')"
|
|
41
|
+
echo "Last author: $last_author"
|
|
42
|
+
if [[ "$last_author" == *"github-actions"* ]]; then
|
|
43
|
+
echo "Commit created by bot. Canceling workflow."
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
- uses: actions/setup-node@v4
|
|
48
|
+
with:
|
|
49
|
+
node-version: 24
|
|
50
|
+
|
|
51
|
+
- name: Install dependencies
|
|
52
|
+
run: npm ci
|
|
53
|
+
|
|
54
|
+
- name: Install release-suite locally (self usage)
|
|
55
|
+
run: npm install .
|
|
56
|
+
|
|
57
|
+
# Compute next version to release
|
|
58
|
+
- name: Compute next version
|
|
59
|
+
id: compute
|
|
60
|
+
run: |
|
|
61
|
+
set +e
|
|
62
|
+
RESULT=$(npx rs-compute-version --ci --json)
|
|
63
|
+
STATUS=$?
|
|
64
|
+
VERSION=$(echo "$RESULT" | jq -r '.nextVersion // empty')
|
|
65
|
+
|
|
66
|
+
echo "$RESULT"
|
|
67
|
+
echo "status=$STATUS" >> $GITHUB_OUTPUT
|
|
68
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
69
|
+
|
|
70
|
+
- name: Bump package.json
|
|
71
|
+
if: steps.compute.outputs.status == '0' && steps.compute.outputs.version != ''
|
|
72
|
+
run: npm version ${{ steps.compute.outputs.version }} --no-git-tag-version
|
|
73
|
+
|
|
74
|
+
- name: Build
|
|
75
|
+
run: npm run build --if-present
|
|
76
|
+
|
|
77
|
+
- name: Generate changelog
|
|
78
|
+
if: steps.compute.outputs.status == '0' && steps.compute.outputs.version != ''
|
|
79
|
+
run: npx rs-generate-changelog
|
|
80
|
+
|
|
81
|
+
- name: Detect dist directory
|
|
82
|
+
id: dist
|
|
83
|
+
run: |
|
|
84
|
+
if [ -d dist ]; then
|
|
85
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
86
|
+
else
|
|
87
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Automatically create branches, commits, and PRs with peter-evans
|
|
91
|
+
- name: Create Release PR
|
|
92
|
+
if: steps.compute.outputs.status == '0' && steps.compute.outputs.version != ''
|
|
93
|
+
uses: peter-evans/create-pull-request@v6
|
|
94
|
+
with:
|
|
95
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
96
|
+
commit-message: ":bricks: chore(release): prepare version ${{ steps.compute.outputs.version }} [skip ci]"
|
|
97
|
+
branch: release/${{ steps.compute.outputs.version }}
|
|
98
|
+
title: ":bricks: chore(release): ${{ steps.compute.outputs.version }}"
|
|
99
|
+
body: |
|
|
100
|
+
This PR contains the release artifacts:
|
|
101
|
+
|
|
102
|
+
- Updated `package.json` and `package-lock.json` with version ${{ steps.compute.outputs.version }}
|
|
103
|
+
- Updated `CHANGELOG.md` with latest changes
|
|
104
|
+
- Generated `/dist` directory with build files (if applicable)
|
|
105
|
+
|
|
106
|
+
Upon approving & merging, the publish will run automatically.
|
|
107
|
+
labels: |
|
|
108
|
+
release
|
|
109
|
+
add-paths: |
|
|
110
|
+
package.json
|
|
111
|
+
CHANGELOG.md
|
|
112
|
+
${{ steps.dist.outputs.exists == 'true' && 'dist/**' || '' }}
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## `publish-on-merge.yml`
|
|
117
|
+
|
|
118
|
+
```yml
|
|
119
|
+
name: Publish Release
|
|
120
|
+
|
|
121
|
+
on:
|
|
122
|
+
pull_request:
|
|
123
|
+
types: [closed]
|
|
124
|
+
branches:
|
|
125
|
+
- main
|
|
126
|
+
|
|
127
|
+
permissions:
|
|
128
|
+
contents: write
|
|
129
|
+
id-token: write # 🔐 REQUIRED for OIDC
|
|
130
|
+
packages: write
|
|
131
|
+
pull-requests: write
|
|
132
|
+
|
|
133
|
+
concurrency:
|
|
134
|
+
group: publish-release
|
|
135
|
+
cancel-in-progress: false
|
|
136
|
+
|
|
137
|
+
jobs:
|
|
138
|
+
publish:
|
|
139
|
+
# Only runs when:
|
|
140
|
+
# - The PR has been merged
|
|
141
|
+
# - The PR has the "release" label
|
|
142
|
+
# - The PR title starts with ":bricks: chore(release):"
|
|
143
|
+
# - The PR was created by a bot
|
|
144
|
+
if: >
|
|
145
|
+
github.event.pull_request.merged == true &&
|
|
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'
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
steps:
|
|
151
|
+
- name: Checkout repository
|
|
152
|
+
uses: actions/checkout@v4
|
|
153
|
+
with:
|
|
154
|
+
fetch-depth: 0
|
|
155
|
+
|
|
156
|
+
- name: Setup Node.js
|
|
157
|
+
uses: actions/setup-node@v4
|
|
158
|
+
with:
|
|
159
|
+
node-version: 24
|
|
160
|
+
registry-url: https://registry.npmjs.org/
|
|
161
|
+
|
|
162
|
+
- name: Install dependencies
|
|
163
|
+
run: npm ci
|
|
164
|
+
|
|
165
|
+
- name: Install release-suite locally (self usage)
|
|
166
|
+
run: npm install .
|
|
167
|
+
|
|
168
|
+
- name: Create Git Tag
|
|
169
|
+
id: tag
|
|
170
|
+
run: |
|
|
171
|
+
set +e
|
|
172
|
+
RESULT=$(npx create-tag)
|
|
173
|
+
STATUS=$?
|
|
174
|
+
TAG=$(echo "$RESULT" | jq -r '.tag // empty')
|
|
175
|
+
|
|
176
|
+
echo "$RESULT"
|
|
177
|
+
echo "status=$STATUS" >> $GITHUB_OUTPUT
|
|
178
|
+
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
|
179
|
+
|
|
180
|
+
# Publish to npm using Trusted Publishing (OIDC)
|
|
181
|
+
- name: Publish to npm (Trusted Publishing)
|
|
182
|
+
if: steps.tag.outputs.status == '0' && steps.tag.outputs.tag != ''
|
|
183
|
+
run: npm publish
|
|
184
|
+
|
|
185
|
+
# Generate release notes for GitHub Release
|
|
186
|
+
- name: Generate GitHub Release Notes
|
|
187
|
+
if: steps.tag.outputs.status == '0' && steps.tag.outputs.tag != ''
|
|
188
|
+
run: npx rs-generate-release-notes
|
|
189
|
+
env:
|
|
190
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
191
|
+
|
|
192
|
+
# Create GitHub Release with notes and attach built assets
|
|
193
|
+
- name: Create GitHub Release + Tag
|
|
194
|
+
if: steps.tag.outputs.status == '0' && steps.tag.outputs.tag != ''
|
|
195
|
+
env:
|
|
196
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
197
|
+
run: |
|
|
198
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
199
|
+
|
|
200
|
+
ASSETS=()
|
|
201
|
+
if [ -d dist ]; then
|
|
202
|
+
ASSETS=(dist/**)
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
gh release create "$VERSION" \
|
|
206
|
+
--title "$VERSION" \
|
|
207
|
+
--notes-file RELEASE_NOTES.md \
|
|
208
|
+
"${ASSETS[@]}"
|
|
209
|
+
|
|
210
|
+
```
|