mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
ci: Allow manual execution of backport workflow (#26576)
This commit is contained in:
parent
b0a4d3db26
commit
7df0125b6d
39
.github/scripts/compute-backport-targets.mjs
vendored
39
.github/scripts/compute-backport-targets.mjs
vendored
|
|
@ -1,6 +1,11 @@
|
|||
// Creates backport PR's according to labels on merged PR
|
||||
|
||||
import { readPrLabels, resolveRcBranchForTrack, writeGithubOutput } from './github-helpers.mjs';
|
||||
import {
|
||||
getPullRequestById,
|
||||
readPrLabels,
|
||||
resolveRcBranchForTrack,
|
||||
writeGithubOutput,
|
||||
} from './github-helpers.mjs';
|
||||
|
||||
/** @type { Record<string, import('./github-helpers.mjs').ReleaseTrack> } */
|
||||
const BACKPORT_BY_TAG_MAP = {
|
||||
|
|
@ -50,8 +55,38 @@ export function labelsToReleaseCandidateBranches(labels) {
|
|||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* This script is called in 2 cases:
|
||||
*
|
||||
* 1. When a PR is merged, in which case functions like `readPrLabels` reads PR info from GITHUB_EVENT_PATH
|
||||
* 2. Manually via Workflow Dispatch, where a Pull Request ID is passed as an env parameter
|
||||
*
|
||||
* @returns { Promise<undefined | any> } Pull request object, if ID was provided in env params
|
||||
*/
|
||||
async function fetchPossiblePullRequestFromEnv() {
|
||||
const pullRequestEnv = process.env.PULL_REQUEST_ID;
|
||||
if (!pullRequestEnv) {
|
||||
// No ID provided, will proceed to read data from GITHUB_EVENT_PATH
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const pullRequestNumber = parseInt(pullRequestEnv);
|
||||
if (isNaN(pullRequestNumber)) {
|
||||
throw new Error(
|
||||
"PULL_REQUEST_ID must be a number. It shouldn't contain any other symbols (#, PR, etc.)",
|
||||
);
|
||||
}
|
||||
|
||||
return await getPullRequestById(pullRequestNumber);
|
||||
}
|
||||
|
||||
export async function getLabels() {
|
||||
const pullRequest = await fetchPossiblePullRequestFromEnv();
|
||||
return new Set(readPrLabels(pullRequest));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const labels = new Set(readPrLabels());
|
||||
const labels = await getLabels();
|
||||
if (!labels || labels.size === 0) {
|
||||
console.log('No labels on PR. Exiting...');
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, it, mock, before } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readPrLabels } from './github-helpers.mjs';
|
||||
|
||||
/**
|
||||
* Run these tests by running
|
||||
|
|
@ -12,9 +13,14 @@ import assert from 'node:assert/strict';
|
|||
mock.module('./github-helpers.mjs', {
|
||||
namedExports: {
|
||||
ensureEnvVar: () => {}, // no-op
|
||||
readPrLabels: () => {}, // no-op
|
||||
readPrLabels: readPrLabels,
|
||||
resolveRcBranchForTrack: mockResolveRcBranchForTrack,
|
||||
writeGithubOutput: () => {}, //no-op
|
||||
getPullRequestById: () => {
|
||||
return {
|
||||
labels: ['n8n team', 'Backport to Beta'],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -28,9 +34,11 @@ function mockResolveRcBranchForTrack(track) {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
let labelsToReleaseCandidateBranches;
|
||||
let labelsToReleaseCandidateBranches, getLabels;
|
||||
before(async () => {
|
||||
({ labelsToReleaseCandidateBranches } = await import('./compute-backport-targets.mjs'));
|
||||
({ labelsToReleaseCandidateBranches, getLabels } = await import(
|
||||
'./compute-backport-targets.mjs'
|
||||
));
|
||||
});
|
||||
|
||||
describe('Compute backport targets', () => {
|
||||
|
|
@ -59,4 +67,38 @@ describe('Compute backport targets', () => {
|
|||
|
||||
assert.equal(result.size, 0);
|
||||
});
|
||||
|
||||
it('Should parse labels properly in Pull request context', async () => {
|
||||
process.env.GITHUB_EVENT_PATH = './fixtures/mock-github-event.json';
|
||||
/** @type { Set<string> } */
|
||||
const labels = await getLabels();
|
||||
|
||||
assert.equal(labels.size, 2);
|
||||
assert.ok(labels.has('release'));
|
||||
assert.ok(labels.has('Backport to Stable'));
|
||||
});
|
||||
it('Should parse labels properly in manual workflow context', async () => {
|
||||
process.env.PULL_REQUEST_ID = '123';
|
||||
/** @type { Set<string> } */
|
||||
const labels = await getLabels();
|
||||
|
||||
assert.equal(labels.size, 2);
|
||||
assert.ok(labels.has('n8n team'));
|
||||
assert.ok(labels.has('Backport to Beta'));
|
||||
});
|
||||
|
||||
it('Should throw when passed pull request id with #', async () => {
|
||||
process.env.PULL_REQUEST_ID = '#123';
|
||||
await assert.rejects(getLabels);
|
||||
});
|
||||
|
||||
it('Should not throw when passed pull request id with just a number', async () => {
|
||||
process.env.PULL_REQUEST_ID = '123';
|
||||
await assert.doesNotReject(getLabels);
|
||||
});
|
||||
|
||||
it('Should throw when passed pull request id with other than numbers included', async () => {
|
||||
process.env.PULL_REQUEST_ID = 'abc-123';
|
||||
await assert.rejects(getLabels);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
5
.github/scripts/fixtures/mock-github-event.json
vendored
Normal file
5
.github/scripts/fixtures/mock-github-event.json
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"pull_request": {
|
||||
"labels": ["release", "Backport to Stable"]
|
||||
}
|
||||
}
|
||||
48
.github/scripts/github-helpers.mjs
vendored
48
.github/scripts/github-helpers.mjs
vendored
|
|
@ -1,5 +1,7 @@
|
|||
import { getOctokit } from '@actions/github';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import semver from 'semver';
|
||||
|
||||
export const RELEASE_TRACKS = /** @type { const } */ ([
|
||||
|
|
@ -117,6 +119,14 @@ export function stripReleasePrefixes(tag) {
|
|||
);
|
||||
}
|
||||
|
||||
export function getEventFromGithubEventPath() {
|
||||
let eventPath = ensureEnvVar('GITHUB_EVENT_PATH');
|
||||
if (!path.isAbsolute(eventPath)) {
|
||||
eventPath = import.meta.dirname + '/' + eventPath;
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} [pullRequest] Optional pull request object. If not provided, reads from GITHUB_EVENT_PATH
|
||||
*
|
||||
|
|
@ -124,8 +134,7 @@ export function stripReleasePrefixes(tag) {
|
|||
*/
|
||||
export function readPrLabels(pullRequest) {
|
||||
if (!pullRequest) {
|
||||
const eventPath = ensureEnvVar('GITHUB_EVENT_PATH');
|
||||
const event = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
||||
const event = getEventFromGithubEventPath();
|
||||
pullRequest = event.pull_request;
|
||||
}
|
||||
/** @type { string[] | { name: string }[] } */
|
||||
|
|
@ -247,3 +256,38 @@ export function localRefExists(ref) {
|
|||
const res = trySh('git', ['show-ref', '--verify', '--quiet', ref]);
|
||||
return res.ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes octokit with GITHUB_TOKEN from env vars.
|
||||
*
|
||||
* Also ensures the existence of useful environment variables.
|
||||
* */
|
||||
export function initGithub() {
|
||||
const token = ensureEnvVar('GITHUB_TOKEN');
|
||||
const repoFullName = ensureEnvVar('GITHUB_REPOSITORY');
|
||||
|
||||
const [owner, repo] = repoFullName.split('/');
|
||||
|
||||
const octokit = getOctokit(token);
|
||||
|
||||
return {
|
||||
octokit,
|
||||
owner,
|
||||
repo,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pullRequestId
|
||||
*/
|
||||
export async function getPullRequestById(pullRequestId) {
|
||||
const { octokit, owner, repo } = initGithub();
|
||||
|
||||
const pullRequest = await octokit.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullRequestId,
|
||||
});
|
||||
|
||||
return pullRequest.data;
|
||||
}
|
||||
|
|
|
|||
14
.github/workflows/backport.yml
vendored
14
.github/workflows/backport.yml
vendored
|
|
@ -1,6 +1,12 @@
|
|||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pull-request-id:
|
||||
description: 'The ID number of the pull request (e.g. 3342). No #, no extra letters.'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
|
@ -8,7 +14,9 @@ permissions:
|
|||
|
||||
jobs:
|
||||
backport:
|
||||
if: github.event.pull_request.merged == true
|
||||
if: |
|
||||
github.event.pull_request.merged == true ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
|
@ -23,6 +31,8 @@ jobs:
|
|||
|
||||
- name: Compute backport targets
|
||||
id: targets
|
||||
env:
|
||||
PULL_REQUEST_ID: ${{ inputs.pull-request-id }}
|
||||
run: node .github/scripts/compute-backport-targets.mjs
|
||||
|
||||
- name: Backport
|
||||
|
|
@ -30,7 +40,7 @@ jobs:
|
|||
uses: korthout/backport-action@c656f5d5851037b2b38fb5db2691a03fa229e3b2 # v4.0.1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
source_pr_number: ${{ github.event.pull_request.number }}
|
||||
source_pr_number: ${{ github.event.pull_request.number || inputs.pull-request-id }}
|
||||
target_branches: ${{ steps.targets.outputs.target_branches }}
|
||||
pull_description: |-
|
||||
# Description
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user