Skip to content

Commit e1d7efa

Browse files
authored
docs(cli): improve upgrade message (#18195)
This PR relates to #18024. It adds information on upgrading between major versions to the upgrade banner. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 65c9282 commit e1d7efa

File tree

3 files changed

+97
-20
lines changed

3 files changed

+97
-20
lines changed

packages/aws-cdk/lib/util/npm.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { exec as _exec } from 'child_process';
2+
import { promisify } from 'util';
3+
import * as semver from 'semver';
4+
import { debug } from '../../lib/logging';
5+
6+
const exec = promisify(_exec);
7+
8+
export async function getLatestVersionFromNpm(): Promise<string> {
9+
const { stdout, stderr } = await exec('npm view aws-cdk version');
10+
if (stderr && stderr.trim().length > 0) {
11+
debug(`The 'npm view' command generated an error stream with content [${stderr.trim()}]`);
12+
}
13+
const latestVersion = stdout.trim();
14+
if (!semver.valid(latestVersion)) {
15+
throw new Error(`npm returned an invalid semver ${latestVersion}`);
16+
}
17+
18+
return latestVersion;
19+
}

packages/aws-cdk/lib/version.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { exec as _exec } from 'child_process';
21
import * as path from 'path';
3-
import { promisify } from 'util';
42
import * as colors from 'colors/safe';
53
import * as fs from 'fs-extra';
64
import * as semver from 'semver';
75
import { debug, print } from '../lib/logging';
86
import { formatAsBanner } from '../lib/util/console-formatters';
97
import { cdkCacheDir } from './util/directories';
8+
import { getLatestVersionFromNpm } from './util/npm';
109

1110
const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60;
1211

13-
const exec = promisify(_exec);
12+
const UPGRADE_DOCUMENTATION_LINKS: Record<number, string> = {
13+
1: 'https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html',
14+
};
1415

1516
export const DISPLAY_VERSION = `${versionNumber()} (build ${commit()})`;
1617

@@ -79,14 +80,7 @@ export async function latestVersionIfHigher(currentVersion: string, cacheFile: V
7980
return null;
8081
}
8182

82-
const { stdout, stderr } = await exec('npm view aws-cdk version');
83-
if (stderr && stderr.trim().length > 0) {
84-
debug(`The 'npm view' command generated an error stream with content [${stderr.trim()}]`);
85-
}
86-
const latestVersion = stdout.trim();
87-
if (!semver.valid(latestVersion)) {
88-
throw new Error(`npm returned an invalid semver ${latestVersion}`);
89-
}
83+
const latestVersion = await getLatestVersionFromNpm();
9084
const isNewer = semver.gt(latestVersion, currentVersion);
9185
await cacheFile.update(latestVersion);
9286

@@ -97,19 +91,30 @@ export async function latestVersionIfHigher(currentVersion: string, cacheFile: V
9791
}
9892
}
9993

