diff --git a/.projenrc.ts b/.projenrc.ts index 803fc6624..518b283a5 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -376,9 +376,17 @@ new JsiiBuild(cloudAssemblySchema, { (() => { cloudAssemblySchema.preCompileTask.exec('tsx projenrc/update.ts'); + // This file will be generated at release time. It needs to be gitignored or it will + // fail projen's "no tamper" check, which means it must also be generated every build time. + // + // Crucially, this must also run during release after bumping, but that is satisfied already + // by making it part of preCompile, because that makes it run as part of projen build. + cloudAssemblySchema.preCompileTask.exec('tsx ../../../projenrc/copy-cli-version-to-assembly.task.ts'); + cloudAssemblySchema.gitignore.addPatterns('cli-version.json'); + cloudAssemblySchema.addPackageIgnore('*.ts'); cloudAssemblySchema.addPackageIgnore('!*.d.ts'); - cloudAssemblySchema.addPackageIgnore('** /scripts'); + cloudAssemblySchema.addPackageIgnore('**/scripts'); })(); ////////////////////////////////////////////////////////////////////// diff --git a/packages/@aws-cdk/cloud-assembly-schema/.gitignore b/packages/@aws-cdk/cloud-assembly-schema/.gitignore index 11bc0858f..382afa8f2 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.gitignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.gitignore @@ -46,3 +46,4 @@ jspm_packages/ !/.eslintrc.js .jsii tsconfig.json +cli-version.json diff --git a/packages/@aws-cdk/cloud-assembly-schema/.npmignore b/packages/@aws-cdk/cloud-assembly-schema/.npmignore index 18c782a18..a46b811d9 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.npmignore +++ b/packages/@aws-cdk/cloud-assembly-schema/.npmignore @@ -24,5 +24,5 @@ build-tools !.jsii *.ts !*.d.ts -** /scripts +**/scripts /.gitattributes diff --git a/packages/@aws-cdk/cloud-assembly-schema/.projen/tasks.json b/packages/@aws-cdk/cloud-assembly-schema/.projen/tasks.json index 38def4c17..3ba9086cb 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/.projen/tasks.json +++ b/packages/@aws-cdk/cloud-assembly-schema/.projen/tasks.json @@ -230,6 +230,9 @@ }, { "exec": "tsx projenrc/update.ts" + }, + { + "exec": "tsx ../../../projenrc/copy-cli-version-to-assembly.task.ts" } ] }, diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts index 400a983b9..bb1a3396d 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts @@ -13,6 +13,15 @@ import * as integ from './integ-tests'; // see exec.ts#createAssembly export const VERSION_MISMATCH: string = 'Cloud assembly schema version mismatch'; +/** + * CLI version is created at build and release time + * + * It needs to be .gitignore'd, otherwise the projen 'no uncommitted + * changes' self-check will fail, which means it needs to be generated + * at build time if it doesn't already exist. + */ +import CLI_VERSION = require('../cli-version.json'); + import ASSETS_SCHEMA = require('../schema/assets.schema.json'); import ASSEMBLY_SCHEMA = require('../schema/cloud-assembly.schema.json'); @@ -141,6 +150,14 @@ export class Manifest { return `${SCHEMA_VERSION.revision}.0.0`; } + /** + * Return the CLI version that supports this Cloud Assembly Schema version + */ + public static cliVersion(): string | undefined { + const version = CLI_VERSION.version; + return version ? version : undefined; + } + /** * Deprecated * @deprecated use `saveAssemblyManifest()` @@ -216,7 +233,11 @@ export class Manifest { schema: jsonschema.Schema, preprocess?: (obj: any) => any, ) { - let withVersion = { ...manifest, version: Manifest.version() }; + let withVersion = { + ...manifest, + version: Manifest.version(), + minimumCliVersion: Manifest.cliVersion(), + } satisfies assembly.AssemblyManifest; Manifest.validate(withVersion, schema); if (preprocess) { withVersion = preprocess(withVersion); diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts b/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts index a03e87abd..8e97bbdb8 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts +++ b/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -31,10 +32,36 @@ test('manifest save', () => { const saved = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })); - expect(saved).toEqual({ + expect(saved).toEqual(expect.objectContaining({ ...assemblyManifest, version: Manifest.version(), // version is forced - }); + })); +}); + +test('manifest contains minimum CLI version', () => { + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-tests')); + const manifestFile = path.join(outdir, 'manifest.json'); + + // This relies on the fact that the cli JSON version file is `require()`d, + // and that the 'require' below will find the same object in the module cache. + const cliVersionFile = require(`${__dirname}/../cli-version.json`); + cliVersionFile.version = '9.9.9'; + try { + const assemblyManifest: AssemblyManifest = { + version: 'version', + runtime: { + libraries: { lib1: '1.2.3' }, + }, + }; + + Manifest.saveAssemblyManifest(assemblyManifest, manifestFile); + + const saved = JSON.parse(fs.readFileSync(manifestFile, { encoding: 'utf-8' })); + + expect(saved.minimumCliVersion).toEqual('9.9.9'); + } finally { + cliVersionFile.version = ''; + } }); test('assumeRoleAdditionalOptions.RoleArn is validated in stack artifact', () => { diff --git a/projenrc/copy-cli-version-to-assembly.task.ts b/projenrc/copy-cli-version-to-assembly.task.ts new file mode 100644 index 000000000..4823789c2 --- /dev/null +++ b/projenrc/copy-cli-version-to-assembly.task.ts @@ -0,0 +1,24 @@ +import { promises as fs } from 'fs'; + +/** + * Copy the version from the CLI into the `@aws-cdk/cloud-assembly-schema` package at release time. + */ +async function main() { + const cliVersion = JSON.parse(await fs.readFile(`${__dirname}/../packages/aws-cdk/package.json`, 'utf8')).version; + + const cliVersionFile = `${__dirname}/../packages/@aws-cdk/cloud-assembly-schema/cli-version.json`; + + // We write an empty string if we're in "development mode" to show that we don't really have a version. + // It's not a missing field so that the `import` statement of that JSON file in TypeScript + // always knows the version field is there, and its value is a string. + const advertisedVersion = cliVersion !== '0.0.0' ? cliVersion : ''; + + await fs.writeFile(cliVersionFile, JSON.stringify({ version: advertisedVersion }), 'utf8'); +} + +main().catch(e => { + // this is effectively a mini-cli + // eslint-disable-next-line no-console + console.error(e); + process.exitCode = 1; +});