Skip to content

Commit 944a20a

Browse files
Merge pull request #3310 from nkomonen-amazon/minimumVersion
fix(codecatalyst): Notify ssh extension min version required
2 parents 965cffb + a3f7ce4 commit 944a20a

File tree

5 files changed

+143
-16
lines changed

5 files changed

+143
-16
lines changed

src/codecatalyst/tools.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { getLogger } from '../shared/logger'
2424
import { getIdeProperties } from '../shared/extensionUtilities'
2525
import { showConfirmationMessage } from '../shared/utilities/messages'
2626
import { getSshConfigPath } from '../shared/extensions/ssh'
27-
import { VSCODE_EXTENSION_ID } from '../shared/extensions'
27+
import { VSCODE_EXTENSION_ID, vscodeExtensionMinVersion } from '../shared/extensions'
2828

2929
interface DependencyPaths {
3030
readonly vsc: string
@@ -40,8 +40,13 @@ interface MissingTool {
4040
export const hostNamePrefix = 'aws-devenv-'
4141

4242
export async function ensureDependencies(): Promise<Result<DependencyPaths, CancellationError | Error>> {
43-
if (!isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh)) {
44-
showInstallExtensionMsg(VSCODE_EXTENSION_ID.remotessh, 'Remote SSH', 'Connecting to Dev Environment')
43+
if (!isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh, vscodeExtensionMinVersion.remotessh)) {
44+
showInstallExtensionMsg(
45+
VSCODE_EXTENSION_ID.remotessh,
46+
'Remote SSH',
47+
'Connecting to Dev Environment',
48+
vscodeExtensionMinVersion.remotessh
49+
)
4550

4651
return Result.err(
4752
new ToolkitError('Remote SSH extension not installed', {

src/shared/extensions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const VSCODE_EXTENSION_ID = {
2525
remotessh: 'ms-vscode-remote.remote-ssh',
2626
}
2727

28+
export const vscodeExtensionMinVersion = {
29+
remotessh: '0.98.0',
30+
}
31+
2832
/**
2933
* Long-lived, extension-scoped, shared globals.
3034
*/

src/shared/utilities/vsCodeUtils.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import * as pathutils from './pathUtils'
1010
import { getLogger } from '../logger/logger'
1111
import { CancellationError, Timeout, waitTimeout, waitUntil } from './timeoutUtils'
1212
import { telemetry } from '../telemetry/telemetry'
13+
import * as semver from 'semver'
14+
import { isNonNullable } from './tsUtils'
1315

1416
// TODO: Consider NLS initialization/configuration here & have packages to import localize from here
1517
export const localize = nls.loadMessageBundle()
@@ -53,8 +55,31 @@ export function isExtensionActive(extId: string): boolean {
5355
return !!extension && extension.isActive
5456
}
5557

56-
export function isExtensionInstalled(extId: string): boolean {
57-
return !!vscode.extensions.getExtension(extId)
58+
/**
59+
* Checks if an extension is installed and meets the version requirement
60+
* @param minVersion The minimum semver required for the extension
61+
*/
62+
export function isExtensionInstalled(
63+
extId: string,
64+
minVersion?: string,
65+
getExtension = vscode.extensions.getExtension
66+
): boolean {
67+
const ext = getExtension(extId)
68+
if (ext === undefined) {
69+
return false
70+
}
71+
72+
if (minVersion === undefined) {
73+
return true
74+
}
75+
76+
// check ext has valid version
77+
const extSemver = semver.coerce(ext.packageJSON.version)
78+
const minSemver = semver.coerce(minVersion)
79+
if (!isNonNullable(extSemver) || !isNonNullable(minSemver)) {
80+
return false
81+
}
82+
return semver.gte(extSemver, minSemver)
5883
}
5984

6085
/**
@@ -63,19 +88,14 @@ export function isExtensionInstalled(extId: string): boolean {
6388
export function showInstallExtensionMsg(
6489
extId: string,
6590
extName: string,
66-
feat = `${getIdeProperties().company} Toolkit`
91+
feat = `${getIdeProperties().company} Toolkit`,
92+
minVersion?: string
6793
): boolean {
68-
if (vscode.extensions.getExtension(extId)) {
94+
if (isExtensionInstalled(extId, minVersion)) {
6995
return true
7096
}
7197

72-
const msg = localize(
73-
'AWS.missingExtension',
74-
'{0} requires the {1} extension ({2}) to be installed and enabled.',
75-
feat,
76-
extName,
77-
extId
78-
)
98+
const msg = buildMissingExtensionMessage(extId, extName, minVersion, feat)
7999

80100
const installBtn = localize('AWS.missingExtension.install', 'Install...')
81101
const items = [installBtn]
@@ -90,6 +110,25 @@ export function showInstallExtensionMsg(
90110
return false
91111
}
92112

113+
export function buildMissingExtensionMessage(
114+
extId: string,
115+
extName: string,
116+
minVersion?: string,
117+
feat = `${getIdeProperties().company} Toolkit`
118+
): string {
119+
const minV = semver.coerce(minVersion)
120+
const expectedVersionMsg = isNonNullable(minV) ? ` of version >=${minV}` : ''
121+
122+
return localize(
123+
'AWS.missingExtension',
124+
"{0} requires the {1} extension ('{2}'{3}) to be installed and enabled.",
125+
feat,
126+
extName,
127+
extId,
128+
expectedVersionMsg
129+
)
130+
}
131+
93132
/**
94133
* Activates an extension and returns it, or does nothing if the extension is
95134
* not installed.

src/test/shared/utilities/vscodeUtils.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as assert from 'assert'
77
import { VSCODE_EXTENSION_ID } from '../../../shared/extensions'
88
import * as vscodeUtil from '../../../shared/utilities/vsCodeUtils'
9+
import * as vscode from 'vscode'
910

1011
describe('vscodeUtils', async function () {
1112
it('activateExtension(), isExtensionActive()', async function () {
@@ -20,3 +21,81 @@ describe('vscodeUtils', async function () {
2021
assert.deepStrictEqual(vscodeUtil.isExtensionActive(VSCODE_EXTENSION_ID.awstoolkit), true)
2122
})
2223
})
24+
25+
describe('isExtensionInstalled()', function () {
26+
const smallerVersion = '0.9.0'
27+
const extVersion = '1.0.0'
28+
const largerVersion = '2.0.0'
29+
const extId = 'my.ext.id'
30+
let ext: vscode.Extension<any>
31+
let getExtension: (extId: string) => vscode.Extension<any>
32+
33+
beforeEach(function () {
34+
ext = {
35+
packageJSON: {
36+
version: extVersion,
37+
},
38+
} as vscode.Extension<any>
39+
getExtension = _ => ext
40+
})
41+
42+
it('fails if extension could not be found', function () {
43+
const noExtFunc = (extId: string) => undefined
44+
assert.ok(!vscodeUtil.isExtensionInstalled(extId, undefined, noExtFunc))
45+
})
46+
47+
it('succeeds on same min version', function () {
48+
assert.ok(vscodeUtil.isExtensionInstalled(extId, extVersion, getExtension))
49+
})
50+
51+
it('succeeds on smaller min version', function () {
52+
assert.ok(vscodeUtil.isExtensionInstalled(extId, smallerVersion, getExtension))
53+
})
54+
55+
it('fails on larger min version', function () {
56+
assert.ok(!vscodeUtil.isExtensionInstalled(extId, largerVersion, getExtension))
57+
})
58+
59+
it('can handle labels on a version', function () {
60+
ext.packageJSON.version = `${extVersion}-SNAPSHOT`
61+
assert.ok(vscodeUtil.isExtensionInstalled(extId, `${smallerVersion}-ALPHA`, getExtension))
62+
})
63+
64+
it('is valid when no min version is provided', function () {
65+
assert.ok(vscodeUtil.isExtensionInstalled(extId, undefined, getExtension))
66+
})
67+
68+
it('fails on malformed version', function () {
69+
// malformed min version
70+
assert.ok(!vscodeUtil.isExtensionInstalled(extId, 'malformed.version', getExtension))
71+
72+
// malformed ext version
73+
ext.packageJSON.version = 'malformed.version'
74+
assert.ok(!vscodeUtil.isExtensionInstalled(extId, extVersion, getExtension))
75+
})
76+
})
77+
78+
describe('buildMissingExtensionMessage()', function () {
79+
const extId = 'MY.EXT.ID'
80+
const extName = 'MY EXTENSION'
81+
const minVer = '1.0.0'
82+
const feat = 'FEATURE'
83+
84+
// Test when a minVer is given
85+
it('minVer', function () {
86+
const message = vscodeUtil.buildMissingExtensionMessage(extId, extName, minVer, feat)
87+
assert.strictEqual(
88+
message,
89+
`${feat} requires the ${extName} extension (\'${extId}\' of version >=${minVer}) to be installed and enabled.`
90+
)
91+
})
92+
93+
// Test when a minVer is not given
94+
it('no minVer', function () {
95+
const message = vscodeUtil.buildMissingExtensionMessage(extId, extName, undefined, feat)
96+
assert.strictEqual(
97+
message,
98+
`${feat} requires the ${extName} extension (\'${extId}\') to be installed and enabled.`
99+
)
100+
})
101+
})

src/testE2E/codecatalyst/client.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { GetDevEnvironmentRequest } from 'aws-sdk/clients/codecatalyst'
2424
import { getTestWindow } from '../../test/shared/vscode/window'
2525
import { patchObject, registerAuthHook, using } from '../../test/setupUtil'
2626
import { isExtensionInstalled } from '../../shared/utilities/vsCodeUtils'
27-
import { VSCODE_EXTENSION_ID } from '../../shared/extensions'
27+
import { VSCODE_EXTENSION_ID, vscodeExtensionMinVersion } from '../../shared/extensions'
2828
import { captureEventOnce } from '../../test/testUtil'
2929
import { toStream } from '../../shared/utilities/collectionUtils'
3030
import { toCollection } from '../../shared/utilities/asyncCollection'
@@ -167,7 +167,7 @@ describe('Test how this codebase uses the CodeCatalyst API', function () {
167167
})
168168

169169
it('prompts to install the ssh extension if not available', async function () {
170-
if (isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh)) {
170+
if (isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh, vscodeExtensionMinVersion.remotessh)) {
171171
this.skip()
172172
}
173173

0 commit comments

Comments
 (0)