Skip to content

ci: add support for automated releases #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Prepare Release

on:
workflow_dispatch:
push:
branches: ['main']
paths: ['include/**', 'def/**']

permissions:
contents: write
pull-requests: write

jobs:
build:
runs-on: ubuntu-latest
name: Prepare Release
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Set up ghauth config (Ubuntu)
run: |
mkdir -p ~/.config/changelog-maker/
echo '{"user": "github-actions[bot]", "token": "'${{ secrets.GITHUB_TOKEN }}'"}' > ~/.config/changelog-maker/config.json
- name: Update package version
run: npm version --no-git-tag-version minor
- name: Update changelog
run: npm run --silent update-changelog
- shell: bash
id: pr-vars
name: Compute Pull Request Variables
run: |
VERSION=$(jq -r ".version" package.json)
COMMIT_MESSAGE="release: v$VERSION"
BRANCH_NAME="release/v$VERSION"
echo $COMMIT_MESSAGE
if git ls-remote --exit-code --heads $GITHUB_SERVER_URL/$GITHUB_REPOSITORY $BRANCH_NAME >/dev/null; then
echo "Branch exists. Nothing to do."
else
echo "Branch does not exists."
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "COMMIT_MESSAGE=$COMMIT_MESSAGE" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
if: ${{ steps.pr-vars.outputs.BRANCH_NAME }}
with:
branch: ${{ steps.pr-vars.outputs.BRANCH_NAME }}
commit-message: ${{ steps.pr-vars.outputs.COMMIT_MESSAGE }}
title: ${{ steps.pr-vars.outputs.COMMIT_MESSAGE }}
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
body: Automated release for version v${{ steps.pr-vars.outputs.VERSION }}
labels: release
delete-branch: true
53 changes: 53 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Publish Release

on:
workflow_dispatch:

# Uncomment below to enable automated running of publish task on changes to
# package.json on main branch.

# push:
# branches: ['main']
# paths: ['package.json']

permissions:
contents: write
pull-requests: write

jobs:
build:
runs-on: ubuntu-latest
name: Publish Release
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- shell: bash
id: release-vars
name: Compute Release Variables
run: |
VERSION=$(jq -r ".version" package.json)
PACKAGE_NAME=$(jq -r ".name" package.json)
npm show $PACKAGE_NAME@$VERSION && true
SHOULD_PUBLISH_VERSION="$?"
echo "VERSION=$VERSION PACKAGE_NAME=$PACKAGE_NAME SHOULD_PUBLISH_VERSION=$SHOULD_PUBLISH_VERSION"
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "SHOULD_PUBLISH_VERSION=$SHOULD_PUBLISH_VERSION" >> $GITHUB_OUTPUT
- name: Publish to npm
if: ${{ steps.release-vars.outputs.SHOULD_PUBLISH_VERSION != '0' }}
run: |
npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub Release
if: ${{ steps.release-vars.outputs.SHOULD_PUBLISH_VERSION != '0' }}
uses: ncipollo/release-action@v1
with:
tag: v${{ steps.release-vars.outputs.VERSION }}
commit: main
name: Release ${{ steps.release-vars.outputs.VERSION }}
generateReleaseNotes: true
token: ${{ secrets.GITHUB_TOKEN }}
skipIfReleaseExists: true
17 changes: 15 additions & 2 deletions .github/workflows/sync-headers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,28 @@ jobs:
echo "Branch does not exists."
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "COMMIT_MESSAGE=$COMMIT_MESSAGE" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
fi
fi
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
id: cpr
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
if: ${{ steps.check-changes.outputs.BRANCH_NAME }}
with:
branch: ${{ steps.check-changes.outputs.BRANCH_NAME }}
commit-message: ${{ steps.check-changes.outputs.COMMIT_MESSAGE }}
title: ${{ steps.check-changes.outputs.COMMIT_MESSAGE }}
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
body: null
body: Automated sync of headers with Node.js version ${{ steps.check-changes.outputs.VERSION }}
labels: headers
delete-branch: true
- name: Close existing PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ steps.cpr.outputs.pull-request-number }}
run: |
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 '.[]' |
while IFS=$"\n" read -r c; do
pr_number=$(echo "$c" | jq -r '.number')
gh pr close $pr_number --delete-branch --comment "Closing in favor of [#${{ steps.cpr.outputs.pull-request-number }}](${{ steps.cpr.outputs.pull-request-url }})."
done
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
},
"scripts": {
"update-headers": "node --no-warnings scripts/update-headers.js",
"update-changelog": "node --no-warnings scripts/update-changelog.js",
"write-symbols": "node --no-warnings scripts/write-symbols.js",
"write-win32-def": "node --no-warnings scripts/write-win32-def.js"
},
Expand Down
141 changes: 141 additions & 0 deletions scripts/update-changelog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const { exec, spawn } = require("node:child_process");
const { createReadStream } = require("node:fs");
const { createInterface } = require("node:readline");
const { resolve: resolvePath } = require("node:path");
const { writeFile } = require("node:fs/promises");

