-
Notifications
You must be signed in to change notification settings - Fork 74
chore(cli): show warning when using deprecated version #102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
b6331f5
a56d547
bf4a6a3
398b8be
6672b83
64e08b5
ca2de44
3c2979f
381c90e
b73dc5b
24cf0e8
755c7d9
f96ec8f
13784e5
1b0356a
dcc358a
5a7dac0
7fb137e
4e75e79
0d97797
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,30 @@ | ||
| import { exec as _exec } from 'child_process'; | ||
| import { promisify } from 'util'; | ||
| import * as semver from 'semver'; | ||
| import { debug } from '../../logging'; | ||
| import { ToolkitError } from '../../toolkit/error'; | ||
|
|
||
| const exec = promisify(_exec); | ||
|
|
||
| /* istanbul ignore next: not called during unit tests */ | ||
| export async function getLatestVersionFromNpm(): Promise<string> { | ||
| const { stdout, stderr } = await exec('npm view aws-cdk version', { timeout: 3000 }); | ||
| if (stderr && stderr.trim().length > 0) { | ||
| debug(`The 'npm view' command generated an error stream with content [${stderr.trim()}]`); | ||
| export async function execNpmView(currentVersion: string) { | ||
| const [latestResult, currentResult] = await Promise.all([ | ||
| exec('npm view aws-cdk@latest version', { timeout: 3000 }) | ||
| .catch(err => { | ||
| throw new ToolkitError(`Failed to fetch latest version info: ${err.message}`); | ||
| }), | ||
| exec(`npm view aws-cdk@${currentVersion} name version deprecated --json`, { timeout: 3000 }) | ||
| .catch(err => { | ||
| throw new ToolkitError(`Failed to fetch current version(${currentVersion}) info: ${err.message}`); | ||
| }) | ||
| ]); | ||
|
|
||
| if (latestResult.stderr && latestResult.stderr.trim().length > 0) { | ||
| throw new ToolkitError(`npm view command failed: ${latestResult.stderr.trim()}`); | ||
| } | ||
| const latestVersion = stdout.trim(); | ||
| if (!semver.valid(latestVersion)) { | ||
| throw new ToolkitError(`npm returned an invalid semver ${latestVersion}`); | ||
| } | ||
|
|
||
| return latestVersion; | ||
|
|
||
| const latestVersion = latestResult.stdout; | ||
| const currentInfo = JSON.parse(currentResult.stdout); | ||
|
|
||
| return { | ||
| latestVersion: latestVersion, | ||
| deprecated: currentInfo.deprecated | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,14 @@ | ||
| /* istanbul ignore file */ | ||
| import * as path from 'path'; | ||
| import * as chalk from 'chalk'; | ||
| import * as fs from 'fs-extra'; | ||
| import * as path from 'path'; | ||
| import * as semver from 'semver'; | ||
| import { debug, info } from '../logging'; | ||
| import { ToolkitError } from '../toolkit/error'; | ||
| import { cdkCacheDir } from '../util'; | ||
| import { cliRootDir } from './root-dir'; | ||
| import { formatAsBanner } from './util/console-formatters'; | ||
| import { getLatestVersionFromNpm } from './util/npm'; | ||
| import { execNpmView } from './util/npm'; | ||
|
|
||
| const ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60; | ||
|
|
||
|
|
@@ -84,20 +84,27 @@ export class VersionCheckTTL { | |
|
|
||
| // Export for unit testing only. | ||
| // Don't use directly, use displayVersionMessage() instead. | ||
| export async function latestVersionIfHigher(currentVersion: string, cacheFile: VersionCheckTTL): Promise<string|null> { | ||
| export async function getVersionMessages(currentVersion: string, cacheFile: VersionCheckTTL): Promise<string[]> { | ||
| if (!(await cacheFile.hasExpired())) { | ||
| return null; | ||
| return []; | ||
| } | ||
|
|
||
| const latestVersion = await getLatestVersionFromNpm(); | ||
| const isNewer = semver.gt(latestVersion, currentVersion); | ||
| const packageInfo = await execNpmView(currentVersion); | ||
| const latestVersion = packageInfo.latestVersion; | ||
| await cacheFile.update(latestVersion); | ||
|
||
|
|
||
| if (isNewer) { | ||
| return latestVersion; | ||
| } else { | ||
| return null; | ||
| if (semver.eq(latestVersion, currentVersion)) { | ||
| return []; | ||
| } | ||
|
Comment on lines
+97
to
99
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only correct if you assume the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the comment 👍 |
||
|
|
||
| const versionMessage = [ | ||
| `${chalk.red(packageInfo.deprecated as string)}`, | ||
|
||
| `Newer version of CDK is available [${chalk.green(latestVersion as string)}]`, | ||
| getMajorVersionUpgradeMessage(currentVersion), | ||
| 'Upgrade recommended (npm install -g aws-cdk)', | ||
| ].filter(Boolean) as string[]; | ||
|
|
||
| return versionMessage; | ||
| } | ||
|
|
||
| function getMajorVersionUpgradeMessage(currentVersion: string): string | void { | ||
|
|
@@ -107,25 +114,14 @@ function getMajorVersionUpgradeMessage(currentVersion: string): string | void { | |
| } | ||
| } | ||
|
|
||
| function getVersionMessage(currentVersion: string, laterVersion: string): string[] { | ||
| return [ | ||
| `Newer version of CDK is available [${chalk.green(laterVersion as string)}]`, | ||
| getMajorVersionUpgradeMessage(currentVersion), | ||
| 'Upgrade recommended (npm install -g aws-cdk)', | ||
| ].filter(Boolean) as string[]; | ||
| } | ||
|
|
||
| export async function displayVersionMessage(currentVersion = versionNumber(), versionCheckCache?: VersionCheckTTL): Promise<void> { | ||
| if (!process.stdout.isTTY || process.env.CDK_DISABLE_VERSION_CHECK) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const laterVersion = await latestVersionIfHigher(currentVersion, versionCheckCache ?? new VersionCheckTTL()); | ||
| if (laterVersion) { | ||
| const bannerMsg = formatAsBanner(getVersionMessage(currentVersion, laterVersion)); | ||
| bannerMsg.forEach((e) => info(e)); | ||
| } | ||
| const versionMessages = await getVersionMessages(currentVersion, versionCheckCache ?? new VersionCheckTTL()); | ||
| formatAsBanner(versionMessages).forEach(e => info(e)); | ||
| } catch (err: any) { | ||
| debug(`Could not run version check - ${err.message}`); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please make sure no other changes than the removal are in this file
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed unnecessary changes. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import { execNpmView } from '../../../lib/cli/util/npm'; | ||
|
|
||
| jest.mock('util', () => { | ||
| const mockExec = jest.fn(); | ||
| const format = jest.fn((fmt, ...args) => { | ||
| return [fmt, ...args].join(' '); | ||
| }); | ||
| return { | ||
| promisify: jest.fn(() => mockExec), | ||
| __mockExec: mockExec, | ||
| format, | ||
| }; | ||
| }); | ||
|
|
||
| const { __mockExec: mockedExec } = jest.requireMock('util'); | ||
|
|
||
| describe('npm.ts', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe('execNpmView', () => { | ||
| test('returns latest version and current version info with deprecated message', async () => { | ||
| // Set up result for the first call (latest version) | ||
| mockedExec.mockImplementationOnce((cmd, options) => { | ||
| expect(cmd).toBe('npm view aws-cdk@latest version'); | ||
| expect(options).toEqual({ timeout: 3000 }); | ||
| return Promise.resolve({ | ||
| stdout: '2.0.0\n', | ||
| stderr: '', | ||
| }); | ||
| }); | ||
|
|
||
| // Set up result for the second call (current version) | ||
| mockedExec.mockImplementationOnce((cmd, options) => { | ||
| expect(cmd).toBe('npm view aws-cdk@1.0.0 name version deprecated --json'); | ||
| expect(options).toEqual({ timeout: 3000 }); | ||
| return Promise.resolve({ | ||
| stdout: '{"version": "1.0.0","deprecated": "This version has been deprecated.", "name": "aws-cdk"}', | ||
| stderr: '', | ||
| }); | ||
| }); | ||
|
|
||
| const result = await execNpmView('1.0.0'); | ||
|
|
||
| expect(result).toEqual({ | ||
| latestVersion: '2.0.0', | ||
| currentVersion: '1.0.0', | ||
| deprecated: 'This version has been deprecated.', | ||
| }); | ||
|
|
||
| expect(mockedExec).toHaveBeenCalledTimes(2); | ||
| }); | ||
|
|
||
| test('returns latest version and current version info without deprecated field', async () => { | ||
| // Set up result for the first call (latest version) | ||
| mockedExec.mockImplementationOnce(() => Promise.resolve({ | ||
| stdout: '2.1000.0\n', | ||
| stderr: '', | ||
| })); | ||
|
|
||
| // Set up result for the second call (current version) | ||
| mockedExec.mockImplementationOnce(() => Promise.resolve({ | ||
| stdout: '{"version": "2.179.0", "name": "aws-cdk"}', | ||
| stderr: '', | ||
| })); | ||
|
|
||
| const result = await execNpmView('2.179.0'); | ||
|
|
||
| expect(result).toEqual({ | ||
| latestVersion: '2.1000.0', | ||
| currentVersion: '2.179.0', | ||
| deprecated: undefined, | ||
| }); | ||
| }); | ||
|
|
||
| test('throws error when latest version npm command fails', async () => { | ||
| // Trigger error for the first call (latest version) | ||
| mockedExec.mockImplementationOnce(() => | ||
| Promise.reject(new Error('npm ERR! code E404\nnpm ERR! 404 Not Found')) | ||
| ); | ||
|
|
||
| await expect(execNpmView('1.0.0')).rejects.toThrow('Failed to fetch latest version info'); | ||
| }); | ||
|
|
||
| test('throws error when current version npm command fails', async () => { | ||
| // Set up result for the first call (latest version) | ||
| mockedExec.mockImplementationOnce(() => Promise.resolve({ | ||
| stdout: '2.0.0\n', | ||
| stderr: '', | ||
| })); | ||
|
|
||
| // Trigger error for the second call (current version) | ||
| mockedExec.mockImplementationOnce(() => | ||
| Promise.reject(new Error('npm ERR! code E404\nnpm ERR! 404 Not Found')) | ||
| ); | ||
|
|
||
| await expect(execNpmView('1.0.0')).rejects.toThrow('Failed to fetch current version'); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super up on Promises syntax. Is
catchwith athrowinside the right way to do this?Where are these errors handled?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s not the wrong way in TypeScript, but this implementation is a little confusing. I used a single
catchto handle all thrown exceptions.