n8n/.github/scripts/ensure-release-candidate-branches.mjs

144 lines
4.0 KiB
JavaScript

import semver from 'semver';
import {
getCommitForRef,
localRefExists,
RELEASE_CANDIDATE_BRANCH_PREFIX,
remoteBranchExists,
resolveReleaseTagForTrack,
sh,
tagVersionInfoToReleaseCandidateBranchName,
trySh,
writeGithubOutput,
} from './github-helpers.mjs';
/**
* @typedef BranchChanges
* @property { import('./github-helpers.mjs').TagVersionInfo[] } branchesToEnsure TagVersionInfo for branches the system needs to make sure exist
* @property { string[] } branchesToDeprecate Branches the system needs to remove as deprecated
* */
/**
* Look into git tags and determine which release candidate branches need to
* exist and which need to be deprecated and removed.
*
* @returns { BranchChanges }
* */
export function determineBranchChanges() {
const branchesToDeprecate = [];
const currentBetaVersion = resolveReleaseTagForTrack('beta');
const currentStableVersion = resolveReleaseTagForTrack('stable');
if (!currentBetaVersion || !currentStableVersion) {
throw new Error(
`Could not find current stable and/or beta tags. Beta: ${currentBetaVersion?.tag ?? 'not found'}, Stable: ${currentStableVersion?.tag ?? 'not found'}`,
);
}
const branchesToEnsure = [currentBetaVersion, currentStableVersion];
const stableVersion = currentStableVersion.version;
// Deprecated branch is the current stable minus 2 versions. e.g. stable: 2.9.x, deprecated is 2.7.x
const deprecatedMinorVersion = semver.minor(stableVersion) - 2;
if (deprecatedMinorVersion >= 0) {
const deprecatedBranch = `${RELEASE_CANDIDATE_BRANCH_PREFIX}${semver.major(stableVersion)}.${deprecatedMinorVersion}.x`;
branchesToDeprecate.push(deprecatedBranch);
}
return {
branchesToEnsure,
branchesToDeprecate,
};
}
/**
* @param {import("./github-helpers.mjs").TagVersionInfo} tagInfo
*/
function ensureBranch(tagInfo) {
const branch = tagVersionInfoToReleaseCandidateBranchName(tagInfo);
if (remoteBranchExists(branch)) {
console.log(`Branch ${branch} already exists on origin. Skipping.`);
return branch;
}
const commitRef = getCommitForRef(tagInfo.tag);
console.log(`Creating branch ${branch} from ${tagInfo.tag} (${commitRef})`);
// Create local branch (force safe: it shouldn't exist, but keep it robust)
if (localRefExists(`refs/heads/${branch}`)) {
sh('git', ['branch', '-f', branch, commitRef]);
} else {
sh('git', ['switch', '-c', branch, commitRef]);
}
sh('git', ['push', 'origin', branch]);
return branch;
}
/**
* @param {string} branch
*/
function removeBranch(branch) {
if (!remoteBranchExists(branch)) {
console.log(`Couldn't find branch ${branch}. Skipping removal.`);
return null;
}
console.log(`Removing remote branch ${branch} from origin...`);
// Delete remote branch
trySh('git', ['push', 'origin', '--delete', branch]);
// Optional local cleanup (keeps reruns tidy)
if (localRefExists(`refs/heads/${branch}`)) {
console.log(`Removing local branch ${branch}...`);
trySh('git', ['branch', '-D', branch]);
}
return branch;
}
function main() {
const branchChanges = determineBranchChanges();
console.log('💡 Determined branch changes');
console.log('');
console.log(
` Branches to ensure: ${branchChanges.branchesToEnsure.map(tagVersionInfoToReleaseCandidateBranchName).join(', ')}`,
);
console.log(` Branches to deprecate: ${branchChanges.branchesToDeprecate.join(', ')}`);
console.log('');
console.log('Preparing to apply changes...');
let ensuredBranches = [];
for (const tagInfo of branchChanges.branchesToEnsure) {
const branch = ensureBranch(tagInfo);
ensuredBranches.push(branch);
}
console.log('');
console.log('Starting deprecation of branches...');
let removedBranches = [];
for (const branch of branchChanges.branchesToDeprecate) {
const removedBranch = removeBranch(branch);
if (removedBranch) {
removedBranches.push(removedBranch);
}
}
console.log('Done!');
writeGithubOutput({
ensuredBranches: ensuredBranches.join(','),
removedBranches: removedBranches.join(','),
});
}
// only run when executed directly, not when imported by tests
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}