100-
export async function displayVersionMessage(): Promise<void> {
94+
function getMajorVersionUpgradeMessage(currentVersion: string): string | void {
95+
const currentMajorVersion = semver.major(currentVersion);
96+
if (UPGRADE_DOCUMENTATION_LINKS[currentMajorVersion]) {
97+
return `Information about upgrading from version ${currentMajorVersion}.x to version ${currentMajorVersion + 1}.x is available here: ${UPGRADE_DOCUMENTATION_LINKS[currentMajorVersion]}`;
98+
}
99+
}
100+
101+
function getVersionMessage(currentVersion: string, laterVersion: string): string[] {
102+
return [
103+
`Newer version of CDK is available [${colors.green(laterVersion as string)}]`,
104+
getMajorVersionUpgradeMessage(currentVersion),
105+
'Upgrade recommended (npm install -g aws-cdk)',
106+
].filter(Boolean) as string[];
107+
}
108+
109+
export async function displayVersionMessage(currentVersion = versionNumber(), versionCheckCache?: VersionCheckTTL): Promise<void> {
101110
if (!process.stdout.isTTY || process.env.CDK_DISABLE_VERSION_CHECK) {
102111
return;
103112
}
104113

105114
try {
106-
const versionCheckCache = new VersionCheckTTL();
107-
const laterVersion = await latestVersionIfHigher(versionNumber(), versionCheckCache);
115+
const laterVersion = await latestVersionIfHigher(currentVersion, versionCheckCache ?? new VersionCheckTTL());
108116
if (laterVersion) {
109-
const bannerMsg = formatAsBanner([
110-
`Newer version of CDK is available [${colors.green(laterVersion as string)}]`,
111-
'Upgrade recommended (npm install -g aws-cdk)',
112-
]);
117+
const bannerMsg = formatAsBanner(getVersionMessage(currentVersion, laterVersion));
113118
bannerMsg.forEach((e) => print(e));
114119
}
115120
} catch (err) {

packages/aws-cdk/test/version.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { promisify } from 'util';
44
import * as fs from 'fs-extra';
55
import * as sinon from 'sinon';
66
import * as logging from '../lib/logging';
7+
import * as npm from '../lib/util/npm';
78
import { latestVersionIfHigher, VersionCheckTTL, displayVersionMessage } from '../lib/version';
89

910
jest.setTimeout(10_000);
@@ -77,9 +78,61 @@ test('No Version specified for storage in the TTL file', async () => {
7778
});
7879

7980
test('Skip version check if environment variable is set', async () => {
80-
process.stdout.isTTY = true;
81-
process.env.CDK_DISABLE_VERSION_CHECK = '1';
81+
sinon.stub(process, 'stdout').value({ ...process.stdout, isTTY: true });
82+
sinon.stub(process, 'env').value({ ...process.env, CDK_DISABLE_VERSION_CHECK: '1' });
8283
const printStub = sinon.stub(logging, 'print');
8384
await displayVersionMessage();
8485
expect(printStub.called).toEqual(false);
8586
});
87+
88+
describe('version message', () => {
89+
let previousIsTty: true | undefined;
90+
beforeAll(() => {
91+
previousIsTty = process.stdout.isTTY;
92+
process.stdout.isTTY = true;
93+
});
94+
95+
afterAll(() => {
96+
process.stdout.isTTY = previousIsTty;
97+
});
98+
99+
test('Prints a message when a new version is available', async () => {
100+
// Given the current version is 1.0.0 and the latest version is 1.1.0
101+
const currentVersion = '1.0.0';
102+
jest.spyOn(npm, 'getLatestVersionFromNpm').mockResolvedValue('1.1.0');
103+
const printSpy = jest.spyOn(logging, 'print');
104+
105+
// When displayVersionMessage is called
106+
await displayVersionMessage(currentVersion, new VersionCheckTTL(tmpfile(), 0));
107+
108+
// Then the new version message is printed to stdout
109+
expect(printSpy).toHaveBeenCalledWith(expect.stringContaining('1.1.0'));
110+
});
111+
112+
test('Includes major upgrade documentation when available', async() => {
113+
// Given the current version is 1.0.0 and the latest version is 2.0.0
114+
const currentVersion = '1.0.0';
115+
jest.spyOn(npm, 'getLatestVersionFromNpm').mockResolvedValue('2.0.0');
116+
const printSpy = jest.spyOn(logging, 'print');
117+
118+
// When displayVersionMessage is called
119+
await displayVersionMessage(currentVersion, new VersionCheckTTL(tmpfile(), 0));
120+
121+
// Then the V1 -> V2 documentation is printed
122+
expect(printSpy).toHaveBeenCalledWith(expect.stringContaining('Information about upgrading from version 1.x to version 2.x is available here: https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html'));
123+
});
124+
125+
test('Does not include major upgrade documentation when unavailable', async() => {
126+
// Given current version is 99.0.0 and the latest version is 100.0.0
127+
const currentVersion = '99.0.0';
128+
jest.spyOn(npm, 'getLatestVersionFromNpm').mockResolvedValue('100.0.0');
129+
const printSpy = jest.spyOn(logging, 'print');
130+
131+
// When displayVersionMessage is called
132+
await displayVersionMessage(currentVersion, new VersionCheckTTL(tmpfile(), 0));
133+
134+
// Then no upgrade documentation is printed
135+
expect(printSpy).toHaveBeenCalledWith(expect.stringContaining('100.0.0'));
136+
expect(printSpy).not.toHaveBeenCalledWith(expect.stringContaining('Information about upgrading from 99.x to 100.x'));
137+
});
138+
});

0 commit comments

Comments
 (0)