ci: Create github releases from mjs scripts (#27121)

This commit is contained in:
Matsu 2026-03-18 11:19:31 +02:00 committed by GitHub
parent 8995c25512
commit 927b7bc818
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 329 additions and 29 deletions

View File

@ -0,0 +1,81 @@
import {
deleteRelease,
ensureEnvVar,
getExistingRelease,
initGithub,
isReleaseTrack,
writeGithubOutput,
} from './github-helpers.mjs';
/**
* Creates release in GitHub.
*
* Required env variables:
* - RELEASE_TAG - Release tag on git e.g. n8n@2.13.0
* - BODY - Body of the release. Contains release notes etc.
* - IS_PRE_RELEASE - If releasing in pre-release. Currently only for beta track.
* - MAKE_LATEST - If released version should be marked as latest on GitHub
* - COMMIT - Commitish for release to point to
*
* Optional env variables:
* - ADDITIONAL_TAGS - Comma-separated list of additional tags to release under e.g. beta
*
* GitHub variables
* - GITHUB_TOKEN - Used to authenticate to octokit - Can be overwritten for privileged access
* - GITHUB_REPOSITORY - Used to determine target repository
* */
async function createGitHubRelease() {
const RELEASE_TAG = ensureEnvVar('RELEASE_TAG');
const ADDITIONAL_TAGS = process.env.ADDITIONAL_TAGS ?? '';
const BODY = ensureEnvVar('BODY');
const IS_PRE_RELEASE = ensureEnvVar('IS_PRE_RELEASE');
const MAKE_LATEST = ensureEnvVar('MAKE_LATEST');
const COMMIT = ensureEnvVar('COMMIT');
const { octokit, owner, repo } = initGithub();
const allTags = [
RELEASE_TAG,
...ADDITIONAL_TAGS.split(',')
.map((t) => t.trim())
.filter(Boolean),
];
const releases = [];
for (const tag of allTags) {
const existingRelease = await getExistingRelease(tag);
const isReleaseTrackTag = isReleaseTrack(tag);
// If we have an existing track release, we want to
// delete the old release before pushing a new one.
if (isReleaseTrackTag && existingRelease) {
await deleteRelease(existingRelease.id);
}
const releaseResponse = await octokit.rest.repos.createRelease({
tag_name: tag,
name: tag,
body: BODY,
draft: false,
prerelease: IS_PRE_RELEASE === 'true',
make_latest: MAKE_LATEST === 'true' ? 'true' : 'false',
target_commitish: COMMIT,
owner,
repo,
});
const release = releaseResponse.data;
releases.push(release);
console.log(`Successfully created release ${release.html_url}`);
}
writeGithubOutput({
release_urls: releases.map((release) => release.html_url).join(', '),
});
}
// only run when executed directly, not when imported by tests
if (import.meta.url === `file://${process.argv[1]}`) {
createGitHubRelease();
}

View File

@ -74,7 +74,9 @@ export function determineTrack(packageVersion) {
writeGithubOutput(output);
console.log(
`Determined track info: track=${track}, version=${packageVersion}, previous_version=${previousVersion}, new_stable_version=${newStable}, release_type=${releaseType}, rc_branch=${rc_branch}`,
`Determined track info: ${Object.entries(output)
.map(([key, val]) => `${key}=${val}`)
.join(', ')}`,
);
return output;

View File

@ -14,6 +14,10 @@ export const RELEASE_TRACKS = /** @type { const } */ ([
'v1',
]);
/**
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
* */
/**
* @typedef {typeof RELEASE_TRACKS[number]} ReleaseTrack
* */
@ -50,6 +54,15 @@ export function pickHighestReleaseTag(tags) {
return /** @type { ReleaseVersion } */ (versions[0]?.tag) ?? null;
}
/**
* @param {any} track
*
* @returns { track is ReleaseTrack }
* */
export function isReleaseTrack(track) {
return RELEASE_TRACKS.includes(track);
}
/**
* @param {any} track
*
@ -337,3 +350,37 @@ export async function getPullRequestById(pullRequestId) {
return pullRequest.data;
}
/**
* @param {string} tag
*/
export async function getExistingRelease(tag) {
const { octokit, owner, repo } = initGithub();
try {
const releaseRequest = await octokit.rest.repos.getReleaseByTag({
owner,
repo,
tag,
});
return releaseRequest.data;
} catch (ex) {
if (ex?.status === 404) {
return undefined;
}
throw ex;
}
}
/**
* @param {number} releaseId
*/
export async function deleteRelease(releaseId) {
const { octokit, owner, repo } = initGithub();
await octokit.rest.repos.deleteRelease({
owner,
repo,
release_id: releaseId,
});
}

View File

@ -3,7 +3,7 @@
"module": "esnext",
"target": "esnext",
"checkJs": true,
"moduleResolution": "node"
"moduleResolution": "bundler"
},
"exclude": ["node_modules"]
}

View File

@ -5,6 +5,7 @@
},
"dependencies": {
"@actions/github": "9.0.0",
"@octokit/core": "7.0.6",
"conventional-changelog": "7.2.0",
"debug": "4.4.3",
"glob": "13.0.6",
@ -12,6 +13,6 @@
"tempfile": "6.0.1"
},
"devDependencies": {
"conventional-changelog-angular": "^8.3.0"
"conventional-changelog-angular": "8.3.0"
}
}

View File

@ -11,6 +11,9 @@ importers:
'@actions/github':
specifier: 9.0.0
version: 9.0.0
'@octokit/core':
specifier: 7.0.6
version: 7.0.6
conventional-changelog:
specifier: 7.2.0
version: 7.2.0(conventional-commits-filter@5.0.0)
@ -28,7 +31,7 @@ importers:
version: 6.0.1
devDependencies:
conventional-changelog-angular:
specifier: ^8.3.0
specifier: 8.3.0
version: 8.3.0
packages:

