Skip to content

Commit

Permalink
feat: record CLI version in manifest package (#184)
Browse files Browse the repository at this point in the history
Record the current CLI version in the manifest package when we release
the manifest.

This by itself will not trigger a new release of the
`cloud-assembly-schema` package, so the version only gets recorded (and
published!) when there is a new version of `cloud-assembly-schema` to
publish anyway, and the version will only be displayed back to the user
if there is an incompatibility of the major version number.

That means we won't necessarily show the *lowest* possible CLI version
number when the user needs to upgrade, but we'll show a valid CLI
version number that will definitely support the given manifest.

---
By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license

---------

Signed-off-by: github-actions <[email protected]>
Co-authored-by: github-actions <[email protected]>
  • Loading branch information
rix0rrr and github-actions authored Mar 7, 2025
1 parent a6d4516 commit ef550a0
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 5 deletions.
10 changes: 9 additions & 1 deletion .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
})();

//////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/cloud-assembly-schema/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/@aws-cdk/cloud-assembly-schema/.npmignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/@aws-cdk/cloud-assembly-schema/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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()`
Expand Down Expand Up @@ -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);
Expand Down
31 changes: 29 additions & 2 deletions packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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', () => {
Expand Down
24 changes: 24 additions & 0 deletions projenrc/copy-cli-version-to-assembly.task.ts
Original file line number Diff line number Diff line change
@@ -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;
});

0 comments on commit ef550a0

Please sign in to comment.