Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: automate creation of the first LTS release #514

Merged
merged 4 commits into from
Nov 1, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions components/git/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const releaseOptions = {
security: {
describe: 'Demarcate the new security release as a security release',
type: 'boolean'
},
startLTS: {
describe: 'Mark the release as the transition from Current to LTS',
type: 'boolean'
}
};

Expand Down
1 change: 1 addition & 0 deletions docs/git-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Options:
--help Show help [boolean]
--prepare Prepare a new release of Node.js [boolean]
--security Demarcate the new security release as a security release [boolean]
--startLTS Mark the release as the transition from Current to LTS [boolean]
```

### Example
Expand Down
60 changes: 56 additions & 4 deletions lib/prepare_release.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const {
getUnmarkedDeprecations,
updateDeprecations
} = require('./deprecations');
const {
getStartLTSBlurb,
updateTestProcessRelease
} = require('./release/utils');

const isWindows = process.platform === 'win32';

Expand All @@ -21,6 +25,7 @@ class ReleasePreparation {
this.dir = dir;
this.isSecurityRelease = argv.security;
this.isLTS = false;
this.isLTSTransition = argv.startLTS;
this.ltsCodename = '';
this.date = '';
this.config = getMergedConfig(this.dir);
Expand Down Expand Up @@ -91,6 +96,20 @@ class ReleasePreparation {
await this.createProposalBranch();
cli.stopSpinner(`Created new proposal branch for ${newVersion}`);

if (this.isLTSTransition) {
// For releases transitioning into LTS, fetch the new code name.
this.ltsCodename = await this.getLTSCodename(versionComponents.major);
// Update test for new LTS code name.
const testFile = path.resolve(
'test',
'parallel',
'test-process-release.js'
);
cli.startSpinner(`Updating ${testFile}`);
await this.updateTestProcessRelease(testFile);
cli.stopSpinner(`Updating ${testFile}`);
}

// Update version and release info in src/node_version.h.
cli.startSpinner(`Updating 'src/node_version.h' for ${newVersion}`);
await this.updateNodeVersion();
Expand Down Expand Up @@ -218,7 +237,7 @@ class ReleasePreparation {

if (changelog.includes('SEMVER-MAJOR')) {
newVersion = `${lastTag.major + 1}.0.0`;
} else if (changelog.includes('SEMVER-MINOR')) {
} else if (changelog.includes('SEMVER-MINOR') || this.isLTSTransition) {
newVersion = `${lastTag.major}.${lastTag.minor + 1}.0`;
} else {
newVersion = `${lastTag.major}.${lastTag.minor}.${lastTag.patch + 1}`;
Expand Down Expand Up @@ -249,6 +268,14 @@ class ReleasePreparation {
]).trim();
}

async getLTSCodename(version) {
const { cli } = this;
return await cli.prompt(
'Enter the LTS code name for this release line:',
{ questionType: 'input', noSeparator: true, defaultAnswer: '' }
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

async updateREPLACEMEs() {
const { newVersion } = this;

Expand Down Expand Up @@ -289,6 +316,7 @@ class ReleasePreparation {
newVersion,
date,
isLTS,
isLTSTransition,
ltsCodename,
username
} = this;
Expand Down Expand Up @@ -321,7 +349,12 @@ class ReleasePreparation {
toAppend.push(`<a id="${newVersion}"></a>`);
toAppend.push(releaseHeader);
toAppend.push('### Notable Changes\n');
toAppend.push(notableChanges);
if (isLTSTransition) {
toAppend.push(`${getStartLTSBlurb(this)}\n`);
}
if (notableChanges.trim()) {
toAppend.push(notableChanges);
}
toAppend.push('### Commits\n');
toAppend.push(allCommits);
toAppend.push('');
Expand All @@ -347,7 +380,7 @@ class ReleasePreparation {
}

async updateNodeVersion() {
const { versionComponents } = this;
const { ltsCodename, versionComponents } = this;

const filePath = path.resolve('src', 'node_version.h');
const data = await fs.readFile(filePath, 'utf8');
Expand All @@ -364,7 +397,16 @@ class ReleasePreparation {
arr[idx] = '#define NODE_VERSION_IS_RELEASE 1';
} else if (line.includes('#define NODE_VERSION_IS_LTS')) {
this.isLTS = arr[idx].split(' ')[2] === '1';
this.ltsCodename = arr[idx + 1].split(' ')[2].slice(1, -1);
if (this.isLTSTransition) {
if (this.isLTS) {
throw new Error('Previous release was already marked as LTS.');
}
this.isLTS = true;
arr[idx] = '#define NODE_VERSION_IS_LTS 1';
arr[idx + 1] = `#define NODE_VERSION_LTS_CODENAME "${ltsCodename}"`;
} else {
this.ltsCodename = arr[idx + 1].split(' ')[2].slice(1, -1);
}
}
});

Expand All @@ -382,10 +424,17 @@ class ReleasePreparation {
writeJson(nmvFilePath, { NODE_MODULE_VERSION: nmvArray });
}

async updateTestProcessRelease(testFile) {
const data = await fs.readFile(testFile, { encoding: 'utf8' });
const updated = updateTestProcessRelease(data, this);
await fs.writeFile(testFile, updated);
}