View File

@ -0,0 +1,45 @@
import {
ensureEnvVar,
getExistingRelease,
initGithub,
writeGithubOutput,
} from './github-helpers.mjs';
/**
* Promotes a GitHub release to latest
*
* Required env variables:
* - RELEASE_TAG - Release tag on git e.g. n8n@2.13.0
*
* GitHub variables
* - GITHUB_TOKEN - Used to authenticate to octokit - Can be overwritten for privileged access
* - GITHUB_REPOSITORY - Used to determine target repository
* */
async function promoteGitHubRelease() {
const RELEASE_TAG = ensureEnvVar('RELEASE_TAG');
const { octokit, owner, repo } = initGithub();
const existingRelease = await getExistingRelease(RELEASE_TAG);
if (!existingRelease) {
console.warn("Couldn't find release by tag. Exiting...");
process.exit(1);
}
const releaseResponse = await octokit.rest.repos.updateRelease({
owner,
repo,
release_id: existingRelease.id,
make_latest: 'true',
});
console.log(`Successfully updated release ${releaseResponse.data.html_url}`);
writeGithubOutput({
release_url: releaseResponse.data.html_url,
});
}
// only run when executed directly, not when imported by tests
if (import.meta.url === `file://${process.argv[1]}`) {
promoteGitHubRelease();
}

View File

@ -0,0 +1,78 @@
name: 'Release: Create GitHub Releases'
run-name: 'Creating GitHub Releases for ${{ inputs.version-tag }} (${{ inputs.track }})'
on:
workflow_call:
inputs:
track:
required: true
type: string
version-tag:
required: true
type: string
body:
required: true
type: string
commit:
required: true
type: string
workflow_dispatch:
inputs:
track:
description: 'Release Track'
required: true
type: choice
options: [stable, beta, v1]
version-tag:
description: 'Version tag (e.g. n8n@2.7.0).'
required: true
type: string
body:
description: 'Release notes body.'
required: true
type: string
commit:
description: 'Commitish the release points to (e.g. branch name or SHA).'
required: true
type: string
permissions:
contents: write
id-token: write
jobs:
create-github-releases:
name: Create GitHub releases
runs-on: ubuntu-slim
environment: release
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.N8N_ASSISTANT_APP_ID }}
private-key: ${{ secrets.N8N_ASSISTANT_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ steps.generate_token.outputs.token }}
fetch-depth: 1
- name: Setup NodeJS
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
- name: Create GitHub releases
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
RELEASE_TAG: ${{ inputs.version-tag }}
BODY: ${{ inputs.body }}
IS_PRE_RELEASE: ${{ inputs.track == 'beta' }}
MAKE_LATEST: ${{ inputs.track == 'stable' }}
COMMIT: ${{ inputs.commit }}
ADDITIONAL_TAGS: ${{ inputs.track }}
run: node ./.github/scripts/create-github-release.mjs

View File

@ -0,0 +1,50 @@
name: 'Release: Promote GitHub Releases'
run-name: 'Promoting GitHub Release ${{ inputs.version-tag }} to latest'
on:
workflow_call:
inputs:
version-tag:
required: true
type: string
workflow_dispatch:
inputs:
version-tag:
description: 'Version tag (e.g. n8n@2.7.0).'
required: true
type: string
permissions:
contents: write
jobs:
promote-github-releases:
name: Promote GitHub release to latest
runs-on: ubuntu-slim
environment: release
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.N8N_ASSISTANT_APP_ID }}
private-key: ${{ secrets.N8N_ASSISTANT_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ steps.generate_token.outputs.token }}
fetch-depth: 1
- name: Setup NodeJS
uses: ./.github/actions/setup-nodejs
with:
build-command: ''
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
- name: Promote GitHub releases
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
RELEASE_TAG: ${{ inputs.version-tag }}
run: node ./.github/scripts/promote-github-release.mjs

View File

@ -49,6 +49,16 @@ jobs:
version: ${{ inputs.new_stable_version }}
release-channel: stable
promote-previous-minor-github-release-to-latest:
name: Promote previous minor Github Release to latest
if: |
inputs.release_type != 'rc' &&
inputs.bump == 'minor'
uses: ./.github/workflows/release-promote-github-release.yml
secrets: inherit
with:
version-tag: 'n8n@${{ inputs.new_stable_version }}'
ensure-release-candidate-branches:
name: 'Ensure release candidate branches'
if: |

View File

@ -97,33 +97,16 @@ jobs:
secrets: inherit
create-github-release:
name: Create a GitHub Release
name: Create GitHub Release
needs: [determine-version-info, publish-to-npm, publish-to-docker-hub]
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
timeout-minutes: 5
permissions:
contents: write
id-token: write
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.N8N_ASSISTANT_APP_ID }}
private-key: ${{ secrets.N8N_ASSISTANT_PRIVATE_KEY }}
- name: Create a Release on GitHub
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
with:
token: ${{ steps.generate_token.outputs.token }}
commit: ${{github.event.pull_request.base.ref}}
tag: 'n8n@${{ needs.determine-version-info.outputs.version }}'
prerelease: ${{ needs.determine-version-info.outputs.track == 'beta' }}
makeLatest: ${{ needs.determine-version-info.outputs.track == 'stable' }}
body: ${{github.event.pull_request.body}}
uses: ./.github/workflows/release-create-github-releases.yml
with:
track: ${{ needs.determine-version-info.outputs.track }}
version-tag: 'n8n@${{ needs.determine-version-info.outputs.version }}'
body: ${{ github.event.pull_request.body }}
commit: ${{ github.event.pull_request.base.ref }}
secrets: inherit
move-track-tag:
name: Move track tag