diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
index 87d1b185..5fa54aa8 100644
--- a/.github/workflows/release-please.yml
+++ b/.github/workflows/release-please.yml
@@ -5,16 +5,20 @@ on:
branches:
- main
+permissions:
+ contents: read
+
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- - uses: google-github-actions/release-please-action@v4
+ - uses: googleapis/release-please-action@v4
id: release
with:
release-type: node
+ token: ${{ secrets.RELEASE_PLEASE_GITHUB_TOKEN }}
npm-publish:
needs: release-please
if: ${{ needs.release-please.outputs.release_created }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd1d8b11..b543292f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog
+## [5.5.0](https://github.com/nodejs/node-core-utils/compare/v5.4.0...v5.5.0) (2024-09-01)
+
+
+### Features
+
+* add git node security --cleanup ([#833](https://github.com/nodejs/node-core-utils/issues/833)) ([871a16f](https://github.com/nodejs/node-core-utils/commit/871a16f7968d112468f751ab019ca575151745d2))
+* add warning when fail on all platforms ([#843](https://github.com/nodejs/node-core-utils/issues/843)) ([4f7ec3e](https://github.com/nodejs/node-core-utils/commit/4f7ec3e7923592dbfef32206bf1c2b21a36e15dd))
+* **git-node:** auto-fetch comparison branch when preparing release ([#846](https://github.com/nodejs/node-core-utils/issues/846)) ([a8529ed](https://github.com/nodejs/node-core-utils/commit/a8529edaab765e293965054b7027774a3f4938f2))
+* **git-node:** auto-fetch latest release tag when preparing release ([#842](https://github.com/nodejs/node-core-utils/issues/842)) ([15ae401](https://github.com/nodejs/node-core-utils/commit/15ae4013b2cbf56ec7f3356d8a8bb682ceef29c3))
+* update next-security-release folder on cleanup ([#840](https://github.com/nodejs/node-core-utils/issues/840)) ([f420432](https://github.com/nodejs/node-core-utils/commit/f420432c6cbdba51da734276d20f73c75728b409))
+* **v8:** add fast_float to V8 deps ([#844](https://github.com/nodejs/node-core-utils/issues/844)) ([4e8ec9c](https://github.com/nodejs/node-core-utils/commit/4e8ec9c072283c8cb31155c66815027ed82fa210))
+
+
+### Bug Fixes
+
+* landing session on different repo/org ([#847](https://github.com/nodejs/node-core-utils/issues/847)) ([7734954](https://github.com/nodejs/node-core-utils/commit/77349540beee5c8d8808f5783e72e106b02f113b))
+
## [5.4.0](https://github.com/nodejs/node-core-utils/compare/v5.3.1...v5.4.0) (2024-08-07)
diff --git a/components/git/release.js b/components/git/release.js
index c74ba5ab..ae82e0ae 100644
--- a/components/git/release.js
+++ b/components/git/release.js
@@ -78,6 +78,8 @@ async function main(state, argv, cli, dir) {
if (state === PREPARE) {
const prep = new ReleasePreparation(argv, cli, dir);
+ await prep.prepareLocalBranch();
+
if (prep.warnForWrongBranch()) return;
// If the new version was automatically calculated, confirm it.
diff --git a/components/git/security.js b/components/git/security.js
index cdb4771a..bd306f1d 100644
--- a/components/git/security.js
+++ b/components/git/security.js
@@ -43,6 +43,10 @@ const securityOptions = {
'post-release': {
describe: 'Create the post-release announcement',
type: 'boolean'
+ },
+ cleanup: {
+ describe: 'cleanup the security release.',
+ type: 'boolean'
}
};
@@ -81,6 +85,9 @@ export function builder(yargs) {
).example(
'git node security --post-release',
'Create the post-release announcement on the Nodejs.org repo'
+ ).example(
+ 'git node security --cleanup',
+ 'Cleanup the security release. Merge the PR and close H1 reports'
);
}
@@ -112,6 +119,9 @@ export function handler(argv) {
if (argv['post-release']) {
return createPostRelease(argv);
}
+ if (argv.cleanup) {
+ return cleanupSecurityRelease(argv);
+ }
yargsInstance.showHelp();
}
@@ -167,6 +177,13 @@ async function startSecurityRelease() {
return release.start();
}
+async function cleanupSecurityRelease() {
+ const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
+ const cli = new CLI(logStream);
+ const release = new PrepareSecurityRelease(cli);
+ return release.cleanup();
+}
+
async function syncSecurityRelease(argv) {
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
const cli = new CLI(logStream);
diff --git a/lib/ci/build-types/citgm_comparison_build.js b/lib/ci/build-types/citgm_comparison_build.js
index fb6c6775..d5fb58a8 100644
--- a/lib/ci/build-types/citgm_comparison_build.js
+++ b/lib/ci/build-types/citgm_comparison_build.js
@@ -44,6 +44,7 @@ export class CITGMComparisonBuild {
const { failures: comparisonFailures } = comparisonBuild.results;
const failures = {};
+ let allPlatformFailures;
for (const platform in comparisonFailures) {
// Account for no failure on this platform, or different platform.
if (!Object.prototype.hasOwnProperty.call(baseFailures, platform)) {
@@ -66,11 +67,18 @@ export class CITGMComparisonBuild {
if (newFailures.length !== 0) {
result = statusType.FAILURE;
}
-
+ if (allPlatformFailures === undefined) {
+ allPlatformFailures = newFailures;
+ } else if (allPlatformFailures.length > 0) {
+ allPlatformFailures = allPlatformFailures.filter(f => {
+ return newFailures.includes(f);
+ });
+ }
failures[platform] = newFailures;
}
this.results.failures = failures;
+ this.results.allPlatformFailures = allPlatformFailures;
this.result = result;
return result;
@@ -124,6 +132,12 @@ export class CITGMComparisonBuild {
const str = `${totalFailures} failures in ${cID} not present in ${bID}`;
cli.log(`${statusType.FAILURE}: ${str}\n\n`);
console.table(output);
+ if (
+ results.allPlatformFailures &&
+ results.allPlatformFailures.length) {
+ const failures = results.allPlatformFailures.join(', ');
+ console.warn(`These modules failed in all platforms: ${failures}`);
+ }
}
formatAsJson() {
diff --git a/lib/landing_session.js b/lib/landing_session.js
index 9deb8cb9..7c86efc5 100644
--- a/lib/landing_session.js
+++ b/lib/landing_session.js
@@ -21,7 +21,7 @@ export default class LandingSession extends Session {
prid, backport, lint, autorebase, fixupAll,
checkCI, oneCommitMax, ...argv
} = {}) {
- super(cli, dir, prid);
+ super(cli, dir, prid, argv);
this.req = req;
this.backport = backport;
this.lint = lint;
diff --git a/lib/prepare_release.js b/lib/prepare_release.js
index 5b64d79f..6b0aa131 100644
--- a/lib/prepare_release.js
+++ b/lib/prepare_release.js
@@ -4,8 +4,7 @@ import { promises as fs } from 'node:fs';
import semver from 'semver';
import { replaceInFile } from 'replace-in-file';
-import { getMergedConfig } from './config.js';
-import { runAsync, runSync } from './run.js';
+import { forceRunAsync, runAsync, runSync } from './run.js';
import { writeJson, readJson } from './file.js';
import Request from './request.js';
import auth from './auth.js';
@@ -15,58 +14,25 @@ import {
updateTestProcessRelease
} from './release/utils.js';
import CherryPick from './cherry_pick.js';
+import Session from './session.js';
const isWindows = process.platform === 'win32';
-export default class ReleasePreparation {
+export default class ReleasePreparation extends Session {
constructor(argv, cli, dir) {
- this.cli = cli;
- this.dir = dir;
+ super(cli, dir);
this.isSecurityRelease = argv.security;
this.isLTS = false;
this.isLTSTransition = argv.startLTS;
this.runBranchDiff = !argv.skipBranchDiff;
this.ltsCodename = '';
this.date = '';
- this.config = getMergedConfig(this.dir);
this.filterLabels = argv.filterLabel && argv.filterLabel.split(',');
+ this.newVersion = argv.newVersion;
+ }
- // Ensure the preparer has set an upstream and username.
- if (this.warnForMissing()) {
- cli.error('Failed to begin the release preparation process.');
- return;
- }
-
- // Allow passing optional new version.
- if (argv.newVersion) {
- const newVersion = semver.clean(argv.newVersion);
- if (!semver.valid(newVersion)) {
- cli.warn(`${newVersion} is not a valid semantic version.`);
- return;
- }
- this.newVersion = newVersion;
- } else {
- this.newVersion = this.calculateNewVersion();
- }
-
- const { upstream, owner, repo, newVersion } = this;
-
- this.versionComponents = {
- major: semver.major(newVersion),
- minor: semver.minor(newVersion),
- patch: semver.patch(newVersion)
- };
-
- this.stagingBranch = `v${this.versionComponents.major}.x-staging`;
- this.releaseBranch = `v${this.versionComponents.major}.x`;
-
- const upstreamHref = runSync('git', [
- 'config', '--get',
- `remote.${upstream}.url`]).trim();
- if (!new RegExp(`${owner}/${repo}(?:.git)?$`).test(upstreamHref)) {
- cli.warn('Remote repository URL does not point to the expected ' +
- `repository ${owner}/${repo}`);
- }
+ get branch() {
+ return this.stagingBranch;
}
warnForNonMergeablePR(pr) {
@@ -205,7 +171,7 @@ export default class ReleasePreparation {
// Check the branch diff to determine if the releaser
// wants to backport any more commits before proceeding.
cli.startSpinner('Fetching branch-diff');
- const raw = this.getBranchDiff({
+ const raw = await this.getBranchDiff({
onlyNotableChanges: false,
comparisonBranch: newVersion
});
@@ -215,10 +181,9 @@ export default class ReleasePreparation {
const outstandingCommits = diff.length - 1;
if (outstandingCommits !== 0) {
- const staging = `v${semver.major(newVersion)}.x-staging`;
const proceed = await cli.prompt(
`There are ${outstandingCommits} commits that may be ` +
- `backported to ${staging} - do you still want to proceed?`,
+ `backported to ${this.stagingBranch} - do you still want to proceed?`,
{ defaultAnswer: false });
if (!proceed) {
@@ -369,24 +334,19 @@ export default class ReleasePreparation {
return missing;
}
- calculateNewVersion() {
- let newVersion;
-
- const lastTagVersion = semver.clean(this.getLastRef());
- const lastTag = {
- major: semver.major(lastTagVersion),
- minor: semver.minor(lastTagVersion),
- patch: semver.patch(lastTagVersion)
- };
-
- const changelog = this.getChangelog();
+ async calculateNewVersion({ tagName, major, minor, patch }) {
+ const changelog = this.getChangelog(tagName);
+ const newVersion = { major, minor, patch };
if (changelog.includes('SEMVER-MAJOR')) {
- newVersion = `${lastTag.major + 1}.0.0`;
+ newVersion.major++;
+ newVersion.minor = 0;
+ newVersion.patch = 0;
} else if (changelog.includes('SEMVER-MINOR') || this.isLTSTransition) {
- newVersion = `${lastTag.major}.${lastTag.minor + 1}.0`;
+ newVersion.minor++;
+ newVersion.patch = 0;
} else {
- newVersion = `${lastTag.major}.${lastTag.minor}.${lastTag.patch + 1}`;
+ newVersion.patch++;
}
return newVersion;
@@ -396,11 +356,22 @@ export default class ReleasePreparation {
return runSync('git', ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
}
- getLastRef() {
- return runSync('git', ['describe', '--abbrev=0', '--tags']).trim();
+ getLastRef(tagName) {
+ if (!tagName) {
+ return runSync('git', ['describe', '--abbrev=0', '--tags']).trim();
+ }
+
+ try {
+ runSync('git', ['rev-parse', tagName]);
+ } catch {
+ this.cli.startSpinner(`Error parsing git ref ${tagName}, attempting fetching it as a tag`);
+ runSync('git', ['fetch', this.upstream, 'tag', '-n', tagName]);
+ this.cli.stopSpinner(`Tag fetched: ${tagName}`);
+ }
+ return tagName;
}
- getChangelog() {
+ getChangelog(tagName) {
const changelogMaker = new URL(
'../node_modules/.bin/changelog-maker' + (isWindows ? '.cmd' : ''),
import.meta.url
@@ -411,7 +382,7 @@ export default class ReleasePreparation {
'--markdown',
'--filter-release',
'--start-ref',
- this.getLastRef()
+ this.getLastRef(tagName)
]).trim();
}
@@ -496,7 +467,7 @@ export default class ReleasePreparation {
const data = await fs.readFile(majorChangelogPath, 'utf8');
const arr = data.split('\n');
const allCommits = this.getChangelog();
- const notableChanges = this.getBranchDiff({ onlyNotableChanges: true });
+ const notableChanges = await this.getBranchDiff({ onlyNotableChanges: true });
let releaseHeader = `## ${date}, Version ${newVersion}` +
` ${releaseInfo}, @${username}\n`;
if (isSecurityRelease) {
@@ -550,14 +521,14 @@ export default class ReleasePreparation {
}
async createProposalBranch(base = this.stagingBranch) {
- const { upstream, newVersion } = this;
+ const { newVersion } = this;
const proposalBranch = `v${newVersion}-proposal`;
await runAsync('git', [
'checkout',
'-b',
proposalBranch,
- `${upstream}/${base}`
+ base
]);
return proposalBranch;
}
@@ -632,7 +603,7 @@ export default class ReleasePreparation {
messageBody.push('This is a security release.\n\n');
}
- const notableChanges = this.getBranchDiff({
+ const notableChanges = await this.getBranchDiff({
onlyNotableChanges: true,
format: 'plaintext'
});
@@ -659,8 +630,9 @@ export default class ReleasePreparation {
return useMessage;
}
- getBranchDiff(opts) {
+ async getBranchDiff(opts) {
const {
+ cli,
versionComponents = {},
upstream,
newVersion,
@@ -688,6 +660,10 @@ export default class ReleasePreparation {
'semver-minor'
];
+ await forceRunAsync('git', ['remote', 'set-branches', '--add', upstream, releaseBranch], {
+ ignoreFailures: false
+ });
+ await forceRunAsync('git', ['fetch', upstream, releaseBranch], { ignoreFailures: false });
branchDiffOptions = [
`${upstream}/${releaseBranch}`,
proposalBranch,
@@ -706,20 +682,43 @@ export default class ReleasePreparation {
'baking-for-lts'
];
- let comparisonBranch = 'main';
+ let comparisonBranch = this.config.branch || 'main';
const isSemverMinor = versionComponents.patch === 0;
if (isLTS) {
+ const res = await fetch('https://nodejs.org/dist/index.json');
+ if (!res.ok) throw new Error('Failed to fetch', { cause: res });
+ const [latest] = await res.json();
// Assume Current branch matches tag with highest semver value.
- const tags = runSync('git',
- ['tag', '-l', '--sort', '-version:refname']).trim();
- const highestVersionTag = tags.split('\n')[0];
- comparisonBranch = `v${semver.coerce(highestVersionTag).major}.x`;
+ comparisonBranch = `v${semver.coerce(latest.version).major}.x`;
if (!isSemverMinor) {
excludeLabels.push('semver-minor');
}
}
+ await forceRunAsync('git', ['fetch', upstream, comparisonBranch], { ignoreFailures: false });
+ const commits = await forceRunAsync('git', ['rev-parse', 'FETCH_HEAD', comparisonBranch], {
+ captureStdout: 'lines',
+ ignoreFailures: true
+ });
+ if (commits == null) {
+ const shouldCreateCompareBranch = await cli.prompt(
+ `No local branch ${comparisonBranch}, do you want to create it?`);
+ if (shouldCreateCompareBranch) {
+ await forceRunAsync('git', ['branch', comparisonBranch, 'FETCH_HEAD'], {
+ ignoreFailures: false
+ });
+ }
+ } else if (commits[0] !== commits[1]) {
+ const shouldUpBranch = cli.prompt(`Local ${comparisonBranch} branch is not in sync with ${
+ upstream}/${comparisonBranch}, do you want to update it?`);
+ if (shouldUpBranch) {
+ await forceRunAsync('git', ['branch', '-f', comparisonBranch, 'FETCH_HEAD'], {
+ ignoreFailures: false
+ });
+ }
+ }
+
branchDiffOptions = [
stagingBranch,
comparisonBranch,
@@ -736,6 +735,67 @@ export default class ReleasePreparation {
return runSync(branchDiff, branchDiffOptions);
}
+ async getLastRelease(major) {
+ const { cli } = this;
+
+ cli.startSpinner(`Parsing CHANGELOG for most recent release of v${major}.x`);
+ const data = await fs.readFile(
+ path.resolve(`doc/changelogs/CHANGELOG_V${major}.md`),
+ 'utf8'
+ );
+ const [,, minor, patch] = /\1\.\2\.\3<\/a>
/.exec(data);
+ this.isLTS = data.includes('