Paul Gschwendtner f96dcc5ce0 feat(dev-infra): tool for staging and publishing releases (#38656)
Creates a tool for staging and publishing releases as per the
new branching and versioning that has been outlined in the following
document. The tool is intended to be used across the organization to
ensure consistent branching/versioning and labeling:

https://docs.google.com/document/d/197kVillDwx-RZtSVOBtPb4BBIAw0E9RT3q3v6DZkykU/edit#heading=h.s3qlps8f4zq7dd

The tool implements the actions as outlined in the following
initial plan: https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A.

The implementation slightly diverged in so far that it performs
staging and publishing together so that releasing is a single
convenient command. In case of errors for which re-running the
full command is not sufficient, we want to consider adding
recover functionality. e.g. when the staging completed, but the
actual NPM publishing aborted unexpectedly due to build errors.

PR Close #38656
2020-09-28 16:11:42 -04:00

94 lines
3.8 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ListChoiceOptions, prompt} from 'inquirer';
import * as semver from 'semver';
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
import {semverInc} from '../../versioning/inc-semver';
import {fetchLongTermSupportBranchesFromNpm} from '../../versioning/long-term-support';
import {ReleaseAction} from '../actions';
/** Interface describing an LTS version branch. */
interface LtsBranch {
/** Name of the branch. */
name: string;
/** Most recent version for the given LTS branch. */
version: semver.SemVer;
/** NPM dist tag for the LTS version. */
npmDistTag: string;
}
/**
* Release action that cuts a new patch release for an active release-train in the long-term
* support phase. The patch segment is incremented. The changelog is generated for the new
* patch version, but also needs to be cherry-picked into the next development branch.
*/
export class CutLongTermSupportPatchAction extends ReleaseAction {
/** Promise resolving an object describing long-term support branches. */
ltsBranches = fetchLongTermSupportBranchesFromNpm(this.config);
async getDescription() {
const {active} = await this.ltsBranches;
return `Cut a new release for an active LTS branch (${active.length} active).`;
}
async perform() {
const ltsBranch = await this._promptForTargetLtsBranch();
const newVersion = semverInc(ltsBranch.version, 'patch');
const {id} = await this.checkoutBranchAndStageVersion(newVersion, ltsBranch.name);
await this.waitForPullRequestToBeMerged(id);
await this.buildAndPublish(newVersion, ltsBranch.name, ltsBranch.npmDistTag);
await this.cherryPickChangelogIntoNextBranch(newVersion, ltsBranch.name);
}
/** Prompts the user to select an LTS branch for which a patch should but cut. */
private async _promptForTargetLtsBranch(): Promise<LtsBranch> {
const {active, inactive} = await this.ltsBranches;
const activeBranchChoices = active.map(branch => this._getChoiceForLtsBranch(branch));
// If there are inactive LTS branches, we allow them to be selected. In some situations,
// patch releases are still cut for inactive LTS branches. e.g. when the LTS duration
// has been increased due to exceptional events ()
if (inactive.length !== 0) {
activeBranchChoices.push({name: 'Inactive LTS versions (not recommended)', value: null});
}
const {activeLtsBranch, inactiveLtsBranch} =
await prompt<{activeLtsBranch: LtsBranch | null, inactiveLtsBranch: LtsBranch}>([
{
name: 'activeLtsBranch',
type: 'list',
message: 'Please select a version for which you want to cut a LTS patch',
choices: activeBranchChoices,
},
{
name: 'inactiveLtsBranch',
type: 'list',
when: o => o.activeLtsBranch === null,
message: 'Please select an inactive LTS version for which you want to cut a LTS patch',
choices: inactive.map(branch => this._getChoiceForLtsBranch(branch)),
}
]);
return activeLtsBranch ?? inactiveLtsBranch;
}
/** Gets an inquirer choice for the given LTS branch. */
private _getChoiceForLtsBranch(branch: LtsBranch): ListChoiceOptions {
return {name: `v${branch.version.major} (from ${branch.name})`, value: branch};
}
static async isActive(active: ActiveReleaseTrains) {
// LTS patch versions can be only cut if there are release trains in LTS phase.
// This action is always selectable as we support publishing of old LTS branches,
// and have prompt for selecting an LTS branch when the action performs.
return true;
}
}