Skip to content

Commit

Permalink
chore(cdk-release): create our release notes as part of the build (#1…
Browse files Browse the repository at this point in the history
…6942)

Currently, our bump process creates `CHANGELOG.md` for the `master` branch, and
`CHANGELOG.v2.md` and `CHANGELOG.v2.alpha.md` on the `v2-main` branch. For the
latter, we need to combine the two changelogs into one for the GitHub release,
where all of the artifacts of the release (both aws-cdk-lib and all the alpha
modules) are bundled together.

As proposed in the [RFC], this combined format shows the stable/aws-cdk-lib
changes first, and then the alpha module changes under their own heading.  This
generated -- but not saved -- RELEASE_NOTES.md will be used by our publishing
pipeline to generate the GitHub release notes.

[RFC]: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0249-v2-experiments.md#changelog--release-notes

See companion PR - cdklabs/aws-delivlib#1044

fixes #16802

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
njlynch authored Oct 13, 2021
1 parent 7c73880 commit 2f0140e
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ yarn-error.log
.nzm-*

/.versionrc.json
RELEASE_NOTES.md
3 changes: 3 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,7 @@ if [ "$check_compat" == "true" ]; then
/bin/bash scripts/check-api-compatibility.sh
fi

# Create the release notes for the current version. These are ephemeral and not saved in source.
node ./scripts/create-release-notes.js

touch $BUILD_INDICATOR
2 changes: 1 addition & 1 deletion scripts/bump.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async function main() {
console.error("🎉 Calling our 'cdk-release' package to make the bump");
console.error("ℹ️ Set the LEGACY_BUMP env variable to use the old 'standard-version' bump instead");
const cdkRelease = require('@aws-cdk/cdk-release');
cdkRelease(opts);
cdkRelease.createRelease(opts);
}
}

Expand Down
18 changes: 18 additions & 0 deletions scripts/create-release-notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env node

const cdkRelease = require('@aws-cdk/cdk-release');
const ver = require('./resolve-version');

async function main() {
await cdkRelease.createReleaseNotes({
versionFile: ver.versionFile,
changelogFile: ver.changelogFile,
alphaChangelogFile: ver.alphaChangelogFile,
releaseNotesFile: 'RELEASE_NOTES.md',
});
}

main().catch(err => {
console.error(err.stack);
process.exit(1);
});
18 changes: 5 additions & 13 deletions tools/@aws-cdk/cdk-release/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { getConventionalCommitsFromGitHistory } from './conventional-commits';
import { defaults } from './defaults';
import { bump } from './lifecycles/bump';
import { writeChangelogs } from './lifecycles/changelog';
import { commit } from './lifecycles/commit';
import { debug, debugObject } from './private/print';
import { PackageInfo, ReleaseOptions, Versions } from './types';
import { PackageInfo, ReleaseOptions } from './types';
import { readVersion } from './versions';
// eslint-disable-next-line @typescript-eslint/no-require-imports
const lerna_project = require('@lerna/project');

