Skip to content

Commit 286b063

Browse files
authored
refactor(cli): split version functions into those that display and those that do not (#709)
Pulled out of #631. Needed so that we can access version functions within the IoHost without offending the circular dependency checker (which checks file imports) --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent 3ceae66 commit 286b063

File tree

7 files changed

+135
-132
lines changed

7 files changed

+135
-132
lines changed

packages/aws-cdk/lib/cli/cli.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ChangeSetDeployment, DeploymentMethod, DirectDeployment } from '@a
44
import { ToolkitError } from '@aws-cdk/toolkit-lib';
55
import * as chalk from 'chalk';
66
import { CdkToolkit, AssetBuildTime } from './cdk-toolkit';
7+
import { displayVersionMessage } from './display-version';
78
import type { IoMessageLevel } from './io-host';
89
import { CliIoHost } from './io-host';
910
import { parseCommandLineArguments } from './parse-command-line-arguments';
@@ -12,7 +13,6 @@ import { prettyPrintError } from './pretty-print-error';
1213
import { GLOBAL_PLUGIN_HOST } from './singleton-plugin-host';
1314
import type { Command } from './user-configuration';
1415
import { Configuration } from './user-configuration';
15-
import * as version from './version';
1616
import { asIoHelper } from '../../lib/api-private';
1717
import type { IReadLock } from '../api';
1818
import { ToolkitInfo, Notices } from '../api';
@@ -30,6 +30,7 @@ import { getMigrateScanType } from '../commands/migrate';
3030
import { execProgram, CloudExecutable } from '../cxapp';
3131
import type { StackSelector, Synthesizer } from '../cxapp';
3232
import { ProxyAgentProvider } from './proxy-agent';
33+
import { isDeveloperBuildVersion, versionWithBuild, versionNumber } from './version';
3334

3435
if (!process.stdout.isTTY) {
3536
// Disable chalk color highlighting
@@ -78,7 +79,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
7879
await ioHost.defaults.debug(`Error while checking for platform warnings: ${e}`);
7980
}
8081

81-
await ioHost.defaults.debug('CDK Toolkit CLI version:', version.displayVersion());
82+
await ioHost.defaults.debug('CDK Toolkit CLI version:', versionWithBuild());
8283
await ioHost.defaults.debug('Command line arguments:', argv);
8384

