Skip to content

Commit 6f9afe0

Browse files
authored
Automated release (#62)
* Update sync-headers to close old PRs * Create prepare-release action, update-changelog script * Create publish-release action
1 parent d5cfe19 commit 6f9afe0

File tree

6 files changed

+270
-3
lines changed

6 files changed

+270
-3
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Prepare Release
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: ['main']
7+
paths: ['include/**', 'def/**']
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
name: Prepare Release
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: 18
24+
- name: Set up ghauth config (Ubuntu)
25+
run: |
26+
mkdir -p ~/.config/changelog-maker/
27+
echo '{"user": "github-actions[bot]", "token": "'${{ secrets.GITHUB_TOKEN }}'"}' > ~/.config/changelog-maker/config.json
28+
- name: Update package version
29+
run: npm version --no-git-tag-version minor
30+
- name: Update changelog
31+
run: npm run --silent update-changelog
32+
- shell: bash
33+
id: pr-vars
34+
name: Compute Pull Request Variables
35+
run: |
36+
VERSION=$(jq -r ".version" package.json)
37+
COMMIT_MESSAGE="release: v$VERSION"
38+
BRANCH_NAME="release/v$VERSION"
39+
echo $COMMIT_MESSAGE
40+
if git ls-remote --exit-code --heads $GITHUB_SERVER_URL/$GITHUB_REPOSITORY $BRANCH_NAME >/dev/null; then
41+
echo "Branch exists. Nothing to do."
42+
else
43+
echo "Branch does not exists."
44+
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
45+
echo "COMMIT_MESSAGE=$COMMIT_MESSAGE" >> $GITHUB_OUTPUT
46+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
47+
fi
48+
- name: Create Pull Request
49+
id: cpr
50+
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
51+
if: ${{ steps.pr-vars.outputs.BRANCH_NAME }}
52+
with:
53+
branch: ${{ steps.pr-vars.outputs.BRANCH_NAME }}
54+
commit-message: ${{ steps.pr-vars.outputs.COMMIT_MESSAGE }}
55+
title: ${{ steps.pr-vars.outputs.COMMIT_MESSAGE }}
56+
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
57+
body: Automated release for version v${{ steps.pr-vars.outputs.VERSION }}
58+
labels: release
59+
delete-branch: true
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Publish Release
2+
3+
on:
4+
workflow_dispatch:
5+
6+
# Uncomment below to enable automated running of publish task on changes to
7+
# package.json on main branch.
8+
9+
# push:
10+
# branches: ['main']
11+
# paths: ['package.json']
12+
13+
permissions:
14+
contents: write
15+
pull-requests: write
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
name: Publish Release
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 20
26+
registry-url: 'https://registry.npmjs.org'
27+
- shell: bash
28+
id: release-vars
29+
name: Compute Release Variables
30+
run: |
31+
VERSION=$(jq -r ".version" package.json)
32+
PACKAGE_NAME=$(jq -r ".name" package.json)
33+
npm show $PACKAGE_NAME@$VERSION && true
34+
SHOULD_PUBLISH_VERSION="$?"
35+
echo "VERSION=$VERSION PACKAGE_NAME=$PACKAGE_NAME SHOULD_PUBLISH_VERSION=$SHOULD_PUBLISH_VERSION"
36+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
37+
echo "SHOULD_PUBLISH_VERSION=$SHOULD_PUBLISH_VERSION" >> $GITHUB_OUTPUT
38+
- name: Publish to npm
39+
if: ${{ steps.release-vars.outputs.SHOULD_PUBLISH_VERSION != '0' }}
40+
run: |
41+
npm publish --provenance --access public
42+
env:
43+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
44+
- name: Create GitHub Release
45+
if: ${{ steps.release-vars.outputs.SHOULD_PUBLISH_VERSION != '0' }}
46+
uses: ncipollo/release-action@v1
47+
with:
48+
tag: v${{ steps.release-vars.outputs.VERSION }}
49+
commit: main
50+
name: Release ${{ steps.release-vars.outputs.VERSION }}
51+
generateReleaseNotes: true
52+
token: ${{ secrets.GITHUB_TOKEN }}
53+
skipIfReleaseExists: true

.github/workflows/sync-headers.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,28 @@ jobs:
3939
echo "Branch does not exists."
4040
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
4141
echo "COMMIT_MESSAGE=$COMMIT_MESSAGE" >> $GITHUB_OUTPUT
42+
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
4243
fi
4344
fi
4445
- name: Create Pull Request
45-
uses: peter-evans/create-pull-request@v4
46+
id: cpr
47+
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
4648
if: ${{ steps.check-changes.outputs.BRANCH_NAME }}
4749
with:
4850
branch: ${{ steps.check-changes.outputs.BRANCH_NAME }}
4951
commit-message: ${{ steps.check-changes.outputs.COMMIT_MESSAGE }}
5052
title: ${{ steps.check-changes.outputs.COMMIT_MESSAGE }}
5153
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
52-
body: null
54+
body: Automated sync of headers with Node.js version ${{ steps.check-changes.outputs.VERSION }}
55+
labels: headers
5356
delete-branch: true
57+
- name: Close existing PRs
58+
env:
59+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60+
if: ${{ steps.cpr.outputs.pull-request-number }}
61+
run: |
62+
gh pr list --json number,author,title,labels --jq '[ .[] | select(.author.login == "app/github-actions" and .number != ${{ steps.cpr.outputs.pull-request-number }} and (.labels[]| select(.name =="headers" )))]' | jq -c '.[]' |
63+
while IFS=$"\n" read -r c; do
64+
pr_number=$(echo "$c" | jq -r '.number')
65+
gh pr close $pr_number --delete-branch --comment "Closing in favor of [#${{ steps.cpr.outputs.pull-request-number }}](${{ steps.cpr.outputs.pull-request-url }})."
66+
done

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"scripts": {
5353
"update-headers": "node --no-warnings scripts/update-headers.js",
54+
"update-changelog": "node --no-warnings scripts/update-changelog.js",
5455
"write-symbols": "node --no-warnings scripts/write-symbols.js",
5556
"write-win32-def": "node --no-warnings scripts/write-win32-def.js"
5657
},

scripts/update-changelog.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const { exec, spawn } = require("node:child_process");
2+
const { createReadStream } = require("node:fs");
3+
const { createInterface } = require("node:readline");
4+
const { resolve: resolvePath } = require("node:path");
5+
const { writeFile } = require("node:fs/promises");
6+
7+
/**
8+
* Returns a string of the new changelog entries by running `npx changelog-maker
9+
* --format=markdown`.
10+
*
11+
* @returns {Promise<string>}
12+
*/
13+
async function getNewChangelogEntries() {
14+
const { stdout, stderr } = await new Promise((resolve, reject) => {
15+
// Echo an empty string to pass as the GitHub Personal Access Token
16+
// (PAT). This causes the process to error if no PAT is found in the
17+
// changelog-maker configuration file.
18+
exec("echo '' | npx changelog-maker --format=markdown", (err, stdout, stderr) => {
19+
if (err) {
20+
reject(err);
21+
} else {
22+
resolve({ stdout, stderr });
23+
}
24+
});
25+
26+
});
27+
28+
return { stdout, stderr };
29+
}
30+
31+
/**
32+
* Returns the text of the changelog file, excluding header lines.
33+
*
34+
* @param {string} changelogPath Path to changelog file
35+
* @returns {Promise<string>}
36+
*/
37+
async function getExistingChangelogText(changelogPath) {
38+
const data = await new Promise((resolve, reject) => {
39+
try {
40+
const rl = createInterface(createReadStream(changelogPath));
41+
42+
let lines = "";
43+
let lineNumber = 1;
44+
45+
rl.on('line', function (line) {
46+
if (lineNumber > 2) {
47+
lines += line + "\n";
48+
}
49+
50+
lineNumber++;
51+
});
52+
53+
rl.on('close', () => {
54+
resolve(lines);
55+
});
56+
57+
rl.on('error', (err) => {
58+
reject(err);
59+
});
60+
61+
} catch (e) {
62+
reject(e);
63+
}
64+
});
65+
66+
return data;
67+
}
68+
69+
70+
/**
71+
* Returns the string for the new changelog file.
72+
*
73+
* @param {string} newEntries New changelog entries
74+
* @param {string} existingText Existing changelog text
75+
* @param {string} author Author of the release
76+
* @returns {string}
77+
*/
78+
function generateChangelogText(newEntries, existingText, author = "github-actions\\[bot]") {
79+
const packageVersion = require("../package.json").version;
80+
const currentDateString = new Date().toISOString().split(/T/)[0];
81+
82+
const notableChanges = Array.from(newEntries.matchAll(/ (- [^(]+) \([^)]+\)( \[#\d+]\([^)]+\))?/g))
83+
.map(matches => matches[1])
84+
.join("\n");
85+
86+
return `# node-api-headers Changelog
87+
88+
## ${currentDateString} Version ${packageVersion}, ${author}
89+
90+
### Notable changes
91+
92+
${notableChanges}
93+
94+
### Commits
95+
96+
${newEntries.trim()}
97+
98+
${existingText.trim()}
99+
`;
100+
}
101+
102+
/**
103+
* Throws an error (asynchronously) if there are uncommitted changes to the changelog file.
104+
*
105+
* @param {string} changelogPath Path to changelog file
106+
* @returns {Promise<void>}
107+
*/
108+
function assertCleanChangelog(changelogPath) {
109+
return new Promise((resolve, reject) => {
110+
const spawned = spawn("git", ["diff", "--quiet", changelogPath]);
111+
spawned.on('exit', function (exitCode) {
112+
if (exitCode === 0) {
113+
resolve(undefined);
114+
} else {
115+
reject(new Error(`There are uncommitted changes to ${changelogPath}. Commit, revert, or stash changes first.`));
116+
}
117+
});
118+
119+
spawned.on('error', function (err) {
120+
reject(err);
121+
});
122+
});
123+
}
124+
125+
async function main() {
126+
const changelogPath = resolvePath(__dirname, "..", "CHANGELOG.md");
127+
await assertCleanChangelog(changelogPath);
128+
const [{ stdout: newEntires, stderr }, existingText] = await Promise.all([getNewChangelogEntries(), getExistingChangelogText(changelogPath)]);
129+
const changelogText = generateChangelogText(newEntires, existingText);
130+
131+
await writeFile(changelogPath, changelogText);
132+
if (stderr) {
133+
console.error("stderr from changelog-maker:\n", stderr)
134+
}
135+
console.log(`Changelog written to ${changelogPath}`);
136+
}
137+
138+
main().catch(e => {
139+
console.error(e);
140+
process.exitCode = 1;
141+
});

scripts/update-headers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ async function main() {
160160
},
161161
});
162162

163-
console.log(`Update headers from nodejs/node tag ${tag}`);
163+
console.log(`headers: update headers from nodejs/node tag ${tag}`);
164164

165165
const files = ['js_native_api_types.h', 'js_native_api.h', 'node_api_types.h', 'node_api.h'];
166166

0 commit comments

Comments
 (0)