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

chore(cdk-release): create our release notes as part of the build #16942

Merged
merged 3 commits into from
Oct 13, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
68 changes: 68 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,68 @@
// 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 [
'## aws-cdk-lib', // Assumption: we only have stable + alpha changelogs on v2+, where aws-cdk-lib is the stable module.
njlynch marked this conversation as resolved.
Show resolved Hide resolved
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
62 changes: 62 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,62 @@
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([
'## aws-cdk-lib',
'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);
}
Loading