refactor(dev-infra): move the release notes into its own directory under release (#42225)
Move the release notes tooling under its own directory under release rather than within publish, in preparation to have a release note generation command for ad-hoc release note generation. PR Close #42225
This commit is contained in:
parent
f74fd64523
commit
a9584c929b
@ -5507,234 +5507,6 @@ function semverInc(version, release, identifier) {
|
|||||||
return clone.inc(release, identifier);
|
return clone.inc(release, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/** Gets the commit message for a new release point in the project. */
|
|
||||||
function getCommitMessageForRelease(newVersion) {
|
|
||||||
return `release: cut the v${newVersion} release`;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Gets the commit message for an exceptional version bump in the next branch. The next
|
|
||||||
* branch version will be bumped without the release being published in some situations.
|
|
||||||
* More details can be found in the `MoveNextIntoFeatureFreeze` release action and in:
|
|
||||||
* https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A.
|
|
||||||
*/
|
|
||||||
function getCommitMessageForExceptionalNextVersionBump(newVersion) {
|
|
||||||
return `release: bump the next branch to v${newVersion}`;
|
|
||||||
}
|
|
||||||
/** Gets the commit message for a release notes cherry-pick commit */
|
|
||||||
function getReleaseNoteCherryPickCommitMessage(newVersion) {
|
|
||||||
return `docs: release notes for the v${newVersion} release`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/** Project-relative path for the changelog file. */
|
|
||||||
const changelogPath = 'CHANGELOG.md';
|
|
||||||
/** Project-relative path for the "package.json" file. */
|
|
||||||
const packageJsonPath = 'package.json';
|
|
||||||
/** Default interval in milliseconds to check whether a pull request has been merged. */
|
|
||||||
const waitForPullRequestInterval = 10000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
* ###############################################################
|
|
||||||
*
|
|
||||||
* This file contains helpers for invoking external `ng-dev` commands. A subset of actions,
|
|
||||||
* like building release output or setting aν NPM dist tag for release packages, cannot be
|
|
||||||
* performed directly as part of the release tool and need to be delegated to external `ng-dev`
|
|
||||||
* commands that exist across arbitrary version branches.
|
|
||||||
*
|
|
||||||
* In a concrete example: Consider a new patch version is released and that a new release
|
|
||||||
* package has been added to the `next` branch. The patch branch will not contain the new
|
|
||||||
* release package, so we could not build the release output for it. To work around this, we
|
|
||||||
* call the ng-dev build command for the patch version branch and expect it to return a list
|
|
||||||
* of built packages that need to be released as part of this release train.
|
|
||||||
*
|
|
||||||
* ###############################################################
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Invokes the `ng-dev release set-dist-tag` command in order to set the specified
|
|
||||||
* NPM dist tag for all packages in the checked out branch to the given version.
|
|
||||||
*/
|
|
||||||
function invokeSetNpmDistCommand(npmDistTag, version) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
try {
|
|
||||||
// Note: No progress indicator needed as that is the responsibility of the command.
|
|
||||||
yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
|
|
||||||
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
error(e);
|
|
||||||
error(red(` ✘ An error occurred while setting the NPM dist tag for "${npmDistTag}".`));
|
|
||||||
throw new FatalReleaseActionError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Invokes the `ng-dev release build` command in order to build the release
|
|
||||||
* packages for the currently checked out branch.
|
|
||||||
*/
|
|
||||||
function invokeReleaseBuildCommand() {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const spinner = ora.call(undefined).start('Building release output.');
|
|
||||||
try {
|
|
||||||
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
|
||||||
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
|
||||||
const { stdout } = yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], { mode: 'silent' });
|
|
||||||
spinner.stop();
|
|
||||||
info(green(' ✓ Built release output for all packages.'));
|
|
||||||
// The `ng-dev release build` command prints a JSON array to stdout
|
|
||||||
// that represents the built release packages and their output paths.
|
|
||||||
return JSON.parse(stdout.trim());
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
spinner.stop();
|
|
||||||
error(e);
|
|
||||||
error(red(' ✘ An error occurred while building the release packages.'));
|
|
||||||
throw new FatalReleaseActionError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Invokes the `yarn install` command in order to install dependencies for
|
|
||||||
* the configured project with the currently checked out revision.
|
|
||||||
*/
|
|
||||||
function invokeYarnInstallCommand(projectDir) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
try {
|
|
||||||
// Note: No progress indicator needed as that is the responsibility of the command.
|
|
||||||
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
|
||||||
yield spawnWithDebugOutput('yarn', ['install', '--frozen-lockfile', '--non-interactive'], { cwd: projectDir });
|
|
||||||
info(green(' ✓ Installed project dependencies.'));
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
error(e);
|
|
||||||
error(red(' ✘ An error occurred while installing dependencies.'));
|
|
||||||
throw new FatalReleaseActionError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Graphql Github API query that can be used to find forks of a given repository
|
|
||||||
* that are owned by the current viewer authenticated with the Github API.
|
|
||||||
*/
|
|
||||||
const findOwnedForksOfRepoQuery = typedGraphqlify.params({
|
|
||||||
$owner: 'String!',
|
|
||||||
$name: 'String!',
|
|
||||||
}, {
|
|
||||||
repository: typedGraphqlify.params({ owner: '$owner', name: '$name' }, {
|
|
||||||
forks: typedGraphqlify.params({ affiliations: 'OWNER', first: 1 }, {
|
|
||||||
nodes: [{
|
|
||||||
owner: {
|
|
||||||
login: typedGraphqlify.types.string,
|
|
||||||
},
|
|
||||||
name: typedGraphqlify.types.string,
|
|
||||||
}],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
/** Thirty seconds in milliseconds. */
|
|
||||||
const THIRTY_SECONDS_IN_MS = 30000;
|
|
||||||
/** Gets whether a given pull request has been merged. */
|
|
||||||
function getPullRequestState(api, id) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const { data } = yield api.github.pulls.get(Object.assign(Object.assign({}, api.remoteParams), { pull_number: id }));
|
|
||||||
if (data.merged) {
|
|
||||||
return 'merged';
|
|
||||||
}
|
|
||||||
// Check if the PR was closed more than 30 seconds ago, this extra time gives Github time to
|
|
||||||
// update the closed pull request to be associated with the closing commit.
|
|
||||||
// Note: a Date constructed with `null` creates an object at 0 time, which will never be greater
|
|
||||||
// than the current date time.
|
|
||||||
if (data.closed_at !== null &&
|
|
||||||
(new Date(data.closed_at).getTime() < Date.now() - THIRTY_SECONDS_IN_MS)) {
|
|
||||||
return (yield isPullRequestClosedWithAssociatedCommit(api, id)) ? 'merged' : 'closed';
|
|
||||||
}
|
|
||||||
return 'open';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Whether the pull request has been closed with an associated commit. This is usually
|
|
||||||
* the case if a PR has been merged using the autosquash merge script strategy. Since
|
|
||||||
* the merge is not fast-forward, Github does not consider the PR as merged and instead
|
|
||||||
* shows the PR as closed. See for example: https://github.com/angular/angular/pull/37918.
|
|
||||||
*/
|
|
||||||
function isPullRequestClosedWithAssociatedCommit(api, id) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const request = api.github.issues.listEvents.endpoint.merge(Object.assign(Object.assign({}, api.remoteParams), { issue_number: id }));
|
|
||||||
const events = yield api.github.paginate(request);
|
|
||||||
// Iterate through the events of the pull request in reverse. We want to find the most
|
|
||||||
// recent events and check if the PR has been closed with a commit associated with it.
|
|
||||||
// If the PR has been closed through a commit, we assume that the PR has been merged
|
|
||||||
// using the autosquash merge strategy. For more details. See the `AutosquashMergeStrategy`.
|
|
||||||
for (let i = events.length - 1; i >= 0; i--) {
|
|
||||||
const { event, commit_id } = events[i];
|
|
||||||
// If we come across a "reopened" event, we abort looking for referenced commits. Any
|
|
||||||
// commits that closed the PR before, are no longer relevant and did not close the PR.
|
|
||||||
if (event === 'reopened') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If a `closed` event is captured with a commit assigned, then we assume that
|
|
||||||
// this PR has been merged properly.
|
|
||||||
if (event === 'closed' && commit_id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If the PR has been referenced by a commit, check if the commit closes this pull
|
|
||||||
// request. Note that this is needed besides checking `closed` as PRs could be merged
|
|
||||||
// into any non-default branch where the `Closes <..>` keyword does not work and the PR
|
|
||||||
// is simply closed without an associated `commit_id`. For more details see:
|
|
||||||
// https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords#:~:text=non-default.
|
|
||||||
if (event === 'referenced' && commit_id &&
|
|
||||||
(yield isCommitClosingPullRequest(api, commit_id, id))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/** Checks whether the specified commit is closing the given pull request. */
|
|
||||||
function isCommitClosingPullRequest(api, sha, id) {
|
|
||||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
||||||
const { data } = yield api.github.repos.getCommit(Object.assign(Object.assign({}, api.remoteParams), { ref: sha }));
|
|
||||||
// Matches the closing keyword supported in commit messages. See:
|
|
||||||
// https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords.
|
|
||||||
return data.commit.message.match(new RegExp(`(?:close[sd]?|fix(?:e[sd]?)|resolve[sd]?):? #${id}(?!\\d)`, 'i'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright Google LLC All Rights Reserved.
|
* Copyright Google LLC All Rights Reserved.
|
||||||
@ -6031,6 +5803,8 @@ _%>
|
|||||||
_%>
|
_%>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/** Project-relative path for the changelog file. */
|
||||||
|
const changelogPath = 'CHANGELOG.md';
|
||||||
/** Gets the path for the changelog file in a given project. */
|
/** Gets the path for the changelog file in a given project. */
|
||||||
function getLocalChangelogFilePath(projectDir) {
|
function getLocalChangelogFilePath(projectDir) {
|
||||||
return path.join(projectDir, changelogPath);
|
return path.join(projectDir, changelogPath);
|
||||||
@ -6110,6 +5884,232 @@ 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
|
||||||
|
*/
|
||||||
|
/** Gets the commit message for a new release point in the project. */
|
||||||
|
function getCommitMessageForRelease(newVersion) {
|
||||||
|
return `release: cut the v${newVersion} release`;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets the commit message for an exceptional version bump in the next branch. The next
|
||||||
|
* branch version will be bumped without the release being published in some situations.
|
||||||
|
* More details can be found in the `MoveNextIntoFeatureFreeze` release action and in:
|
||||||
|
* https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A.
|
||||||
|
*/
|
||||||
|
function getCommitMessageForExceptionalNextVersionBump(newVersion) {
|
||||||
|
return `release: bump the next branch to v${newVersion}`;
|
||||||
|
}
|
||||||
|
/** Gets the commit message for a release notes cherry-pick commit */
|
||||||
|
function getReleaseNoteCherryPickCommitMessage(newVersion) {
|
||||||
|
return `docs: release notes for the v${newVersion} release`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/** Project-relative path for the "package.json" file. */
|
||||||
|
const packageJsonPath = 'package.json';
|
||||||
|
/** Default interval in milliseconds to check whether a pull request has been merged. */
|
||||||
|
const waitForPullRequestInterval = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* ###############################################################
|
||||||
|
*
|
||||||
|
* This file contains helpers for invoking external `ng-dev` commands. A subset of actions,
|
||||||
|
* like building release output or setting aν NPM dist tag for release packages, cannot be
|
||||||
|
* performed directly as part of the release tool and need to be delegated to external `ng-dev`
|
||||||
|
* commands that exist across arbitrary version branches.
|
||||||
|
*
|
||||||
|
* In a concrete example: Consider a new patch version is released and that a new release
|
||||||
|
* package has been added to the `next` branch. The patch branch will not contain the new
|
||||||
|
* release package, so we could not build the release output for it. To work around this, we
|
||||||
|
* call the ng-dev build command for the patch version branch and expect it to return a list
|
||||||
|
* of built packages that need to be released as part of this release train.
|
||||||
|
*
|
||||||
|
* ###############################################################
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Invokes the `ng-dev release set-dist-tag` command in order to set the specified
|
||||||
|
* NPM dist tag for all packages in the checked out branch to the given version.
|
||||||
|
*/
|
||||||
|
function invokeSetNpmDistCommand(npmDistTag, version) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
// Note: No progress indicator needed as that is the responsibility of the command.
|
||||||
|
yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'set-dist-tag', npmDistTag, version.format()]);
|
||||||
|
info(green(` ✓ Set "${npmDistTag}" NPM dist tag for all packages to v${version}.`));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
error(e);
|
||||||
|
error(red(` ✘ An error occurred while setting the NPM dist tag for "${npmDistTag}".`));
|
||||||
|
throw new FatalReleaseActionError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Invokes the `ng-dev release build` command in order to build the release
|
||||||
|
* packages for the currently checked out branch.
|
||||||
|
*/
|
||||||
|
function invokeReleaseBuildCommand() {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const spinner = ora.call(undefined).start('Building release output.');
|
||||||
|
try {
|
||||||
|
// Since we expect JSON to be printed from the `ng-dev release build` command,
|
||||||
|
// we spawn the process in silent mode. We have set up an Ora progress spinner.
|
||||||
|
const { stdout } = yield spawnWithDebugOutput('yarn', ['--silent', 'ng-dev', 'release', 'build', '--json'], { mode: 'silent' });
|
||||||
|
spinner.stop();
|
||||||
|
info(green(' ✓ Built release output for all packages.'));
|
||||||
|
// The `ng-dev release build` command prints a JSON array to stdout
|
||||||
|
// that represents the built release packages and their output paths.
|
||||||
|
return JSON.parse(stdout.trim());
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
spinner.stop();
|
||||||
|
error(e);
|
||||||
|
error(red(' ✘ An error occurred while building the release packages.'));
|
||||||
|
throw new FatalReleaseActionError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Invokes the `yarn install` command in order to install dependencies for
|
||||||
|
* the configured project with the currently checked out revision.
|
||||||
|
*/
|
||||||
|
function invokeYarnInstallCommand(projectDir) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
try {
|
||||||
|
// Note: No progress indicator needed as that is the responsibility of the command.
|
||||||
|
// TODO: Consider using an Ora spinner instead to ensure minimal console output.
|
||||||
|
yield spawnWithDebugOutput('yarn', ['install', '--frozen-lockfile', '--non-interactive'], { cwd: projectDir });
|
||||||
|
info(green(' ✓ Installed project dependencies.'));
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
error(e);
|
||||||
|
error(red(' ✘ An error occurred while installing dependencies.'));
|
||||||
|
throw new FatalReleaseActionError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Graphql Github API query that can be used to find forks of a given repository
|
||||||
|
* that are owned by the current viewer authenticated with the Github API.
|
||||||
|
*/
|
||||||
|
const findOwnedForksOfRepoQuery = typedGraphqlify.params({
|
||||||
|
$owner: 'String!',
|
||||||
|
$name: 'String!',
|
||||||
|
}, {
|
||||||
|
repository: typedGraphqlify.params({ owner: '$owner', name: '$name' }, {
|
||||||
|
forks: typedGraphqlify.params({ affiliations: 'OWNER', first: 1 }, {
|
||||||
|
nodes: [{
|
||||||
|
owner: {
|
||||||
|
login: typedGraphqlify.types.string,
|
||||||
|
},
|
||||||
|
name: typedGraphqlify.types.string,
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
/** Thirty seconds in milliseconds. */
|
||||||
|
const THIRTY_SECONDS_IN_MS = 30000;
|
||||||
|
/** Gets whether a given pull request has been merged. */
|
||||||
|
function getPullRequestState(api, id) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const { data } = yield api.github.pulls.get(Object.assign(Object.assign({}, api.remoteParams), { pull_number: id }));
|
||||||
|
if (data.merged) {
|
||||||
|
return 'merged';
|
||||||
|
}
|
||||||
|
// Check if the PR was closed more than 30 seconds ago, this extra time gives Github time to
|
||||||
|
// update the closed pull request to be associated with the closing commit.
|
||||||
|
// Note: a Date constructed with `null` creates an object at 0 time, which will never be greater
|
||||||
|
// than the current date time.
|
||||||
|
if (data.closed_at !== null &&
|
||||||
|
(new Date(data.closed_at).getTime() < Date.now() - THIRTY_SECONDS_IN_MS)) {
|
||||||
|
return (yield isPullRequestClosedWithAssociatedCommit(api, id)) ? 'merged' : 'closed';
|
||||||
|
}
|
||||||
|
return 'open';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Whether the pull request has been closed with an associated commit. This is usually
|
||||||
|
* the case if a PR has been merged using the autosquash merge script strategy. Since
|
||||||
|
* the merge is not fast-forward, Github does not consider the PR as merged and instead
|
||||||
|
* shows the PR as closed. See for example: https://github.com/angular/angular/pull/37918.
|
||||||
|
*/
|
||||||
|
function isPullRequestClosedWithAssociatedCommit(api, id) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const request = api.github.issues.listEvents.endpoint.merge(Object.assign(Object.assign({}, api.remoteParams), { issue_number: id }));
|
||||||
|
const events = yield api.github.paginate(request);
|
||||||
|
// Iterate through the events of the pull request in reverse. We want to find the most
|
||||||
|
// recent events and check if the PR has been closed with a commit associated with it.
|
||||||
|
// If the PR has been closed through a commit, we assume that the PR has been merged
|
||||||
|
// using the autosquash merge strategy. For more details. See the `AutosquashMergeStrategy`.
|
||||||
|
for (let i = events.length - 1; i >= 0; i--) {
|
||||||
|
const { event, commit_id } = events[i];
|
||||||
|
// If we come across a "reopened" event, we abort looking for referenced commits. Any
|
||||||
|
// commits that closed the PR before, are no longer relevant and did not close the PR.
|
||||||
|
if (event === 'reopened') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If a `closed` event is captured with a commit assigned, then we assume that
|
||||||
|
// this PR has been merged properly.
|
||||||
|
if (event === 'closed' && commit_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If the PR has been referenced by a commit, check if the commit closes this pull
|
||||||
|
// request. Note that this is needed besides checking `closed` as PRs could be merged
|
||||||
|
// into any non-default branch where the `Closes <..>` keyword does not work and the PR
|
||||||
|
// is simply closed without an associated `commit_id`. For more details see:
|
||||||
|
// https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords#:~:text=non-default.
|
||||||
|
if (event === 'referenced' && commit_id &&
|
||||||
|
(yield isCommitClosingPullRequest(api, commit_id, id))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/** Checks whether the specified commit is closing the given pull request. */
|
||||||
|
function isCommitClosingPullRequest(api, sha, id) {
|
||||||
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||||
|
const { data } = yield api.github.repos.getCommit(Object.assign(Object.assign({}, api.remoteParams), { ref: sha }));
|
||||||
|
// Matches the closing keyword supported in commit messages. See:
|
||||||
|
// https://docs.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywords.
|
||||||
|
return data.commit.message.match(new RegExp(`(?:close[sd]?|fix(?:e[sd]?)|resolve[sd]?):? #${id}(?!\\d)`, 'i'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright Google LLC All Rights Reserved.
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
22
dev-infra/release/notes/BUILD.bazel
Normal file
22
dev-infra/release/notes/BUILD.bazel
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
load("@npm//@bazel/typescript:index.bzl", "ts_library")
|
||||||
|
|
||||||
|
ts_library(
|
||||||
|
name = "notes",
|
||||||
|
srcs = glob([
|
||||||
|
"**/*.ts",
|
||||||
|
]),
|
||||||
|
module_name = "@angular/dev-infra-private/release/notes",
|
||||||
|
visibility = ["//dev-infra:__subpackages__"],
|
||||||
|
deps = [
|
||||||
|
"//dev-infra/commit-message",
|
||||||
|
"//dev-infra/release/config",
|
||||||
|
"//dev-infra/release/versioning",
|
||||||
|
"//dev-infra/utils",
|
||||||
|
"@npm//@types/ejs",
|
||||||
|
"@npm//@types/node",
|
||||||
|
"@npm//@types/semver",
|
||||||
|
"@npm//@types/yargs",
|
||||||
|
"@npm//ejs",
|
||||||
|
"@npm//semver",
|
||||||
|
],
|
||||||
|
)
|
@ -6,10 +6,10 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {COMMIT_TYPES, ReleaseNotesLevel} from '../../../commit-message/config';
|
import {COMMIT_TYPES, ReleaseNotesLevel} from '../../commit-message/config';
|
||||||
import {CommitFromGitLog} from '../../../commit-message/parse';
|
import {CommitFromGitLog} from '../../commit-message/parse';
|
||||||
import {GithubConfig} from '../../../utils/config';
|
import {GithubConfig} from '../../utils/config';
|
||||||
import {ReleaseNotesConfig} from '../../config/index';
|
import {ReleaseNotesConfig} from '../config/index';
|
||||||
|
|
||||||
|
|
||||||
/** List of types to be included in the release notes. */
|
/** List of types to be included in the release notes. */
|
@ -6,25 +6,18 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {render} from 'ejs';
|
import {render} from 'ejs';
|
||||||
import {join} from 'path';
|
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import {CommitFromGitLog} from '../../../commit-message/parse';
|
import {CommitFromGitLog} from '../../commit-message/parse';
|
||||||
|
|
||||||
import {getCommitsInRange} from '../../../commit-message/utils';
|
import {getCommitsInRange} from '../../commit-message/utils';
|
||||||
import {promptInput} from '../../../utils/console';
|
import {promptInput} from '../../utils/console';
|
||||||
import {GitClient} from '../../../utils/git/index';
|
import {GitClient} from '../../utils/git/index';
|
||||||
import {DevInfraReleaseConfig, getReleaseConfig, ReleaseNotesConfig} from '../../config/index';
|
import {DevInfraReleaseConfig, getReleaseConfig, ReleaseNotesConfig} from '../config/index';
|
||||||
import {changelogPath} from '../constants';
|
|
||||||
import {RenderContext} from './context';
|
import {RenderContext} from './context';
|
||||||
|
|
||||||
import changelogTemplate from './templates/changelog';
|
import changelogTemplate from './templates/changelog';
|
||||||
import githubReleaseTemplate from './templates/github-release';
|
import githubReleaseTemplate from './templates/github-release';
|
||||||
|
|
||||||
/** Gets the path for the changelog file in a given project. */
|
|
||||||
export function getLocalChangelogFilePath(projectDir: string): string {
|
|
||||||
return join(projectDir, changelogPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Release note generation. */
|
/** Release note generation. */
|
||||||
export class ReleaseNotes {
|
export class ReleaseNotes {
|
||||||
static async fromRange(version: semver.SemVer, startingRef: string, endingRef: string) {
|
static async fromRange(version: semver.SemVer, startingRef: string, endingRef: string) {
|
@ -11,6 +11,7 @@ ts_library(
|
|||||||
"//dev-infra/commit-message",
|
"//dev-infra/commit-message",
|
||||||
"//dev-infra/pr/merge",
|
"//dev-infra/pr/merge",
|
||||||
"//dev-infra/release/config",
|
"//dev-infra/release/config",
|
||||||
|
"//dev-infra/release/notes",
|
||||||
"//dev-infra/release/versioning",
|
"//dev-infra/release/versioning",
|
||||||
"//dev-infra/utils",
|
"//dev-infra/utils",
|
||||||
"@npm//@octokit/rest",
|
"@npm//@octokit/rest",
|
||||||
|
@ -15,6 +15,7 @@ import {debug, error, green, info, promptConfirm, red, warn, yellow} from '../..
|
|||||||
import {getListCommitsInBranchUrl, getRepositoryGitUrl} from '../../utils/git/github-urls';
|
import {getListCommitsInBranchUrl, getRepositoryGitUrl} from '../../utils/git/github-urls';
|
||||||
import {GitClient} from '../../utils/git/index';
|
import {GitClient} from '../../utils/git/index';
|
||||||
import {BuiltPackage, ReleaseConfig} from '../config/index';
|
import {BuiltPackage, ReleaseConfig} from '../config/index';
|
||||||
|
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 {runNpmPublish} from '../versioning/npm-publish';
|
import {runNpmPublish} from '../versioning/npm-publish';
|
||||||
@ -25,7 +26,6 @@ import {changelogPath, packageJsonPath, waitForPullRequestInterval} from './cons
|
|||||||
import {invokeReleaseBuildCommand, invokeYarnInstallCommand} from './external-commands';
|
import {invokeReleaseBuildCommand, invokeYarnInstallCommand} from './external-commands';
|
||||||
import {findOwnedForksOfRepoQuery} from './graphql-queries';
|
import {findOwnedForksOfRepoQuery} from './graphql-queries';
|
||||||
import {getPullRequestState} from './pull-request-state';
|
import {getPullRequestState} from './pull-request-state';
|
||||||
import {getLocalChangelogFilePath, ReleaseNotes} from './release-notes/release-notes';
|
|
||||||
|
|
||||||
/** Interface describing a Github repository. */
|
/** Interface describing a Github repository. */
|
||||||
export interface GithubRepo {
|
export interface GithubRepo {
|
||||||
@ -320,7 +320,7 @@ export abstract class ReleaseAction {
|
|||||||
* @returns A boolean indicating whether the release notes have been prepended.
|
* @returns A boolean indicating whether the release notes have been prepended.
|
||||||
*/
|
*/
|
||||||
protected async prependReleaseNotesToChangelog(releaseNotes: ReleaseNotes): Promise<void> {
|
protected async prependReleaseNotesToChangelog(releaseNotes: ReleaseNotes): Promise<void> {
|
||||||
const localChangelogPath = getLocalChangelogFilePath(this.projectDir);
|
const localChangelogPath = join(this.projectDir, changelogPath);
|
||||||
const localChangelog = await fs.readFile(localChangelogPath, 'utf8');
|
const localChangelog = await fs.readFile(localChangelogPath, 'utf8');
|
||||||
const releaseNotesEntry = await releaseNotes.getChangelogEntry();
|
const releaseNotesEntry = await releaseNotes.getChangelogEntry();
|
||||||
await fs.writeFile(localChangelogPath, `${releaseNotesEntry}\n\n${localChangelog}`);
|
await fs.writeFile(localChangelogPath, `${releaseNotesEntry}\n\n${localChangelog}`);
|
||||||
|
@ -9,12 +9,12 @@
|
|||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
import {green, info, yellow} from '../../../utils/console';
|
import {green, info, yellow} from '../../../utils/console';
|
||||||
|
import {ReleaseNotes} from '../../notes/release-notes';
|
||||||
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
|
||||||
import {computeNewPrereleaseVersionForNext} from '../../versioning/next-prerelease-version';
|
import {computeNewPrereleaseVersionForNext} from '../../versioning/next-prerelease-version';
|
||||||
import {ReleaseAction} from '../actions';
|
import {ReleaseAction} from '../actions';
|
||||||
import {getCommitMessageForExceptionalNextVersionBump, getReleaseNoteCherryPickCommitMessage} from '../commit-message';
|
import {getCommitMessageForExceptionalNextVersionBump, getReleaseNoteCherryPickCommitMessage} from '../commit-message';
|
||||||
import {changelogPath, packageJsonPath} from '../constants';
|
import {changelogPath, packageJsonPath} from '../constants';
|
||||||
import {ReleaseNotes} from '../release-notes/release-notes';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release action that moves the next release-train into the feature-freeze phase. This means
|
* Release action that moves the next release-train into the feature-freeze phase. This means
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Project-relative path for the changelog file. */
|
|
||||||
export const changelogPath = 'CHANGELOG.md';
|
|
||||||
|
|
||||||
/** Project-relative path for the "package.json" file. */
|
/** Project-relative path for the "package.json" file. */
|
||||||
export const packageJsonPath = 'package.json';
|
export const packageJsonPath = 'package.json';
|
||||||
|
|
||||||
|
/** Project-relative path for the changelog file. */
|
||||||
|
export 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. */
|
||||||
export const waitForPullRequestInterval = 10000;
|
export const waitForPullRequestInterval = 10000;
|
||||||
|
@ -10,6 +10,7 @@ ts_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//dev-infra/commit-message",
|
"//dev-infra/commit-message",
|
||||||
"//dev-infra/release/config",
|
"//dev-infra/release/config",
|
||||||
|
"//dev-infra/release/notes",
|
||||||
"//dev-infra/release/publish",
|
"//dev-infra/release/publish",
|
||||||
"//dev-infra/release/versioning",
|
"//dev-infra/release/versioning",
|
||||||
"//dev-infra/utils",
|
"//dev-infra/utils",
|
||||||
|
@ -11,14 +11,13 @@ 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 {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 {ReleaseNotes} from '../release-notes/release-notes';
|
|
||||||
|
|
||||||
import {fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
import {fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {CommitFromGitLog, parseCommitFromGitLog} from '../../../../commit-message/parse';
|
import {CommitFromGitLog, parseCommitFromGitLog} from '../../../../commit-message/parse';
|
||||||
import {commitMessageBuilder} from '../../../../commit-message/test-util';
|
import {commitMessageBuilder} from '../../../../commit-message/test-util';
|
||||||
import {RenderContext, RenderContextData,} from '../../release-notes/context';
|
import {RenderContext, RenderContextData,} from '../../../notes/context';
|
||||||
|
|
||||||
const defaultContextData: RenderContextData = {
|
const defaultContextData: RenderContextData = {
|
||||||
commits: [],
|
commits: [],
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
import {DevInfraReleaseConfig, ReleaseConfig} from '../../../config';
|
import {DevInfraReleaseConfig, ReleaseConfig} from '../../../config';
|
||||||
import {ReleaseNotes} from '../../release-notes/release-notes';
|
import {ReleaseNotes} from '../../../notes/release-notes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock version of the ReleaseNotes for testing, preventing actual calls to git for commits and
|
* Mock version of the ReleaseNotes for testing, preventing actual calls to git for commits and
|
||||||
|
Loading…
x
Reference in New Issue
Block a user