async createReleaseCommit() {
const {
cli,
isLTS,
isLTSTransition,
ltsCodename,
newVersion,
isSecurityRelease,
Expand All @@ -405,6 +454,9 @@ class ReleasePreparation {
format: 'plaintext'
});
messageBody.push('Notable changes:\n\n');
if (isLTSTransition) {
messageBody.push(`${getStartLTSBlurb(this)}\n\n`);
}
messageBody.push(notableChanges);
messageBody.push('\nPR-URL: TODO');

Expand Down
46 changes: 46 additions & 0 deletions lib/release/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

function getStartLTSBlurb({ date, ltsCodename, versionComponents }) {
const dateFormat = { month: 'long', year: 'numeric' };
// TODO pull these from the schedule.json in the Release repo?
// Active LTS lasts for one year.
const mainDate = new Date(date);
mainDate.setMonth(mainDate.getMonth() + 12);
const mainStart = mainDate.toLocaleString('en-US', dateFormat);
// Maintenance LTS lasts another 18 months.
const eolDate = new Date(mainStart);
eolDate.setMonth(eolDate.getMonth() + 18);
const eol = eolDate.toLocaleString('en-US', dateFormat);
const { major } = versionComponents;
return [
/* eslint-disable max-len */
`This release marks the transition of Node.js ${major}.x into Long Term Support (LTS)`,
`with the codename '${ltsCodename}'. The ${major}.x release line now moves into "Active LTS"`,
`and will remain so until ${mainStart}. After that time, it will move into`,
`"Maintenance" until end of life in ${eol}.`
/* eslint-enable */
].join('\n');
}

function updateTestProcessRelease(test, { versionComponents, ltsCodename }) {
if (test.includes(ltsCodename)) {
return test;
}
const inLines = test.split('\n');
const outLines = [];
const { major, minor } = versionComponents;
for (const line of inLines) {
if (line === '} else {') {
outLines.push(`} else if (versionParts[0] === '${major}' ` +
`&& versionParts[1] >= ${minor}) {`
);
outLines.push(
` assert.strictEqual(process.release.lts, '${ltsCodename}');`
);
}
outLines.push(line);
}
return outLines.join('\n');
}

module.exports = { getStartLTSBlurb, updateTestProcessRelease };
26 changes: 26 additions & 0 deletions test/fixtures/release/expected-test-process-release.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

require('../common');

const assert = require('assert');
const versionParts = process.versions.node.split('.');

assert.strictEqual(process.release.name, 'node');

// It's expected that future LTS release lines will have additional
// branches in here
if (versionParts[0] === '4' && versionParts[1] >= 2) {
assert.strictEqual(process.release.lts, 'Argon');
} else if (versionParts[0] === '6' && versionParts[1] >= 9) {
assert.strictEqual(process.release.lts, 'Boron');
} else if (versionParts[0] === '8' && versionParts[1] >= 9) {
assert.strictEqual(process.release.lts, 'Carbon');
} else if (versionParts[0] === '10' && versionParts[1] >= 13) {
assert.strictEqual(process.release.lts, 'Dubnium');
} else if (versionParts[0] === '12' && versionParts[1] >= 13) {
assert.strictEqual(process.release.lts, 'Erbium');
} else if (versionParts[0] === '14' && versionParts[1] >= 15) {
assert.strictEqual(process.release.lts, 'Fermium');
} else {
assert.strictEqual(process.release.lts, undefined);
}
24 changes: 24 additions & 0 deletions test/fixtures/release/original-test-process-release.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

require('../common');

const assert = require('assert');
const versionParts = process.versions.node.split('.');

assert.strictEqual(process.release.name, 'node');

// It's expected that future LTS release lines will have additional
// branches in here
if (versionParts[0] === '4' && versionParts[1] >= 2) {
assert.strictEqual(process.release.lts, 'Argon');
} else if (versionParts[0] === '6' && versionParts[1] >= 9) {
assert.strictEqual(process.release.lts, 'Boron');
} else if (versionParts[0] === '8' && versionParts[1] >= 9) {
assert.strictEqual(process.release.lts, 'Carbon');
} else if (versionParts[0] === '10' && versionParts[1] >= 13) {
assert.strictEqual(process.release.lts, 'Dubnium');
} else if (versionParts[0] === '12' && versionParts[1] >= 13) {
assert.strictEqual(process.release.lts, 'Erbium');
} else {
assert.strictEqual(process.release.lts, undefined);
}
46 changes: 46 additions & 0 deletions test/unit/prepare_release.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

const assert = require('assert');
const { readFileSync } = require('fs');
const utils = require('../../lib/release/utils');

describe('prepare_release: utils.getStartLTSBlurb', () => {
it('generates first LTS release text with correct dates', () => {
const expected = [
/* eslint-disable max-len */
'This release marks the transition of Node.js 14.x into Long Term Support (LTS)',
'with the codename \'Fermium\'. The 14.x release line now moves into "Active LTS"',
'and will remain so until October 2021. After that time, it will move into',
'"Maintenance" until end of life in April 2023.'
/* eslint-enable max-len */
].join('\n');
const text = utils.getStartLTSBlurb({
date: '2020-10-27',
ltsCodename: 'Fermium',
versionComponents: { major: 14 }
});
assert.strictEqual(text, expected);
});
});

describe('prepare_release: utils.updateTestProcessRelease', () => {
it('inserts test a for a new LTS codename', () => {
const expected = readFileSync(
`${__dirname}/../fixtures/release/expected-test-process-release.js`,
{ encoding: 'utf8' }
);
const test = readFileSync(
`${__dirname}/../fixtures/release/original-test-process-release.js`,
{ encoding: 'utf8' }
);
const context = {
ltsCodename: 'Fermium',
versionComponents: {
major: 14,
minor: 15
}
};
const updated = utils.updateTestProcessRelease(test, context);
assert.strictEqual(updated, expected);
});
});