diff --git a/.gitignore b/.gitignore index 96e153ea6e266..03b7512a00c05 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ yarn-error.log # Cloud9 .c9 +/.versionrc.json diff --git a/.versionrc.json b/.versionrc.json deleted file mode 100644 index 3178955551057..0000000000000 --- a/.versionrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "skip": { "tag": true }, - "packageFiles": [ { "filename": "lerna.json", "type": "json" } ], - "bumpFiles": [ { "filename": "lerna.json", "type": "json" } ] -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c5137c77a4a5..b538f7c009ee0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -779,24 +779,59 @@ CDK](https://github.com/aws/aws-cdk/issues/3398) we will either remove the legacy behavior or flip the logic for all these features and then reset the `FEATURE_FLAGS` map for the next cycle. -### Versioning - -All `package.json` files in this repo use a stable marker version of `0.0.0`. -This means that when you declare dependencies, you should always use `0.0.0`. -This makes it easier for us to bump a new version (the `bump.sh` script will -just update the central version and create a CHANGELOG entry) and also reduces -the chance of merge conflicts after a new version is released. - -Additional scripts that take part in the versioning mechanism: - -- `scripts/get-version.js` can be used to obtain the actual version of the repo. - You can use either from JavaScript code by `require('./scripts/get-version')` - or from a shell script `node -p "require('./scripts/get-version')"`. -- `scripts/get-version-marker.js` returns `0.0.0` and used to DRY the version - marker. -- `scripts/align-version.sh` and `scripts/align-version.js` are used to align - all package.json files in the repo to the official version. This script is - invoked in CI builds and should not be used inside a development environment. +### Versioning and Release + +The `release.json` file at the root of the repo determines which release line +this branch belongs to. + +```js +{ + "majorVersion": 1 | 2, + "releaseType": "stable" | "alpha" | "rc" +} +``` + +To reduce merge conflicts in automatic merges between version branches, the +current version number is stored under `version.vNN.json` (where `NN` is +`majorVersion`) and changelogs are stored under `CHANGELOG.NN.md` (for +historical reasons, the changelog for 1.x is under `CHANGELOG.md`). When we +fork to a new release branch (e.g. `v2-main`), we will update `release.json` in +this branch to reflect the new version line, and this information will be used +to determine how releases are cut. + +The actual `version` field in all `package.json` files should always be `0.0.0`. +This means that local development builds will use version `0.0.0` instead of the +official version from the version file. + +#### `./bump.sh` + +This script uses [standard-version] to update the version in `version.vNN.json` +to the next version. By default it will perform a **minor** bump, but `./bump.sh +patch` can be used to perform a patch release if that's needed. + +This script will also update the relevant changelog file. + +[standard-version]: https://github.com/conventional-changelog/standard-version + +#### `scripts/resolve-version.js` + +The script evaluates evaluates the configuration in `release.json` and exports an +object like this: + +```js +{ + version: '2.0.0-alpha.1', // the current version + versionFile: 'version.v2.json', // the version file + changelogFile: 'CHANGELOG.v2.md', // changelog file name + prerelease: 'alpha', // prerelease tag (undefined for stable) + marker: '0.0.0' // version marker in package.json files +} +``` + +#### scripts/align-version.sh + +In official builds, the `scripts/align-version.sh` is used to update all +`package.json` files based on the version from `version.vNN.json`. ## Troubleshooting diff --git a/bump.sh b/bump.sh index 028779e5a748d..750d452da496d 100755 --- a/bump.sh +++ b/bump.sh @@ -13,21 +13,4 @@ # # -------------------------------------------------------------------------------------------------- set -euo pipefail -version=${1:-minor} - -echo "Starting ${version} version bump" - -# /bin/bash ./install.sh - -# Generate CHANGELOG and create a commit (see .versionrc.json) -npx standard-version --release-as ${version} - -# I am sorry. -# -# I've gone diving through the code of `conventional-changelog` to see if there -# was a way to configure the string and ultimately I decided that a 'sed' was the simpler -# way to go. -sed -i.tmp -e 's/BREAKING CHANGES$/BREAKING CHANGES TO EXPERIMENTAL FEATURES/' CHANGELOG.md -rm CHANGELOG.md.tmp -git add CHANGELOG.md -git commit --amend --no-edit +./scripts/bump.js ${1:-minor} diff --git a/lerna.json b/lerna.json index 16f32cc984c13..0fcae573a32ae 100644 --- a/lerna.json +++ b/lerna.json @@ -8,8 +8,9 @@ "packages/@aws-cdk-containers/*", "packages/@monocdk-experiment/*", "packages/@aws-cdk/*/lambda-packages/*", - "tools/*" + "tools/*", + "scripts/script-tests" ], "rejectCycles": "true", - "version": "1.71.0" + "version": "0.0.0" } diff --git a/pack.sh b/pack.sh index 02b901f141273..a61a461ce9f3e 100755 --- a/pack.sh +++ b/pack.sh @@ -7,6 +7,11 @@ export PATH=$PWD/node_modules/.bin:$PATH export NODE_OPTIONS="--max-old-space-size=4096 ${NODE_OPTIONS:-}" root=$PWD +# Get version and changelog file name (these require that .versionrc.json would have been generated) +version=$(node -p "require('./scripts/resolve-version').version") +changelog_file=$(node -p "require('./scripts/resolve-version').changelogFile") +marker=$(node -p "require('./scripts/resolve-version').marker") + PACMAK=${PACMAK:-jsii-pacmak} ROSETTA=${ROSETTA:-jsii-rosetta} TMPDIR=${TMPDIR:-$(dirname $(mktemp -u))} @@ -57,15 +62,6 @@ done # Remove a JSII aggregate POM that may have snuk past rm -rf dist/java/software/amazon/jsii -# Get version -version="$(node -p "require('./scripts/get-version')")" - -# Ensure we don't publish anything beyond 1.x for now -if [[ ! "${version}" == "1."* ]]; then - echo "ERROR: accidentally releasing a major version? Expecting repo version to start with '1.' but got '${version}'" - exit 1 -fi - # Get commit from CodePipeline (or git, if we are in CodeBuild) # If CODEBUILD_RESOLVED_SOURCE_VERSION is not defined (i.e. local # build or CodePipeline build), use the HEAD commit hash). @@ -83,12 +79,11 @@ cat > ${distdir}/build.json < { + console.error(err.stack); + process.exit(1); +}); diff --git a/scripts/changelog-experimental-fix.sh b/scripts/changelog-experimental-fix.sh new file mode 100755 index 0000000000000..15284ac0c69bc --- /dev/null +++ b/scripts/changelog-experimental-fix.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail +changelog="${1:-}" + +if [ -z "${changelog}" ]; then + echo "Usage: $0 CHANGELOG.md" + exit 1 +fi + +sed -i.tmp -e 's/BREAKING CHANGES$/BREAKING CHANGES TO EXPERIMENTAL FEATURES/' ${changelog} +rm ${changelog}.tmp diff --git a/scripts/get-version-marker.js b/scripts/get-version-marker.js deleted file mode 100644 index e5f8c49806a67..0000000000000 --- a/scripts/get-version-marker.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Returns the version marker used to indicate this is a local dependency. - * - * Usage: - * - * const version = require('./get-version-marker'); - * - * Or from the command line: - * - * node -p require('./get-version-marker') - * - */ -module.exports = '0.0.0'; diff --git a/scripts/get-version.js b/scripts/get-version.js deleted file mode 100644 index 9e6972582c427..0000000000000 --- a/scripts/get-version.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Returns the current repo version. - * - * Usage: - * - * const version = require('./get-version'); - * - * Or from the command line: - * - * node -p require('./get-version') - * - */ -const versionFile = require('../.versionrc.json').packageFiles[0].filename; -if (!versionFile) { - throw new Error(`unable to determine version filename from .versionrc.json at the root of the repo`); -} - -module.exports = require(`../${versionFile}`).version; diff --git a/scripts/resolve-version-lib.js b/scripts/resolve-version-lib.js new file mode 100755 index 0000000000000..a4b3d1d9f22b4 --- /dev/null +++ b/scripts/resolve-version-lib.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +const path = require('path'); +const fs = require('fs'); + +//============================================================= +// UNIT TESTS: tools/script-tests/test/resolve-version.test.js +//============================================================= + +function resolveVersion(rootdir) { + const ALLOWED_RELEASE_TYPES = [ 'alpha', 'rc', 'stable' ]; + const MIN_MAJOR = 1, MAX_MAJOR = 2; // extra safety: update to allow new major versions + + // + // parse release.json + // + const releaseFile = path.join(rootdir, 'release.json'); + const releaseConfig = require(releaseFile); + const majorVersion = releaseConfig.majorVersion; + const releaseType = releaseConfig.releaseType; + if (!majorVersion) { throw new Error(`"majorVersion"" must be defined in ${releaseFile}`); } + if (!releaseType) { throw new Error(`"releaseType" must be defined in ${releaseFile}`); } + if (typeof(majorVersion) !== 'number') { throw new Error(`majorVersion=${majorVersion} must be a number`); } + if (majorVersion < MIN_MAJOR || majorVersion > MAX_MAJOR) { throw new Error(`majorVersion=${majorVersion} is an unsupported major version (should be between ${MIN_MAJOR} and ${MAX_MAJOR})`); } + if (!ALLOWED_RELEASE_TYPES.includes(releaseType)) { throw new Error(`releaseType=${releaseType} is not allowed. Allowed values: ${ALLOWED_RELEASE_TYPES.join(',')}`); } + + // + // resolve and check that we have a version file + // + + const versionFile = `version.v${majorVersion}.json`; + const versionFilePath = path.join(rootdir, versionFile); + if (!fs.existsSync(versionFilePath)) { + throw new Error(`unable to find version file ${versionFile} for major version ${majorVersion}`); + } + + // + // validate that current version matches the requirements + // + + const currentVersion = require(versionFilePath).version; + console.error(`current version: ${currentVersion}`); + if (!currentVersion.startsWith(`${majorVersion}.`)) { + throw new Error(`current version "${currentVersion}" does not use the expected major version ${majorVersion}`); + } + if (releaseType === 'stable') { + if (currentVersion.includes('-')) { + throw new Error(`found pre-release tag in version specified in ${versionFile} is ${currentVersion} but "releaseType"" is set to "stable"`); + } + } else { + if (!currentVersion.includes(`-${releaseType}.`)) { + throw new Error(`could not find pre-release tag "${releaseType}" in current version "${currentVersion}" defined in ${versionFile}`); + } + } + + // + // determine changelog file name + // + + const changelogFile = majorVersion === 1 + ? 'CHANGELOG.md' + : `CHANGELOG.v${majorVersion}.md`; + + // + // export all of it + // + + return { + version: currentVersion, + versionFile: versionFile, + changelogFile: changelogFile, + prerelease: releaseType !== 'stable' ? releaseType : undefined, + marker: '0.0.0', + }; +} + +module.exports = resolveVersion; \ No newline at end of file diff --git a/scripts/resolve-version.js b/scripts/resolve-version.js new file mode 100755 index 0000000000000..60ee3a018a3fe --- /dev/null +++ b/scripts/resolve-version.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +const path = require('path'); +const ROOTDIR = path.resolve(__dirname, '..'); +const resolveVersion = require('./resolve-version-lib'); +module.exports = resolveVersion(ROOTDIR); diff --git a/scripts/script-tests/.gitignore b/scripts/script-tests/.gitignore new file mode 100644 index 0000000000000..dfd5365951031 --- /dev/null +++ b/scripts/script-tests/.gitignore @@ -0,0 +1,8 @@ + +.LAST_BUILD +*.snk +junit.xml +.nyc_output +coverage +nyc.config.js +!.eslintrc.js \ No newline at end of file diff --git a/scripts/script-tests/README.md b/scripts/script-tests/README.md new file mode 100644 index 0000000000000..a819fff580b1e --- /dev/null +++ b/scripts/script-tests/README.md @@ -0,0 +1,3 @@ +# script tests + +This directory includes tests for scripts under `./scripts`. \ No newline at end of file diff --git a/scripts/script-tests/package.json b/scripts/script-tests/package.json new file mode 100644 index 0000000000000..2c6d0ff48e94a --- /dev/null +++ b/scripts/script-tests/package.json @@ -0,0 +1,15 @@ +{ + "name": "script-tests", + "private": true, + "version": "0.0.0", + "description": "various tests for development and build scripts", + "scripts": { + "build": "echo ok", + "test": "jest", + "build+test": "npm run build && npm test", + "build+test+package": "npm run build+test" + }, + "devDependencies": { + "jest": "^26.6.2" + } +} diff --git a/scripts/script-tests/resolve-version.test.js b/scripts/script-tests/resolve-version.test.js new file mode 100644 index 0000000000000..67621d4fc56af --- /dev/null +++ b/scripts/script-tests/resolve-version.test.js @@ -0,0 +1,147 @@ +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const resolveVersion = require('../resolve-version-lib'); + +beforeAll(() => spyOn(console, 'error')); + +happy({ + name: 'stable release', + inputs: { + 'release.json': { majorVersion: 2, releaseType: 'stable' }, + 'version.v2.json': { version: '2.1.0' }, + }, + expected: { + changelogFile: 'CHANGELOG.v2.md', + marker: '0.0.0', + prerelease: undefined, + version: '2.1.0', + versionFile: 'version.v2.json' + } +}); + +happy({ + name: 'alpha releases', + inputs: { + 'release.json': { majorVersion: 2, releaseType: 'alpha' }, + 'version.v2.json': { version: '2.1.0-alpha.0' }, + }, + expected: { + changelogFile: 'CHANGELOG.v2.md', + marker: '0.0.0', + prerelease: 'alpha', + version: '2.1.0-alpha.0', + versionFile: 'version.v2.json' + } +}); + +happy({ + name: 'rc releases', + inputs: { + 'release.json': { majorVersion: 2, releaseType: 'rc' }, + 'version.v2.json': { version: '2.1.0-rc.0' }, + }, + expected: { + changelogFile: 'CHANGELOG.v2.md', + marker: '0.0.0', + prerelease: 'rc', + version: '2.1.0-rc.0', + versionFile: 'version.v2.json' + } +}); + +happy({ + name: 'v1 changelog is still called CHANGELOG.md for backwards compatibility', + inputs: { + 'release.json': { majorVersion: 1, releaseType: 'stable' }, + 'version.v1.json': { version: '1.72.0' } + }, + expected: { + changelogFile: 'CHANGELOG.md', + marker: '0.0.0', + prerelease: undefined, + version: '1.72.0', + versionFile: 'version.v1.json' + } +}); + +failure({ + name: 'invalid release type', + inputs: { 'release.json': { majorVersion: 2, releaseType: 'build' } }, + expected: 'releaseType=build is not allowed. Allowed values: alpha,rc,stable' +}); + +failure({ + name: 'invalid major version (less then min)', + inputs: { 'release.json': { majorVersion: -1, releaseType: 'rc' } }, + expected: 'majorVersion=-1 is an unsupported major version (should be between 1 and 2)' +}); + +failure({ + name: 'invalid major version (over max)', + inputs: { 'release.json': { majorVersion: 3, releaseType: 'rc' } }, + expected: 'majorVersion=3 is an unsupported major version (should be between 1 and 2)' +}); + +failure({ + name: 'invalid major version (non-number)', + inputs: { 'release.json': { majorVersion: '2', releaseType: 'rc' } }, + expected: 'majorVersion=2 must be a number' +}); + +failure({ + name: 'no version file', + inputs: { 'release.json': { majorVersion: 2, releaseType: 'alpha' } }, + expected: 'unable to find version file version.v2.json for major version 2' +}); + +failure({ + name: 'actual version not the right major', + inputs: { + 'release.json': { majorVersion: 1, releaseType: 'stable' }, + 'version.v1.json': { version: '2.0.0' } + }, + expected: 'current version "2.0.0" does not use the expected major version 1' +}); + +failure({ + name: 'actual version not the right pre-release', + inputs: { + 'release.json': { majorVersion: 2, releaseType: 'alpha' }, + 'version.v2.json': { version: '2.0.0-rc.0' } + }, + expected: 'could not find pre-release tag "alpha" in current version "2.0.0-rc.0" defined in version.v2.json' +}); + +failure({ + name: 'actual version not the right pre-release (stable)', + inputs: { + 'release.json': { majorVersion: 2, releaseType: 'stable' }, + 'version.v2.json': { version: '2.0.0-alpha.0' } + }, + expected: 'found pre-release tag in version specified in version.v2.json is 2.0.0-alpha.0 but "releaseType"" is set to "stable"' +}); + +function happy({ name, inputs, expected } = opts) { + test(name, () => { + const tmpdir = stage(inputs); + const actual = resolveVersion(tmpdir); + expect(actual).toStrictEqual(expected); + }); +} + +function failure({ name, inputs, expected } = opts) { + test(name, () => { + const tmpdir = stage(inputs); + expect(() => resolveVersion(tmpdir)).toThrow(expected); + }); +} + +function stage(inputs) { + const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'resolve-version-')); + for (const [ name, contents ] of Object.entries(inputs)) { + const data = typeof(contents) === 'string' ? contents : JSON.stringify(contents); + fs.writeFileSync(path.join(tmpdir, name), data); + } + return tmpdir; +} diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 23c791c4cac23..9c35dc39ca0f0 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -1499,9 +1499,14 @@ function hasIntegTests(pkg: PackageJson) { * Return whether this package should use CDK build tools */ function shouldUseCDKBuildTools(pkg: PackageJson) { - // The packages that DON'T use CDKBuildTools are the package itself - // and the packages used by it. - return pkg.packageName !== 'cdk-build-tools' && pkg.packageName !== 'merkle-build' && pkg.packageName !== 'awslint'; + const exclude = [ + 'cdk-build-tools', + 'merkle-build', + 'awslint', + 'script-tests', + ]; + + return !exclude.includes(pkg.packageName); } function repoRoot(dir: string) { diff --git a/version.v1.json b/version.v1.json new file mode 100644 index 0000000000000..003975241dcee --- /dev/null +++ b/version.v1.json @@ -0,0 +1,3 @@ +{ + "version": "1.71.0" +} \ No newline at end of file