feat(dev-infra): add release notes generation to ng-dev (#42225)
Adds tooling to create ad-hoc release note entries via `ng-dev release notes`. PR Close #42225
This commit is contained in:
parent
a9584c929b
commit
594e63315e
|
@ -24,8 +24,8 @@ var cliProgress = require('cli-progress');
|
||||||
var os = require('os');
|
var os = require('os');
|
||||||
var shelljs = require('shelljs');
|
var shelljs = require('shelljs');
|
||||||
var minimatch = require('minimatch');
|
var minimatch = require('minimatch');
|
||||||
var ora = require('ora');
|
|
||||||
var ejs = require('ejs');
|
var ejs = require('ejs');
|
||||||
|
var ora = require('ora');
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
var ts = require('typescript');
|
var ts = require('typescript');
|
||||||
|
|
||||||
|
@ -5228,285 +5228,6 @@ const ReleaseBuildCommandModule = {
|
||||||
describe: 'Builds the release output for the current branch.',
|
describe: 'Builds the release output for the current branch.',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Spawns a given command with the specified arguments inside an interactive shell. All process
|
|
||||||
* stdin, stdout and stderr output is printed to the current console.
|
|
||||||
*
|
|
||||||
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
|
||||||
*/
|
|
||||||
function spawnInteractiveCommand(command, args, options) {
|
|
||||||
if (options === void 0) { options = {}; }
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
var commandText = command + " " + args.join(' ');
|
|
||||||
debug("Executing command: " + commandText);
|
|
||||||
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'inherit' }));
|
|
||||||
childProcess.on('exit', function (status) { return status === 0 ? resolve() : reject(status); });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Spawns a given command with the specified arguments inside a shell. All process stdout
|
|
||||||
* output is captured and returned as resolution on completion. Depending on the chosen
|
|
||||||
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
|
||||||
*
|
|
||||||
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
|
||||||
* rejects on command failure.
|
|
||||||
*/
|
|
||||||
function spawnWithDebugOutput(command, args, options) {
|
|
||||||
if (options === void 0) { options = {}; }
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
var commandText = command + " " + args.join(' ');
|
|
||||||
var outputMode = options.mode;
|
|
||||||
debug("Executing command: " + commandText);
|
|
||||||
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'pipe' }));
|
|
||||||
var logOutput = '';
|
|
||||||
var stdout = '';
|
|
||||||
var stderr = '';
|
|
||||||
// Capture the stdout separately so that it can be passed as resolve value.
|
|
||||||
// This is useful if commands return parsable stdout.
|
|
||||||
childProcess.stderr.on('data', function (message) {
|
|
||||||
stderr += message;
|
|
||||||
logOutput += message;
|
|
||||||
// If console output is enabled, print the message directly to the stderr. Note that
|
|
||||||
// we intentionally print all output to stderr as stdout should not be polluted.
|
|
||||||
if (outputMode === undefined || outputMode === 'enabled') {
|
|
||||||
process.stderr.write(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
childProcess.stdout.on('data', function (message) {
|
|
||||||
stdout += message;
|
|
||||||
logOutput += message;
|
|
||||||
// If console output is enabled, print the message directly to the stderr. Note that
|
|
||||||
// we intentionally print all output to stderr as stdout should not be polluted.
|
|
||||||
if (outputMode === undefined || outputMode === 'enabled') {
|
|
||||||
process.stderr.write(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
childProcess.on('exit', function (status, signal) {
|
|
||||||
var exitDescription = status !== null ? "exit code \"" + status + "\"" : "signal \"" + signal + "\"";
|
|
||||||
var printFn = outputMode === 'on-error' ? error : debug;
|
|
||||||
printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
|
|
||||||
printFn("Process output: \n" + logOutput);
|
|
||||||
// On success, resolve the promise. Otherwise reject with the captured stderr
|
|
||||||
// and stdout log output if the output mode was set to `silent`.
|
|
||||||
if (status === 0) {
|
|
||||||
resolve({ stdout: stdout, stderr: stderr });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reject(outputMode === 'silent' ? logOutput : undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Runs NPM publish within a specified package directory.
|
|
||||||
* @throws With the process log output if the publish failed.
|
|
||||||
*/
|
|
||||||
function runNpmPublish(packagePath, distTag, registryUrl) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const args = ['publish', '--access', 'public', '--tag', distTag];
|
|
||||||
// If a custom registry URL has been specified, add the `--registry` flag.
|
|
||||||
if (registryUrl !== undefined) {
|
|
||||||
args.push('--registry', registryUrl);
|
|
||||||
}
|
|
||||||
yield spawnWithDebugOutput('npm', args, { cwd: packagePath, mode: 'silent' });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets the NPM tag to the specified version for the given package.
|
|
||||||
* @throws With the process log output if the tagging failed.
|
|
||||||
*/
|
|
||||||
function setNpmTagForPackage(packageName, distTag, version, registryUrl) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const args = ['dist-tag', 'add', `${packageName}@${version}`, distTag];
|
|
||||||
// If a custom registry URL has been specified, add the `--registry` flag.
|
|
||||||
if (registryUrl !== undefined) {
|
|
||||||
args.push('--registry', registryUrl);
|
|
||||||
}
|
|
||||||
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks whether the user is currently logged into NPM.
|
|
||||||
* @returns Whether the user is currently logged into NPM.
|
|
||||||
*/
|
|
||||||
function npmIsLoggedIn(registryUrl) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const args = ['whoami'];
|
|
||||||
// If a custom registry URL has been specified, add the `--registry` flag.
|
|
||||||
if (registryUrl !== undefined) {
|
|
||||||
args.push('--registry', registryUrl);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Log into NPM at a provided registry.
|
|
||||||
* @throws With the `npm login` status code if the login failed.
|
|
||||||
*/
|
|
||||||
function npmLogin(registryUrl) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const args = ['login', '--no-browser'];
|
|
||||||
// If a custom registry URL has been specified, add the `--registry` flag. The `--registry` flag
|
|
||||||
// must be spliced into the correct place in the command as npm expects it to be the flag
|
|
||||||
// immediately following the login subcommand.
|
|
||||||
if (registryUrl !== undefined) {
|
|
||||||
args.splice(1, 0, '--registry', registryUrl);
|
|
||||||
}
|
|
||||||
// The login command prompts for username, password and other profile information. Hence
|
|
||||||
// the process needs to be interactive (i.e. respecting current TTYs stdin).
|
|
||||||
yield spawnInteractiveCommand('npm', args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Log out of NPM at a provided registry.
|
|
||||||
* @returns Whether the user was logged out of NPM.
|
|
||||||
*/
|
|
||||||
function npmLogout(registryUrl) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const args = ['logout'];
|
|
||||||
// If a custom registry URL has been specified, add the `--registry` flag. The `--registry` flag
|
|
||||||
// must be spliced into the correct place in the command as npm expects it to be the flag
|
|
||||||
// immediately following the logout subcommand.
|
|
||||||
if (registryUrl !== undefined) {
|
|
||||||
args.splice(1, 0, '--registry', registryUrl);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
return npmIsLoggedIn(registryUrl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Prints the active release trains to the console.
|
|
||||||
* @params active Active release trains that should be printed.
|
|
||||||
* @params config Release configuration used for querying NPM on published versions.
|
|
||||||
*/
|
|
||||||
function printActiveReleaseTrains(active, config) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const { releaseCandidate, next, latest } = active;
|
|
||||||
const isNextPublishedToNpm = yield isVersionPublishedToNpm(next.version, config);
|
|
||||||
const nextTrainType = next.isMajor ? 'major' : 'minor';
|
|
||||||
const ltsBranches = yield fetchLongTermSupportBranchesFromNpm(config);
|
|
||||||
info();
|
|
||||||
info(blue('Current version branches in the project:'));
|
|
||||||
// Print information for release trains in the feature-freeze/release-candidate phase.
|
|
||||||
if (releaseCandidate !== null) {
|
|
||||||
const rcVersion = releaseCandidate.version;
|
|
||||||
const rcTrainType = releaseCandidate.isMajor ? 'major' : 'minor';
|
|
||||||
const rcTrainPhase = rcVersion.prerelease[0] === 'next' ? 'feature-freeze' : 'release-candidate';
|
|
||||||
info(` • ${bold(releaseCandidate.branchName)} contains changes for an upcoming ` +
|
|
||||||
`${rcTrainType} that is currently in ${bold(rcTrainPhase)} phase.`);
|
|
||||||
info(` Most recent pre-release for this branch is "${bold(`v${rcVersion}`)}".`);
|
|
||||||
}
|
|
||||||
// Print information about the release-train in the latest phase. i.e. the patch branch.
|
|
||||||
info(` • ${bold(latest.branchName)} contains changes for the most recent patch.`);
|
|
||||||
info(` Most recent patch version for this branch is "${bold(`v${latest.version}`)}".`);
|
|
||||||
// Print information about the release-train in the next phase.
|
|
||||||
info(` • ${bold(next.branchName)} contains changes for a ${nextTrainType} ` +
|
|
||||||
`currently in active development.`);
|
|
||||||
// Note that there is a special case for versions in the next release-train. The version in
|
|
||||||
// the next branch is not always published to NPM. This can happen when we recently branched
|
|
||||||
// off for a feature-freeze release-train. More details are in the next pre-release action.
|
|
||||||
if (isNextPublishedToNpm) {
|
|
||||||
info(` Most recent pre-release version for this branch is "${bold(`v${next.version}`)}".`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
info(` Version is currently set to "${bold(`v${next.version}`)}", but has not been ` +
|
|
||||||
`published yet.`);
|
|
||||||
}
|
|
||||||
// If no release-train in release-candidate or feature-freeze phase is active,
|
|
||||||
// we print a message as last bullet point to make this clear.
|
|
||||||
if (releaseCandidate === null) {
|
|
||||||
info(' • No release-candidate or feature-freeze branch currently active.');
|
|
||||||
}
|
|
||||||
info();
|
|
||||||
info(blue('Current active LTS version branches:'));
|
|
||||||
// Print all active LTS branches (each branch as own bullet point).
|
|
||||||
if (ltsBranches.active.length !== 0) {
|
|
||||||
for (const ltsBranch of ltsBranches.active) {
|
|
||||||
info(` • ${bold(ltsBranch.name)} is currently in active long-term support phase.`);
|
|
||||||
info(` Most recent patch version for this branch is "${bold(`v${ltsBranch.version}`)}".`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/** Error that will be thrown if the user manually aborted a release action. */
|
|
||||||
class UserAbortedReleaseActionError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
|
|
||||||
// a limitation in down-leveling.
|
|
||||||
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
|
|
||||||
Object.setPrototypeOf(this, UserAbortedReleaseActionError.prototype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Error that will be thrown if the action has been aborted due to a fatal error. */
|
|
||||||
class FatalReleaseActionError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
|
|
||||||
// a limitation in down-leveling.
|
|
||||||
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
|
|
||||||
Object.setPrototypeOf(this, FatalReleaseActionError.prototype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Increments a specified SemVer version. Compared to the original increment in SemVer,
|
|
||||||
* the version is cloned to not modify the original version instance.
|
|
||||||
*/
|
|
||||||
function semverInc(version, release, identifier) {
|
|
||||||
const clone = new semver.SemVer(version.version);
|
|
||||||
return clone.inc(release, identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright Google LLC All Rights Reserved.
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
@ -5803,12 +5524,6 @@ _%>
|
||||||
_%>
|
_%>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/** Project-relative path for the changelog file. */
|
|
||||||
const changelogPath = 'CHANGELOG.md';
|
|
||||||
/** Gets the path for the changelog file in a given project. */
|
|
||||||
function getLocalChangelogFilePath(projectDir) {
|
|
||||||
return path.join(projectDir, changelogPath);
|
|
||||||
}
|
|
||||||
/** Release note generation. */
|
/** Release note generation. */
|
||||||
class ReleaseNotes {
|
class ReleaseNotes {
|
||||||
constructor(version, startingRef, endingRef) {
|
constructor(version, startingRef, endingRef) {
|
||||||
|
@ -5884,6 +5599,346 @@ class ReleaseNotes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/** Yargs command builder for configuring the `ng-dev release build` command. */
|
||||||
|
function builder$8(argv) {
|
||||||
|
return argv
|
||||||
|
.option('releaseVersion', { type: 'string', default: '0.0.0', coerce: (version) => new semver.SemVer(version) })
|
||||||
|
.option('from', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The git tag or ref to start the changelog entry from',
|
||||||
|
defaultDescription: 'The latest semver tag',
|
||||||
|
})
|
||||||
|
.option('to', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The git tag or ref to end the changelog entry with',
|
||||||
|
default: 'HEAD',
|
||||||
|
})
|
||||||
|
.option('type', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The type of release notes to create',
|
||||||
|
choices: ['github-release', 'changelog'],
|
||||||
|
default: 'changelog',
|
||||||
|
})
|
||||||
|
.option('outFile', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'File location to write the generated release notes to',
|
||||||
|
coerce: (filePath) => filePath ? path.join(process.cwd(), filePath) : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/** Yargs command handler for generating release notes. */
|
||||||
|
function handler$8({ releaseVersion, from, to, outFile, type }) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
// Since `yargs` evaluates defaults even if a value as been provided, if no value is provided to
|
||||||
|
// the handler, the latest semver tag on the branch is used.
|
||||||
|
from = from || GitClient.getInstance().getLatestSemverTag().format();
|
||||||
|
/** The ReleaseNotes instance to generate release notes. */
|
||||||
|
const releaseNotes = yield ReleaseNotes.fromRange(releaseVersion, from, to);
|
||||||
|
/** The requested release notes entry. */
|
||||||
|
const releaseNotesEntry = yield (type === 'changelog' ? releaseNotes.getChangelogEntry() :
|
||||||
|
releaseNotes.getGithubReleaseEntry());
|
||||||
|
if (outFile) {
|
||||||
|
fs.writeFileSync(outFile, releaseNotesEntry);
|
||||||
|
info(`Generated release notes for "${releaseVersion}" written to ${outFile}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
process.stdout.write(releaseNotesEntry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/** CLI command module for generating release notes. */
|
||||||
|
const ReleaseNotesCommandModule = {
|
||||||
|
builder: builder$8,
|
||||||
|
handler: handler$8,
|
||||||
|
command: 'notes',
|
||||||
|
describe: 'Generate release notes',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Spawns a given command with the specified arguments inside an interactive shell. All process
|
||||||
|
* stdin, stdout and stderr output is printed to the current console.
|
||||||
|
*
|
||||||
|
* @returns a Promise resolving on success, and rejecting on command failure with the status code.
|
||||||
|
*/
|
||||||
|
function spawnInteractiveCommand(command, args, options) {
|
||||||
|
if (options === void 0) { options = {}; }
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var commandText = command + " " + args.join(' ');
|
||||||
|
debug("Executing command: " + commandText);
|
||||||
|
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'inherit' }));
|
||||||
|
childProcess.on('exit', function (status) { return status === 0 ? resolve() : reject(status); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Spawns a given command with the specified arguments inside a shell. All process stdout
|
||||||
|
* output is captured and returned as resolution on completion. Depending on the chosen
|
||||||
|
* output mode, stdout/stderr output is also printed to the console, or only on error.
|
||||||
|
*
|
||||||
|
* @returns a Promise resolving with captured stdout and stderr on success. The promise
|
||||||
|
* rejects on command failure.
|
||||||
|
*/
|
||||||
|
function spawnWithDebugOutput(command, args, options) {
|
||||||
|
if (options === void 0) { options = {}; }
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var commandText = command + " " + args.join(' ');
|
||||||
|
var outputMode = options.mode;
|
||||||
|
debug("Executing command: " + commandText);
|
||||||
|
var childProcess = child_process.spawn(command, args, tslib.__assign(tslib.__assign({}, options), { shell: true, stdio: 'pipe' }));
|
||||||
|
var logOutput = '';
|
||||||
|
var stdout = '';
|
||||||
|
var stderr = '';
|
||||||
|
// Capture the stdout separately so that it can be passed as resolve value.
|
||||||
|
// This is useful if commands return parsable stdout.
|
||||||
|
childProcess.stderr.on('data', function (message) {
|
||||||
|
stderr += message;
|
||||||
|
logOutput += message;
|
||||||
|
// If console output is enabled, print the message directly to the stderr. Note that
|
||||||
|
// we intentionally print all output to stderr as stdout should not be polluted.
|
||||||
|
if (outputMode === undefined || outputMode === 'enabled') {
|
||||||
|
process.stderr.write(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
childProcess.stdout.on('data', function (message) {
|
||||||
|
stdout += message;
|
||||||
|
logOutput += message;
|
||||||
|
// If console output is enabled, print the message directly to the stderr. Note that
|
||||||
|
// we intentionally print all output to stderr as stdout should not be polluted.
|
||||||
|
if (outputMode === undefined || outputMode === 'enabled') {
|
||||||
|
process.stderr.write(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
childProcess.on('exit', function (status, signal) {
|
||||||
|
var exitDescription = status !== null ? "exit code \"" + status + "\"" : "signal \"" + signal + "\"";
|
||||||
|
var printFn = outputMode === 'on-error' ? error : debug;
|
||||||
|
printFn("Command \"" + commandText + "\" completed with " + exitDescription + ".");
|
||||||
|
printFn("Process output: \n" + logOutput);
|
||||||
|
// On success, resolve the promise. Otherwise reject with the captured stderr
|
||||||
|
// and stdout log output if the output mode was set to `silent`.
|
||||||
|
if (status === 0) {
|
||||||
|
resolve({ stdout: stdout, stderr: stderr });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(outputMode === 'silent' ? logOutput : undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Runs NPM publish within a specified package directory.
|
||||||
|
* @throws With the process log output if the publish failed.
|
||||||
|
*/
|
||||||
|
function runNpmPublish(packagePath, distTag, registryUrl) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const args = ['publish', '--access', 'public', '--tag', distTag];
|
||||||
|
// If a custom registry URL has been specified, add the `--registry` flag.
|
||||||
|
if (registryUrl !== undefined) {
|
||||||
|
args.push('--registry', registryUrl);
|
||||||
|
}
|
||||||
|
yield spawnWithDebugOutput('npm', args, { cwd: packagePath, mode: 'silent' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets the NPM tag to the specified version for the given package.
|
||||||
|
* @throws With the process log output if the tagging failed.
|
||||||
|
*/
|
||||||
|
function setNpmTagForPackage(packageName, distTag, version, registryUrl) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const args = ['dist-tag', 'add', `${packageName}@${version}`, distTag];
|
||||||
|
// If a custom registry URL has been specified, add the `--registry` flag.
|
||||||
|
if (registryUrl !== undefined) {
|
||||||
|
args.push('--registry', registryUrl);
|
||||||
|
}
|
||||||
|
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks whether the user is currently logged into NPM.
|
||||||
|
* @returns Whether the user is currently logged into NPM.
|
||||||
|
*/
|
||||||
|
function npmIsLoggedIn(registryUrl) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const args = ['whoami'];
|
||||||
|
// If a custom registry URL has been specified, add the `--registry` flag.
|
||||||
|
if (registryUrl !== undefined) {
|
||||||
|
args.push('--registry', registryUrl);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Log into NPM at a provided registry.
|
||||||
|
* @throws With the `npm login` status code if the login failed.
|
||||||
|
*/
|
||||||
|
function npmLogin(registryUrl) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const args = ['login', '--no-browser'];
|
||||||
|
// If a custom registry URL has been specified, add the `--registry` flag. The `--registry` flag
|
||||||
|
// must be spliced into the correct place in the command as npm expects it to be the flag
|
||||||
|
// immediately following the login subcommand.
|
||||||
|
if (registryUrl !== undefined) {
|
||||||
|
args.splice(1, 0, '--registry', registryUrl);
|
||||||
|
}
|
||||||
|
// The login command prompts for username, password and other profile information. Hence
|
||||||
|
// the process needs to be interactive (i.e. respecting current TTYs stdin).
|
||||||
|
yield spawnInteractiveCommand('npm', args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Log out of NPM at a provided registry.
|
||||||
|
* @returns Whether the user was logged out of NPM.
|
||||||
|
*/
|
||||||
|
function npmLogout(registryUrl) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const args = ['logout'];
|
||||||
|
// If a custom registry URL has been specified, add the `--registry` flag. The `--registry` flag
|
||||||
|
// must be spliced into the correct place in the command as npm expects it to be the flag
|
||||||
|
// immediately following the logout subcommand.
|
||||||
|
if (registryUrl !== undefined) {
|
||||||
|
args.splice(1, 0, '--registry', registryUrl);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
yield spawnWithDebugOutput('npm', args, { mode: 'silent' });
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
return npmIsLoggedIn(registryUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Prints the active release trains to the console.
|
||||||
|
* @params active Active release trains that should be printed.
|
||||||
|
* @params config Release configuration used for querying NPM on published versions.
|
||||||
|
*/
|
||||||
|
function printActiveReleaseTrains(active, config) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const { releaseCandidate, next, latest } = active;
|
||||||
|
const isNextPublishedToNpm = yield isVersionPublishedToNpm(next.version, config);
|
||||||
|
const nextTrainType = next.isMajor ? 'major' : 'minor';
|
||||||
|
const ltsBranches = yield fetchLongTermSupportBranchesFromNpm(config);
|
||||||
|
info();
|
||||||
|
info(blue('Current version branches in the project:'));
|
||||||
|
// Print information for release trains in the feature-freeze/release-candidate phase.
|
||||||
|
if (releaseCandidate !== null) {
|
||||||
|
const rcVersion = releaseCandidate.version;
|
||||||
|
const rcTrainType = releaseCandidate.isMajor ? 'major' : 'minor';
|
||||||
|
const rcTrainPhase = rcVersion.prerelease[0] === 'next' ? 'feature-freeze' : 'release-candidate';
|
||||||
|
info(` • ${bold(releaseCandidate.branchName)} contains changes for an upcoming ` +
|
||||||
|
`${rcTrainType} that is currently in ${bold(rcTrainPhase)} phase.`);
|
||||||
|
info(` Most recent pre-release for this branch is "${bold(`v${rcVersion}`)}".`);
|
||||||
|
}
|
||||||
|
// Print information about the release-train in the latest phase. i.e. the patch branch.
|
||||||
|
info(` • ${bold(latest.branchName)} contains changes for the most recent patch.`);
|
||||||
|
info(` Most recent patch version for this branch is "${bold(`v${latest.version}`)}".`);
|
||||||
|
// Print information about the release-train in the next phase.
|
||||||
|
info(` • ${bold(next.branchName)} contains changes for a ${nextTrainType} ` +
|
||||||
|
`currently in active development.`);
|
||||||
|
// Note that there is a special case for versions in the next release-train. The version in
|
||||||
|
// the next branch is not always published to NPM. This can happen when we recently branched
|
||||||
|
// off for a feature-freeze release-train. More details are in the next pre-release action.
|
||||||
|
if (isNextPublishedToNpm) {
|
||||||
|
info(` Most recent pre-release version for this branch is "${bold(`v${next.version}`)}".`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
info(` Version is currently set to "${bold(`v${next.version}`)}", but has not been ` +
|
||||||
|
`published yet.`);
|
||||||
|
}
|
||||||
|
// If no release-train in release-candidate or feature-freeze phase is active,
|
||||||
|
// we print a message as last bullet point to make this clear.
|
||||||
|
if (releaseCandidate === null) {
|
||||||
|
info(' • No release-candidate or feature-freeze branch currently active.');
|
||||||
|
}
|
||||||
|
info();
|
||||||
|
info(blue('Current active LTS version branches:'));
|
||||||
|
// Print all active LTS branches (each branch as own bullet point).
|
||||||
|
if (ltsBranches.active.length !== 0) {
|
||||||
|
for (const ltsBranch of ltsBranches.active) {
|
||||||
|
info(` • ${bold(ltsBranch.name)} is currently in active long-term support phase.`);
|
||||||
|
info(` Most recent patch version for this branch is "${bold(`v${ltsBranch.version}`)}".`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/** Error that will be thrown if the user manually aborted a release action. */
|
||||||
|
class UserAbortedReleaseActionError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
|
||||||
|
// a limitation in down-leveling.
|
||||||
|
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
|
||||||
|
Object.setPrototypeOf(this, UserAbortedReleaseActionError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Error that will be thrown if the action has been aborted due to a fatal error. */
|
||||||
|
class FatalReleaseActionError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
// Set the prototype explicitly because in ES5, the prototype is accidentally lost due to
|
||||||
|
// a limitation in down-leveling.
|
||||||
|
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work.
|
||||||
|
Object.setPrototypeOf(this, FatalReleaseActionError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Increments a specified SemVer version. Compared to the original increment in SemVer,
|
||||||
|
* the version is cloned to not modify the original version instance.
|
||||||
|
*/
|
||||||
|
function semverInc(version, release, identifier) {
|
||||||
|
const clone = new semver.SemVer(version.version);
|
||||||
|
return clone.inc(release, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright Google LLC All Rights Reserved.
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
@ -5918,6 +5973,8 @@ function getReleaseNoteCherryPickCommitMessage(newVersion) {
|
||||||
*/
|
*/
|
||||||
/** Project-relative path for the "package.json" file. */
|
/** Project-relative path for the "package.json" file. */
|
||||||
const packageJsonPath = 'package.json';
|
const packageJsonPath = 'package.json';
|
||||||
|
/** Project-relative path for the changelog file. */
|
||||||
|
const changelogPath = 'CHANGELOG.md';
|
||||||
/** Default interval in milliseconds to check whether a pull request has been merged. */
|
/** Default interval in milliseconds to check whether a pull request has been merged. */
|
||||||
const waitForPullRequestInterval = 10000;
|
const waitForPullRequestInterval = 10000;
|
||||||
|
|
||||||
|
@ -6357,7 +6414,7 @@ class ReleaseAction {
|
||||||
*/
|
*/
|
||||||
prependReleaseNotesToChangelog(releaseNotes) {
|
prependReleaseNotesToChangelog(releaseNotes) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
const localChangelogPath = getLocalChangelogFilePath(this.projectDir);
|
const localChangelogPath = path.join(this.projectDir, changelogPath);
|
||||||
const localChangelog = yield fs.promises.readFile(localChangelogPath, 'utf8');
|
const localChangelog = yield fs.promises.readFile(localChangelogPath, 'utf8');
|
||||||
const releaseNotesEntry = yield releaseNotes.getChangelogEntry();
|
const releaseNotesEntry = yield releaseNotes.getChangelogEntry();
|
||||||
yield fs.promises.writeFile(localChangelogPath, `${releaseNotesEntry}\n\n${localChangelog}`);
|
yield fs.promises.writeFile(localChangelogPath, `${releaseNotesEntry}\n\n${localChangelog}`);
|
||||||
|
@ -7223,11 +7280,11 @@ class ReleaseTool {
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
/** Yargs command builder for configuring the `ng-dev release publish` command. */
|
/** Yargs command builder for configuring the `ng-dev release publish` command. */
|
||||||
function builder$8(argv) {
|
function builder$9(argv) {
|
||||||
return addGithubTokenOption(argv);
|
return addGithubTokenOption(argv);
|
||||||
}
|
}
|
||||||
/** Yargs command handler for staging a release. */
|
/** Yargs command handler for staging a release. */
|
||||||
function handler$8() {
|
function handler$9() {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
const git = GitClient.getInstance();
|
const git = GitClient.getInstance();
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
@ -7252,8 +7309,8 @@ function handler$8() {
|
||||||
}
|
}
|
||||||
/** CLI command module for publishing a release. */
|
/** CLI command module for publishing a release. */
|
||||||
const ReleasePublishCommandModule = {
|
const ReleasePublishCommandModule = {
|
||||||
builder: builder$8,
|
builder: builder$9,
|
||||||
handler: handler$8,
|
handler: handler$9,
|
||||||
command: 'publish',
|
command: 'publish',
|
||||||
describe: 'Publish new releases and configure version branches.',
|
describe: 'Publish new releases and configure version branches.',
|
||||||
};
|
};
|
||||||
|
@ -7265,7 +7322,7 @@ const ReleasePublishCommandModule = {
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
function builder$9(args) {
|
function builder$a(args) {
|
||||||
return args
|
return args
|
||||||
.positional('tagName', {
|
.positional('tagName', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -7279,7 +7336,7 @@ function builder$9(args) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/** Yargs command handler for building a release. */
|
/** Yargs command handler for building a release. */
|
||||||
function handler$9(args) {
|
function handler$a(args) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
const { targetVersion: rawVersion, tagName } = args;
|
const { targetVersion: rawVersion, tagName } = args;
|
||||||
const { npmPackages, publishRegistry } = getReleaseConfig();
|
const { npmPackages, publishRegistry } = getReleaseConfig();
|
||||||
|
@ -7311,8 +7368,8 @@ function handler$9(args) {
|
||||||
}
|
}
|
||||||
/** CLI command module for setting an NPM dist tag. */
|
/** CLI command module for setting an NPM dist tag. */
|
||||||
const ReleaseSetDistTagCommand = {
|
const ReleaseSetDistTagCommand = {
|
||||||
builder: builder$9,
|
builder: builder$a,
|
||||||
handler: handler$9,
|
handler: handler$a,
|
||||||
command: 'set-dist-tag <tag-name> <target-version>',
|
command: 'set-dist-tag <tag-name> <target-version>',
|
||||||
describe: 'Sets a given NPM dist tag for all release packages.',
|
describe: 'Sets a given NPM dist tag for all release packages.',
|
||||||
};
|
};
|
||||||
|
@ -7392,22 +7449,22 @@ function getCurrentGitUser() {
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
function builder$a(args) {
|
function builder$b(args) {
|
||||||
return args.option('mode', {
|
return args.option('mode', {
|
||||||
demandOption: true,
|
demandOption: true,
|
||||||
description: 'Whether the env-stamp should be built for a snapshot or release',
|
description: 'Whether the env-stamp should be built for a snapshot or release',
|
||||||
choices: ['snapshot', 'release']
|
choices: ['snapshot', 'release']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function handler$a({ mode }) {
|
function handler$b({ mode }) {
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
buildEnvStamp(mode);
|
buildEnvStamp(mode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/** CLI command module for building the environment stamp. */
|
/** CLI command module for building the environment stamp. */
|
||||||
const BuildEnvStampCommand = {
|
const BuildEnvStampCommand = {
|
||||||
builder: builder$a,
|
builder: builder$b,
|
||||||
handler: handler$a,
|
handler: handler$b,
|
||||||
command: 'build-env-stamp',
|
command: 'build-env-stamp',
|
||||||
describe: 'Build the environment stamping information',
|
describe: 'Build the environment stamping information',
|
||||||
};
|
};
|
||||||
|
@ -7420,7 +7477,8 @@ function buildReleaseParser(localYargs) {
|
||||||
.command(ReleasePublishCommandModule)
|
.command(ReleasePublishCommandModule)
|
||||||
.command(ReleaseBuildCommandModule)
|
.command(ReleaseBuildCommandModule)
|
||||||
.command(ReleaseSetDistTagCommand)
|
.command(ReleaseSetDistTagCommand)
|
||||||
.command(BuildEnvStampCommand);
|
.command(BuildEnvStampCommand)
|
||||||
|
.command(ReleaseNotesCommandModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,7 @@ ts_library(
|
||||||
visibility = ["//dev-infra:__subpackages__"],
|
visibility = ["//dev-infra:__subpackages__"],
|
||||||
deps = [
|
deps = [
|
||||||
"//dev-infra/release/build",
|
"//dev-infra/release/build",
|
||||||
|
"//dev-infra/release/notes",
|
||||||
"//dev-infra/release/publish",
|
"//dev-infra/release/publish",
|
||||||
"//dev-infra/release/set-dist-tag",
|
"//dev-infra/release/set-dist-tag",
|
||||||
"//dev-infra/utils",
|
"//dev-infra/utils",
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
|
|
||||||
import {ReleaseBuildCommandModule} from './build/cli';
|
import {ReleaseBuildCommandModule} from './build/cli';
|
||||||
|
import {ReleaseNotesCommandModule} from './notes/cli';
|
||||||
import {ReleasePublishCommandModule} from './publish/cli';
|
import {ReleasePublishCommandModule} from './publish/cli';
|
||||||
import {ReleaseSetDistTagCommand} from './set-dist-tag/cli';
|
import {ReleaseSetDistTagCommand} from './set-dist-tag/cli';
|
||||||
import {BuildEnvStampCommand} from './stamping/cli';
|
import {BuildEnvStampCommand} from './stamping/cli';
|
||||||
|
@ -20,5 +21,6 @@ export function buildReleaseParser(localYargs: yargs.Argv) {
|
||||||
.command(ReleasePublishCommandModule)
|
.command(ReleasePublishCommandModule)
|
||||||
.command(ReleaseBuildCommandModule)
|
.command(ReleaseBuildCommandModule)
|
||||||
.command(ReleaseSetDistTagCommand)
|
.command(ReleaseSetDistTagCommand)
|
||||||
.command(BuildEnvStampCommand);
|
.command(BuildEnvStampCommand)
|
||||||
|
.command(ReleaseNotesCommandModule);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* @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 {writeFileSync} from 'fs';
|
||||||
|
import {join} from 'path';
|
||||||
|
import {SemVer} from 'semver';
|
||||||
|
import {Arguments, Argv, CommandModule} from 'yargs';
|
||||||
|
|
||||||
|
import {debug, info} from '../../utils/console';
|
||||||
|
import {GitClient} from '../../utils/git/index';
|
||||||
|
|
||||||
|
import {ReleaseNotes} from './release-notes';
|
||||||
|
|
||||||
|
/** Command line options for building a release. */
|
||||||
|
export interface ReleaseNotesOptions {
|
||||||
|
from?: string;
|
||||||
|
to: string;
|
||||||
|
outFile?: string;
|
||||||
|
releaseVersion: SemVer;
|
||||||
|
type: 'github-release'|'changelog';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Yargs command builder for configuring the `ng-dev release build` command. */
|
||||||
|
function builder(argv: Argv): Argv<ReleaseNotesOptions> {
|
||||||
|
return argv
|
||||||
|
.option(
|
||||||
|
'releaseVersion',
|
||||||
|
{type: 'string', default: '0.0.0', coerce: (version: string) => new SemVer(version)})
|
||||||
|
.option('from', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The git tag or ref to start the changelog entry from',
|
||||||
|
defaultDescription: 'The latest semver tag',
|
||||||
|
})
|
||||||
|
.option('to', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The git tag or ref to end the changelog entry with',
|
||||||
|
default: 'HEAD',
|
||||||
|
})
|
||||||
|
.option('type', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'The type of release notes to create',
|
||||||
|
choices: ['github-release', 'changelog'] as const,
|
||||||
|
default: 'changelog' as const,
|
||||||
|
})
|
||||||
|
.option('outFile', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'File location to write the generated release notes to',
|
||||||
|
coerce: (filePath?: string) => filePath ? join(process.cwd(), filePath) : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Yargs command handler for generating release notes. */
|
||||||
|
async function handler({releaseVersion, from, to, outFile, type}: Arguments<ReleaseNotesOptions>) {
|
||||||
|
// Since `yargs` evaluates defaults even if a value as been provided, if no value is provided to
|
||||||
|
// the handler, the latest semver tag on the branch is used.
|
||||||
|
from = from || GitClient.getInstance().getLatestSemverTag().format();
|
||||||
|
/** The ReleaseNotes instance to generate release notes. */
|
||||||
|
const releaseNotes = await ReleaseNotes.fromRange(releaseVersion, from, to);
|
||||||
|
|
||||||
|
/** The requested release notes entry. */
|
||||||
|
const releaseNotesEntry = await (
|
||||||
|
type === 'changelog' ? releaseNotes.getChangelogEntry() :
|
||||||
|
releaseNotes.getGithubReleaseEntry());
|
||||||
|
|
||||||
|
if (outFile) {
|
||||||
|
writeFileSync(outFile, releaseNotesEntry);
|
||||||
|
info(`Generated release notes for "${releaseVersion}" written to ${outFile}`);
|
||||||
|
} else {
|
||||||
|
process.stdout.write(releaseNotesEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** CLI command module for generating release notes. */
|
||||||
|
export const ReleaseNotesCommandModule: CommandModule<{}, ReleaseNotesOptions> = {
|
||||||
|
builder,
|
||||||
|
handler,
|
||||||
|
command: 'notes',
|
||||||
|
describe: 'Generate release notes',
|
||||||
|
};
|
|
@ -11,13 +11,14 @@ import {join} from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
import {getBranchPushMatcher} from '../../../utils/testing';
|
import {getBranchPushMatcher} from '../../../utils/testing';
|
||||||
import {changelogPath, ReleaseNotes} from '../../notes/release-notes';
|
import {ReleaseNotes} from '../../notes/release-notes';
|
||||||
import {NpmDistTag} from '../../versioning';
|
import {NpmDistTag} from '../../versioning';
|
||||||
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
||||||
import * as npm from '../../versioning/npm-publish';
|
import * as npm from '../../versioning/npm-publish';
|
||||||
import {ReleaseTrain} from '../../versioning/release-trains';
|
import {ReleaseTrain} from '../../versioning/release-trains';
|
||||||
import {ReleaseAction} from '../actions';
|
import {ReleaseAction} from '../actions';
|
||||||
import {actions} from '../actions/index';
|
import {actions} from '../actions/index';
|
||||||
|
import {changelogPath} from '../constants';
|
||||||
|
|
||||||
import {fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
import {fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue