56b0e756d6
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
186 lines
4.7 KiB
JavaScript
Executable File
186 lines
4.7 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/*
|
|
* Copyright 2021 The Backstage Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
const fs = require('fs-extra');
|
|
const path = require('path');
|
|
const semver = require('semver');
|
|
const { Octokit } = require('@octokit/rest');
|
|
const { execFile: execFileCb } = require('child_process');
|
|
const { promisify } = require('util');
|
|
|
|
const execFile = promisify(execFileCb);
|
|
|
|
const owner = 'backstage';
|
|
const repo = 'backstage';
|
|
const rootDir = path.resolve(__dirname, '..');
|
|
|
|
const octokit = new Octokit({
|
|
auth: process.env.GITHUB_TOKEN,
|
|
});
|
|
|
|
async function run(command, ...args) {
|
|
const { stdout, stderr } = await execFile(command, args, {
|
|
cwd: rootDir,
|
|
});
|
|
|
|
if (stderr) {
|
|
console.error(stderr);
|
|
}
|
|
|
|
return stdout.trim();
|
|
}
|
|
|
|
/**
|
|
* Finds the current stable release version of the repo, looking at
|
|
* the current commit and backwards, finding the first commit were a
|
|
* stable version is present.
|
|
*/
|
|
async function findCurrentReleaseVersion() {
|
|
const rootPkgPath = path.resolve(rootDir, 'package.json');
|
|
const pkg = await fs.readJson(rootPkgPath);
|
|
|
|
if (!semver.prerelease(pkg.version)) {
|
|
return pkg.version;
|
|
}
|
|
|
|
const { stdout: revListStr } = await execFile('git', [
|
|
'rev-list',
|
|
'HEAD',
|
|
'--',
|
|
'package.json',
|
|
]);
|
|
const revList = revListStr.trim().split(/\r?\n/);
|
|
|
|
for (const rev of revList) {
|
|
const { stdout: pkgJsonStr } = await execFile('git', [
|
|
'show',
|
|
`${rev}:package.json`,
|
|
]);
|
|
if (pkgJsonStr) {
|
|
const pkgJson = JSON.parse(pkgJsonStr);
|
|
if (!semver.prerelease(pkgJson.version)) {
|
|
return pkgJson.version;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error('No stable release found');
|
|
}
|
|
|
|
async function main(args) {
|
|
const prNumbers = args.map(s => {
|
|
const num = parseInt(s, 10);
|
|
if (!Number.isInteger(num)) {
|
|
throw new Error(`Must provide valid PR number arguments, got ${s}`);
|
|
}
|
|
return num;
|
|
});
|
|
console.log(`PR number(s): ${prNumbers.join(', ')}`);
|
|
|
|
if (await run('git', 'status', '--porcelain')) {
|
|
throw new Error('Cannot run with a dirty working tree');
|
|
}
|
|
|
|
const release = await findCurrentReleaseVersion();
|
|
console.log(`Patching release ${release}`);
|
|
|
|
await run('git', 'fetch');
|
|
|
|
const patchBranch = `patch/v${release}`;
|
|
try {
|
|
await run('git', 'checkout', `origin/${patchBranch}`);
|
|
} catch {
|
|
await run('git', 'checkout', '-b', patchBranch, `v${release}`);
|
|
await run('git', 'push', 'origin', '-u', patchBranch);
|
|
}
|
|
|
|
// Create new branch, apply changes from all commits on PR branch, commit, push
|
|
const branchName = `patch-release-pr-${prNumbers.join('-')}`;
|
|
await run('git', 'checkout', '-b', branchName);
|
|
|
|
for (const prNumber of prNumbers) {
|
|
const { data } = await octokit.pulls.get({
|
|
owner,
|
|
repo,
|
|
pull_number: prNumber,
|
|
});
|
|
|
|
const headSha = data.head.sha;
|
|
if (!headSha) {
|
|
throw new Error('head sha not available');
|
|
}
|
|
const baseSha = data.base.sha;
|
|
if (!baseSha) {
|
|
throw new Error('base sha not available');
|
|
}
|
|
const mergeBaseSha = await run('git', 'merge-base', headSha, baseSha);
|
|
|
|
const logLines = await run(
|
|
'git',
|
|
'log',
|
|
`${mergeBaseSha}...${headSha}`,
|
|
'--reverse',
|
|
'--pretty=%H',
|
|
);
|
|
for (const logSha of logLines.split(/\r?\n/)) {
|
|
await run('git', 'cherry-pick', '-n', logSha);
|
|
}
|
|
await run(
|
|
'git',
|
|
'commit',
|
|
'--signoff',
|
|
'--no-verify',
|
|
'-m',
|
|
`Patch from PR #${prNumber}`,
|
|
);
|
|
}
|
|
|
|
console.log('Running "yarn install" ...');
|
|
await run('yarn', 'install');
|
|
|
|
console.log('Running "yarn release" ...');
|
|
await run('yarn', 'release');
|
|
|
|
await run('git', 'add', '.');
|
|
await run(
|
|
'git',
|
|
'commit',
|
|
'--signoff',
|
|
'--no-verify',
|
|
'-m',
|
|
'Generate Release',
|
|
);
|
|
|
|
await run('git', 'push', 'origin', '-u', branchName);
|
|
|
|
const params = new URLSearchParams({
|
|
expand: 1,
|
|
body: 'This release fixes an issue where',
|
|
title: `Patch release of ${prNumbers.map(nr => `#${nr}`).join(', ')}`,
|
|
});
|
|
|
|
const url = `https://github.com/backstage/backstage/compare/${patchBranch}...${branchName}?${params}`;
|
|
console.log(`Opening ${url} ...`);
|
|
|
|
await run('open', url);
|
|
}
|
|
|
|
main(process.argv.slice(2)).catch(error => {
|
|
console.error(error.stack || error);
|
|
process.exit(1);
|
|
});
|