8485
const configuration = await Configuration.fromArgsAndFiles(ioHelper,
@@ -103,7 +104,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
103104
context: configuration.context,
104105
output: configuration.settings.get(['outdir']),
105106
httpOptions: { agent: proxyAgent },
106-
cliVersion: version.versionNumber(),
107+
cliVersion: versionNumber(),
107108
});
108109
const refreshNotices = (async () => {
109110
// the cdk notices command has it's own refresh
@@ -164,7 +165,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
164165
await outDirLock?.release();
165166

166167
// Do PSAs here
167-
await version.displayVersionMessage(ioHelper);
168+
await displayVersionMessage(ioHelper);
168169

169170
await refreshNotices;
170171
if (cmd === 'notices') {
@@ -499,7 +500,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
499500
});
500501
case 'version':
501502
ioHost.currentAction = 'version';
502-
return ioHost.defaults.result(version.displayVersion());
503+
return ioHost.defaults.result(versionWithBuild());
503504

504505
default:
505506
throw new ToolkitError('Unknown command: ' + command);
@@ -637,7 +638,7 @@ export function cli(args: string[] = process.argv.slice(2)) {
637638
.catch((err) => {
638639
// Log the stack trace if we're on a developer workstation. Otherwise this will be into a minified
639640
// file and the printed code line and stack trace are huge and useless.
640-
prettyPrintError(err, version.isDeveloperBuild());
641+
prettyPrintError(err, isDeveloperBuildVersion());
641642
process.exitCode = 1;
642643
});
643644
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import * as path from 'path';
2+
import { ToolkitError } from '@aws-cdk/toolkit-lib';
3+
import * as chalk from 'chalk';
4+
import * as fs from 'fs-extra';
5+
import * as semver from 'semver';
6+
import type { IoHelper } from '../api-private';
7+
import { cdkCacheDir, versionNumber } from '../util';
8+
import { formatAsBanner } from './util/console-formatters';
9+
import { execNpmView } from './util/npm';
10+
11+
const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60;
12+
13+
const UPGRADE_DOCUMENTATION_LINKS: Record<number, string> = {
14+
1: 'https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html',
15+
};
16+
17+
export class VersionCheckTTL {
18+
public static timestampFilePath(): string {
19+
// Using the same path from account-cache.ts
20+
return path.join(cdkCacheDir(), 'repo-version-ttl');
21+
}
22+
23+
private readonly file: string;
24+
25+
// File modify times are accurate only to the second
26+
private readonly ttlSecs: number;
27+
28+
constructor(file?: string, ttlSecs?: number) {
29+
this.file = file || VersionCheckTTL.timestampFilePath();
30+
try {
31+
fs.mkdirsSync(path.dirname(this.file));
32+
fs.accessSync(path.dirname(this.file), fs.constants.W_OK);
33+
} catch {
34+
throw new ToolkitError(`Directory (${path.dirname(this.file)}) is not writable.`);
35+
}
36+
this.ttlSecs = ttlSecs || ONE_DAY_IN_SECONDS;
37+
}
38+
39+
public async hasExpired(): Promise<boolean> {
40+
try {
41+
const lastCheckTime = (await fs.stat(this.file)).mtimeMs;
42+
const today = new Date().getTime();
43+
44+
if ((today - lastCheckTime) / 1000 > this.ttlSecs) { // convert ms to sec
45+
return true;
46+
}
47+
return false;
48+
} catch (err: any) {
49+
if (err.code === 'ENOENT') {
50+
return true;
51+
} else {
52+
throw err;
53+
}
54+
}
55+
}
56+
57+
public async update(latestVersion?: string): Promise<void> {
58+
if (!latestVersion) {
59+
latestVersion = '';
60+
}
61+
await fs.writeFile(this.file, latestVersion);
62+
}
63+
}
64+
65+
// Export for unit testing only.
66+
// Don't use directly, use displayVersionMessage() instead.
67+
export async function getVersionMessages(currentVersion: string, cacheFile: VersionCheckTTL): Promise<string[]> {
68+
if (!(await cacheFile.hasExpired())) {
69+
return [];
70+
}
71+
72+
const packageInfo = await execNpmView(currentVersion);
73+
const latestVersion = packageInfo.latestVersion;
74+
await cacheFile.update(JSON.stringify(packageInfo));
75+
76+
// If the latest version is the same as the current version, there is no need to display a message
77+
if (semver.eq(latestVersion, currentVersion)) {
78+
return [];
79+
}
80+
81+
const versionMessage = [
82+
packageInfo.deprecated ? `${chalk.red(packageInfo.deprecated as string)}` : undefined,
83+
`Newer version of CDK is available [${chalk.green(latestVersion as string)}]`,
84+
getMajorVersionUpgradeMessage(currentVersion),
85+
'Upgrade recommended (npm install -g aws-cdk)',
86+
].filter(Boolean) as string[];
87+
88+
return versionMessage;
89+
}
90+
91+
function getMajorVersionUpgradeMessage(currentVersion: string): string | void {
92+
const currentMajorVersion = semver.major(currentVersion);
93+
if (UPGRADE_DOCUMENTATION_LINKS[currentMajorVersion]) {
94+
return `Information about upgrading from version ${currentMajorVersion}.x to version ${currentMajorVersion + 1}.x is available here: ${UPGRADE_DOCUMENTATION_LINKS[currentMajorVersion]}`;
95+
}
96+
}
97+
98+
export async function displayVersionMessage(
99+
ioHelper: IoHelper,
100+
currentVersion = versionNumber(),
101+
versionCheckCache?: VersionCheckTTL,
102+
): Promise<void> {
103+
if (!process.stdout.isTTY || process.env.CDK_DISABLE_VERSION_CHECK) {
104+
return;
105+
}
106+
107+
try {
108+
const versionMessages = await getVersionMessages(currentVersion, versionCheckCache ?? new VersionCheckTTL());
109+
for (const e of formatAsBanner(versionMessages)) {
110+
await ioHelper.defaults.info(e);
111+
}
112+
} catch (err: any) {
113+
await ioHelper.defaults.debug(`Could not run version check - ${err.message}`);
114+
}
115+
}

packages/aws-cdk/lib/cli/util/yargs-helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ciSystemIsStdErrSafe } from '../ci-systems';
22
import { isCI } from '../io-host';
3-
import * as version from '../version';
3+
import { versionWithBuild } from '../version';
44

55
export { isCI } from '../io-host';
66

@@ -31,7 +31,7 @@ export function yargsNegativeAlias<T extends { [x in S | L]: boolean | undefined
3131
* @returns the current version of the CLI
3232
*/
3333
export function cliVersion(): string {
34-
return version.displayVersion();
34+
return versionWithBuild();
3535
}
3636

3737
/**

packages/aws-cdk/lib/cli/version.ts

Lines changed: 2 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
/* c8 ignore start */
22
import * as path from 'path';
3-
import { ToolkitError } from '@aws-cdk/toolkit-lib';
4-
import * as chalk from 'chalk';
5-
import * as fs from 'fs-extra';
6-
import * as semver from 'semver';
7-
import { cdkCacheDir } from '../util';
83
import { cliRootDir } from './root-dir';
9-
import type { IoHelper } from '../api-private';
10-
import { formatAsBanner } from './util/console-formatters';
11-
import { execNpmView } from './util/npm';
124

13-
const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60;
14-
15-
const UPGRADE_DOCUMENTATION_LINKS: Record<number, string> = {
16-
1: 'https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html',
17-
};
18-
19-
export function displayVersion() {
5+
export function versionWithBuild() {
206
return `${versionNumber()} (build ${commit()})`;
217
}
228

23-
export function isDeveloperBuild(): boolean {
9+
export function isDeveloperBuildVersion(): boolean {
2410
return versionNumber() === '0.0.0';
2511
}
2612

@@ -33,104 +19,3 @@ function commit(): string {
3319
// eslint-disable-next-line @typescript-eslint/no-require-imports
3420
return require(path.join(cliRootDir(), 'build-info.json')).commit;
3521
}
36-
37-
export class VersionCheckTTL {
38-
public static timestampFilePath(): string {
39-
// Using the same path from account-cache.ts
40-
return path.join(cdkCacheDir(), 'repo-version-ttl');
41-
}
42-
43-
private readonly file: string;
44-
45-
// File modify times are accurate only to the second
46-
private readonly ttlSecs: number;
47-
48-
constructor(file?: string, ttlSecs?: number) {
49-
this.file = file || VersionCheckTTL.timestampFilePath();
50-
try {
51-
fs.mkdirsSync(path.dirname(this.file));
52-
fs.accessSync(path.dirname(this.file), fs.constants.W_OK);
53-
} catch {
54-
throw new ToolkitError(`Directory (${path.dirname(this.file)}) is not writable.`);
55-
}
56-
this.ttlSecs = ttlSecs || ONE_DAY_IN_SECONDS;
57-
}
58-
59-
public async hasExpired(): Promise<boolean> {
60-
try {
61-
const lastCheckTime = (await fs.stat(this.file)).mtimeMs;
62-
const today = new Date().getTime();
63-
64-
if ((today - lastCheckTime) / 1000 > this.ttlSecs) { // convert ms to sec
65-
return true;
66-
}
67-
return false;
68-
} catch (err: any) {
69-
if (err.code === 'ENOENT') {
70-
return true;
71-
} else {
72-
throw err;
73-
}
74-
}
75-
}
76-
77-
public async update(latestVersion?: string): Promise<void> {
78-
if (!latestVersion) {
79-
latestVersion = '';
80-
}
81-
await fs.writeFile(this.file, latestVersion);
82-
}
83-
}
84-
85-
// Export for unit testing only.
86-
// Don't use directly, use displayVersionMessage() instead.
87-
export async function getVersionMessages(currentVersion: string, cacheFile: VersionCheckTTL): Promise<string[]> {
88-
if (!(await cacheFile.hasExpired())) {
89-
return [];
90-
}
91-
92-
const packageInfo = await execNpmView(currentVersion);
93-
const latestVersion = packageInfo.latestVersion;
94-
await cacheFile.update(JSON.stringify(packageInfo));
95-
96-
// If the latest version is the same as the current version, there is no need to display a message
97-
if (semver.eq(latestVersion, currentVersion)) {
98-
return [];
99-
}
100-
101-
const versionMessage = [
102-
packageInfo.deprecated ? `${chalk.red(packageInfo.deprecated as string)}` : undefined,
103-
`Newer version of CDK is available [${chalk.green(latestVersion as string)}]`,
104-
getMajorVersionUpgradeMessage(currentVersion),
105-
'Upgrade recommended (npm install -g aws-cdk)',
106-
].filter(Boolean) as string[];
107-
108-
return versionMessage;
109-
}
110-
111-
function getMajorVersionUpgradeMessage(currentVersion: string): string | void {
112-
const currentMajorVersion = semver.major(currentVersion);
113-
if (UPGRADE_DOCUMENTATION_LINKS[currentMajorVersion]) {
114-
return `Information about upgrading from version ${currentMajorVersion}.x to version ${currentMajorVersion + 1}.x is available here: ${UPGRADE_DOCUMENTATION_LINKS[currentMajorVersion]}`;
115-
}
116-
}
117-
118-
export async function displayVersionMessage(
119-
ioHelper: IoHelper,
120-
currentVersion = versionNumber(),
121-
versionCheckCache?: VersionCheckTTL,
122-
): Promise<void> {
123-
if (!process.stdout.isTTY || process.env.CDK_DISABLE_VERSION_CHECK) {
124-
return;
125-
}
126-
127-
try {
128-
const versionMessages = await getVersionMessages(currentVersion, versionCheckCache ?? new VersionCheckTTL());
129-
for (const e of formatAsBanner(versionMessages)) {
130-
await ioHelper.defaults.info(e);
131-
}
132-
} catch (err: any) {
133-
await ioHelper.defaults.debug(`Could not run version check - ${err.message}`);
134-
}
135-
}
136-
/* c8 ignore stop */

packages/aws-cdk/lib/commands/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import * as chalk from 'chalk';
33
import { minimatch } from 'minimatch';
44
import type { Context } from '../api/context';
55
import type { IoHelper } from '../api-private';
6+
import { displayVersionMessage } from '../cli/display-version';
67
import { renderTable } from '../cli/tables';
78
import { PROJECT_CONFIG, PROJECT_CONTEXT, USER_DEFAULTS } from '../cli/user-configuration';
8-
import * as version from '../cli/version';
99

1010
/**
1111
* Options for the context command
@@ -71,7 +71,7 @@ export async function contextHandler(options: ContextOptions): Promise<number> {
7171
await listContext(ioHelper, options.context);
7272
}
7373
}
74-
await version.displayVersionMessage(ioHelper);
74+
await displayVersionMessage(ioHelper);
7575

7676
return 0;
7777
}

packages/aws-cdk/lib/commands/doctor.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as process from 'process';
22
import * as cxapi from '@aws-cdk/cx-api';
33
import * as chalk from 'chalk';
44
import type { IoHelper } from '../api-private';
5-
import * as version from '../cli/version';
5+
import { displayVersionMessage } from '../cli/display-version';
6+
import { versionWithBuild } from '../cli/version';
67

78
export async function doctor({ ioHelper }: { ioHelper: IoHelper }): Promise<number> {
89
let exitStatus: number = 0;
@@ -11,7 +12,7 @@ export async function doctor({ ioHelper }: { ioHelper: IoHelper }): Promise<numb
1112
exitStatus = -1;
1213
}
1314
}
14-
await version.displayVersionMessage(ioHelper);
15+
await displayVersionMessage(ioHelper);
1516
return exitStatus;
1617
}
1718

@@ -24,7 +25,7 @@ const verifications: Array<(ioHelper: IoHelper) => boolean | Promise<boolean>> =
2425
// ### Verifications ###
2526

2627
async function displayVersionInformation(ioHelper: IoHelper) {
27-
await ioHelper.defaults.info(`ℹ️ CDK Version: ${chalk.green(version.displayVersion())}`);
28+
await ioHelper.defaults.info(`ℹ️ CDK Version: ${chalk.green(versionWithBuild())}`);
2829
return true;
2930
}
3031

0 commit comments

Comments
 (0)