module.exports = async function main(opts: ReleaseOptions): Promise<void> {
export * from './release-notes';

export async function createRelease(opts: ReleaseOptions): Promise<void> {
// handle the default options
const args: ReleaseOptions = {
...defaults,
Expand All @@ -34,15 +35,6 @@ module.exports = async function main(opts: ReleaseOptions): Promise<void> {
await commit(args, newVersion.stableVersion, [args.versionFile, ...changelogResults.map(r => r.filePath)]);
};

function readVersion(versionFile: string): Versions {
const versionPath = path.resolve(process.cwd(), versionFile);
const contents = JSON.parse(fs.readFileSync(versionPath, { encoding: 'utf-8' }));
return {
stableVersion: contents.version,
alphaVersion: contents.alphaVersion,
};
}

function getProjectPackageInfos(): PackageInfo[] {
const packages = lerna_project.Project.getPackagesSync();

Expand Down
2 changes: 1 addition & 1 deletion tools/@aws-cdk/cdk-release/lib/private/files.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from 'fs';

interface WriteFileOpts {
export interface WriteFileOpts {
readonly dryRun?: boolean;
}

Expand Down
67 changes: 67 additions & 0 deletions tools/@aws-cdk/cdk-release/lib/release-notes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
import parseChangelog = require('changelog-parser');
import { WriteFileOpts, writeFile } from './private/files';
import { debugObject, LoggingOptions } from './private/print';
import { Versions } from './types';
import { readVersion } from './versions';

export interface ReleaseNotesOpts {
/** path to the version file for the current branch (e.g., version.v2.json) */
versionFile: string;
/** path to the primary changelog file (e.g., 'CHANGELOG.v2.md') */
changelogFile: string;
/** (optional) path to the independent alpha changelog file (e.g., 'CHANGELOG.v2.alpha.md') */
alphaChangelogFile?: string;
/** path to write out the final release notes (e.g., 'RELEASE_NOTES.md'). */
releaseNotesFile: string;
}

/**
* Creates a release notes file from one (or more) changelog files for the current version.
* If an alpha version and alpha changelog file aren't present, this is identical to the contents
* of the (main) changelog for the current version. Otherwise, a combined release is put together
* from the contents of the stable and alpha changelogs.
*/
export async function createReleaseNotes(opts: ReleaseNotesOpts & LoggingOptions & WriteFileOpts) {
const currentVersion = readVersion(opts.versionFile);
debugObject(opts, 'Current version info', currentVersion);

writeFile(opts, opts.releaseNotesFile, await releaseNoteContents(currentVersion, opts));
}

async function releaseNoteContents(currentVersion: Versions, opts: ReleaseNotesOpts) {
const stableChangelogContents = await readChangelogSection(opts.changelogFile, currentVersion.stableVersion);
// If we don't have an alpha version and distinct alpha changelog, the release notes are just the main changelog section.
if (!opts.alphaChangelogFile || !currentVersion.alphaVersion) { return stableChangelogContents; }

const alphaChangelogContents = await readChangelogSection(opts.alphaChangelogFile, currentVersion.alphaVersion);

// See https://github.com/aws/aws-cdk-rfcs/blob/master/text/0249-v2-experiments.md#changelog--release-notes for format
return [
stableChangelogContents,
'---',
`## Alpha modules (${currentVersion.alphaVersion})`,
alphaChangelogContents,
].join('\n');
}

async function readChangelogSection(changelogFile: string, version: string) {
const changelog = await parseChangelog(changelogFile) as Changelog;
const entry = (changelog.versions || []).find(section => section.version === version);
if (!entry) {
throw new Error(`No changelog entry found for version ${version} in ${changelogFile}`);
}
return entry.body;
}

/** @types/changelog-parser only returns `object`; this is slightly more helpful */
interface Changelog {
title: string;
description: string;
versions?: ChangelogVersion[];
}
interface ChangelogVersion {
version: string;
title: string;
body: string;
}
12 changes: 12 additions & 0 deletions tools/@aws-cdk/cdk-release/lib/versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { Versions } from './types';

export function readVersion(versionFile: string): Versions {
const versionPath = path.resolve(process.cwd(), versionFile);
const contents = JSON.parse(fs.readFileSync(versionPath, { encoding: 'utf-8' }));
return {
stableVersion: contents.version,
alphaVersion: contents.alphaVersion,
};
}
10 changes: 6 additions & 4 deletions tools/@aws-cdk/cdk-release/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,26 @@
"devDependencies": {
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/changelog-parser": "^2.7.1",
"@types/fs-extra": "^8.1.2",
"@types/jest": "^26.0.24",
"@types/yargs": "^15.0.14",
"jest": "^26.6.3"
},
"dependencies": {
"@lerna/project": "^4.0.0",
"changelog-parser": "^2.8.0",
"conventional-changelog": "^3.1.24",
"conventional-changelog-config-spec": "^2.1.0",
"conventional-changelog-preset-loader": "^2.3.4",
"conventional-commits-parser": "^3.2.2",
"conventional-changelog-writer": "^4.1.0",
"conventional-commits-parser": "^3.2.2",
"detect-indent": "^6.1.0",
"detect-newline": "^3.1.0",
"fs-extra": "^9.1.0",
"git-raw-commits": "^2.0.10",
"semver": "^7.3.5",
"stringify-package": "^1.0.1",
"detect-indent": "^6.1.0",
"detect-newline": "^3.1.0"
"stringify-package": "^1.0.1"
},
"keywords": [
"aws",
Expand Down
61 changes: 61 additions & 0 deletions tools/@aws-cdk/cdk-release/test/release-notes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as files from '../lib/private/files';
import { createReleaseNotes } from '../lib/release-notes';
import * as versions from '../lib/versions';

/** MOCKS */
const mockWriteFile = jest.spyOn(files, 'writeFile').mockImplementation(() => jest.fn());
const mockReadVersion = jest.spyOn(versions, 'readVersion');
jest.mock('changelog-parser', () => { return jest.fn(); });
// eslint-disable-next-line @typescript-eslint/no-require-imports
const changelogParser = require('changelog-parser');
/** MOCKS */

beforeEach(() => { jest.resetAllMocks(); });

const DEFAULT_OPTS = {
changelogFile: 'CHANGELOG.md',
releaseNotesFile: 'RELEASE_NOTES.md',
versionFile: 'versions.json',
};

test('without alpha releases, only the stable changelog is returned', async () => {
mockReadVersion.mockImplementation((_) => { return { stableVersion: '1.2.3' }; });
mockChangelogOnceForVersion('1.2.3', 'foo');

await createReleaseNotes(DEFAULT_OPTS);

expectReleaseNotes('foo');
});

test('with alpha releases the contents of both are returned as separate sections', async () => {
mockReadVersion.mockImplementation((_) => { return { stableVersion: '1.2.3', alphaVersion: '1.2.3-alpha' }; });
mockChangelogOnceForVersion('1.2.3', 'foo'); // stable
mockChangelogOnceForVersion('1.2.3-alpha', 'bar'); // alpha

await createReleaseNotes({ ...DEFAULT_OPTS, alphaChangelogFile: 'CHANGELOG.alpha.md' });

expectReleaseNotes([
'foo',
'---',
'## Alpha modules (1.2.3-alpha)',
'bar',
]);
});

test('throws if no matching version is found in the changelog', async () => {
mockReadVersion.mockImplementation((_) => { return { stableVersion: '1.2.3' }; });
mockChangelogOnceForVersion('4.5.6', 'foo');

await expect(createReleaseNotes(DEFAULT_OPTS))
.rejects
.toThrow(/No changelog entry found for version 1.2.3 in CHANGELOG.md/);
});

function mockChangelogOnceForVersion(version: string, body: string) {
changelogParser.mockImplementationOnce((_: string) => { return { versions: [{ version, body }] }; });
}

function expectReleaseNotes(contents: string | string[]) {
const data = (typeof contents === 'string') ? contents : contents.join('\n');
expect(mockWriteFile).toBeCalledWith(expect.any(Object), 'RELEASE_NOTES.md', data);
}
27 changes: 25 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1583,7 +1583,7 @@
dependencies:
"@types/glob" "*"

"@types/aws-lambda@^8.10.84":
"@types/aws-lambda@^8.10.83", "@types/aws-lambda@^8.10.84":
version "8.10.84"
resolved "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.84.tgz#b1f391ceeb6908b28d8416d93f27afe8d1348d4e"
integrity sha512-5V78eLtmN0d4RA14hKDwcsMQRl3JotQJlhGFDBo/jdE2TyDFRaYwB/UmMUC4SzhSvRGn+YMkh7jGPnXi8COAng==
Expand Down Expand Up @@ -1621,6 +1621,11 @@
dependencies:
"@babel/types" "^7.3.0"

"@types/changelog-parser@^2.7.1":
version "2.7.1"
resolved "https://registry.npmjs.org/@types/changelog-parser/-/changelog-parser-2.7.1.tgz#da124373fc8abfb6951fef83718ea5f041fea527"
integrity sha512-OFZB7OlG6nrkcnvJhcyV2Zm/PUGk40oHyfaEBRjlm+ghrKxbFQI+xao/IzYL0G72fpLCTGGs3USrhe38/FF6QQ==

"@types/eslint@^7.28.1":
version "7.28.1"
resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.1.tgz#50b07747f1f84c2ba8cd394cf0fe0ba07afce320"
Expand Down Expand Up @@ -2705,6 +2710,14 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
ansi-styles "^4.1.0"
supports-color "^7.1.0"

changelog-parser@^2.8.0:
version "2.8.0"
resolved "https://registry.npmjs.org/changelog-parser/-/changelog-parser-2.8.0.tgz#c14293e3e8fab797913c722de965480198650108"
integrity sha512-ZtSwN0hY7t+WpvaXqqXz98RHCNhWX9HsvCRAv1aBLlqJ7BpKtqdM6Nu6JOiUhRAWR7Gov0aN0fUnmflTz0WgZg==
dependencies:
line-reader "^0.2.4"
remove-markdown "^0.2.2"

char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
Expand Down Expand Up @@ -6334,6 +6347,11 @@ lie@~3.3.0:
dependencies:
immediate "~3.0.5"

line-reader@^0.2.4:
version "0.2.4"
resolved "https://registry.npmjs.org/line-reader/-/line-reader-0.2.4.tgz#c4392b587dea38580c9678570e6e8e49fce52622"
integrity sha1-xDkrWH3qOFgMlnhXDm6OSfzlJiI=

lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
Expand Down Expand Up @@ -8211,6 +8229,11 @@ release-zalgo@^1.0.0:
dependencies:
es6-error "^4.0.1"

remove-markdown@^0.2.2:
version "0.2.2"
resolved "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.2.2.tgz#66b0ceeba9fb77ca9636bb1b0307ce21a32a12a6"
integrity sha1-ZrDO66n7d8qWNrsbAwfOIaMqEqY=

remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
Expand Down Expand Up @@ -9004,7 +9027,7 @@ symbol-tree@^3.2.4:
resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==

table@*, table@^6.0.9, table@^6.7.2:
table@*, table@^6.0.9, table@^6.7.1, table@^6.7.2:
version "6.7.2"
resolved "https://registry.npmjs.org/table/-/table-6.7.2.tgz#a8d39b9f5966693ca8b0feba270a78722cbaf3b0"
integrity sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==
Expand Down

0 comments on commit 2f0140e

Please sign in to comment.