/**
* Returns a string of the new changelog entries by running `npx changelog-maker
* --format=markdown`.
*
* @returns {Promise<string>}
*/
async function getNewChangelogEntries() {
const { stdout, stderr } = await new Promise((resolve, reject) => {
// Echo an empty string to pass as the GitHub Personal Access Token
// (PAT). This causes the process to error if no PAT is found in the
// changelog-maker configuration file.
exec("echo '' | npx changelog-maker --format=markdown", (err, stdout, stderr) => {
if (err) {
reject(err);
} else {
resolve({ stdout, stderr });
}
});

});

return { stdout, stderr };
}

/**
* Returns the text of the changelog file, excluding header lines.
*
* @param {string} changelogPath Path to changelog file
* @returns {Promise<string>}
*/
async function getExistingChangelogText(changelogPath) {
const data = await new Promise((resolve, reject) => {
try {
const rl = createInterface(createReadStream(changelogPath));

let lines = "";
let lineNumber = 1;

rl.on('line', function (line) {
if (lineNumber > 2) {
lines += line + "\n";
}

lineNumber++;
});

rl.on('close', () => {
resolve(lines);
});

rl.on('error', (err) => {
reject(err);
});

} catch (e) {
reject(e);
}
});

return data;
}


/**
* Returns the string for the new changelog file.
*
* @param {string} newEntries New changelog entries
* @param {string} existingText Existing changelog text
* @param {string} author Author of the release
* @returns {string}
*/
function generateChangelogText(newEntries, existingText, author = "github-actions\\[bot]") {
const packageVersion = require("../package.json").version;
const currentDateString = new Date().toISOString().split(/T/)[0];

const notableChanges = Array.from(newEntries.matchAll(/ (- [^(]+) \([^)]+\)( \[#\d+]\([^)]+\))?/g))
.map(matches => matches[1])
.join("\n");

return `# node-api-headers Changelog

## ${currentDateString} Version ${packageVersion}, ${author}

### Notable changes

${notableChanges}

### Commits

${newEntries.trim()}

${existingText.trim()}
`;
}

/**
* Throws an error (asynchronously) if there are uncommitted changes to the changelog file.
*
* @param {string} changelogPath Path to changelog file
* @returns {Promise<void>}
*/
function assertCleanChangelog(changelogPath) {
return new Promise((resolve, reject) => {
const spawned = spawn("git", ["diff", "--quiet", changelogPath]);
spawned.on('exit', function (exitCode) {
if (exitCode === 0) {
resolve(undefined);
} else {
reject(new Error(`There are uncommitted changes to ${changelogPath}. Commit, revert, or stash changes first.`));
}
});

spawned.on('error', function (err) {
reject(err);
});
});
}

async function main() {
const changelogPath = resolvePath(__dirname, "..", "CHANGELOG.md");
await assertCleanChangelog(changelogPath);
const [{ stdout: newEntires, stderr }, existingText] = await Promise.all([getNewChangelogEntries(), getExistingChangelogText(changelogPath)]);
const changelogText = generateChangelogText(newEntires, existingText);

await writeFile(changelogPath, changelogText);
if (stderr) {
console.error("stderr from changelog-maker:\n", stderr)
}
console.log(`Changelog written to ${changelogPath}`);
}

main().catch(e => {
console.error(e);
process.exitCode = 1;
});
2 changes: 1 addition & 1 deletion scripts/update-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ async function main() {
},
});

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

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

Expand Down