diff --git a/extensions/resource-deployment/src/services/tools/SemVerProxy.ts b/extensions/resource-deployment/src/services/tools/SemVerProxy.ts new file mode 100644 index 0000000000..ad94b3ffda --- /dev/null +++ b/extensions/resource-deployment/src/services/tools/SemVerProxy.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { SemVer } from 'semver'; + + + +function fourPart2SemVer(version: string): string { + if (version.includes('-')) { + //return unchanged if version contains a pre-release suffix + return version; + } else { + let parts: string[] = version.split('.'); + if (parts.length > 3) { + version = `${parts[0]}.${parts[1]}.${parts[2]}+${parts.slice(3).join('.')}`; + } + return version; + } +} + +/** + * This Proxy for SemVer behaves the same way as the SamVer except the build number of the SemVer specification at: https://semver.org/ is prefixed by a '.' as well instead of a '+'. So while the BNF for valid SemVer is: + ::= + | "-" + | "+" + | "-" "+" + ::= "." "." + + SemVerProxy support the following BNF: + ::= + | "-" + | "." + | "-" "+" + ::= "." "." + */ +export class SemVerProxy extends SemVer { + private _version: string; + + constructor(version: string | SemVerProxy, loose?: boolean) { + let ver: string; + + if (version instanceof SemVer) { + ver = version.version; + if (!ver) { + throw new Error('Invalid version'); + } + } else { + ver = fourPart2SemVer(version); + } + super(ver, loose); + if (ver.includes('-')) { + this._version = ver; + } else { + this._version = ver.replace('+', '.'); // change back any '+' character used to delimit the build portion of the version with a '.' + } + } + + get version(): string { + return this._version; + } +} diff --git a/extensions/resource-deployment/src/services/tools/azdataTool.ts b/extensions/resource-deployment/src/services/tools/azdataTool.ts index 830c748aa3..cfdfab882e 100644 --- a/extensions/resource-deployment/src/services/tools/azdataTool.ts +++ b/extensions/resource-deployment/src/services/tools/azdataTool.ts @@ -11,6 +11,7 @@ import { AzdataInstallLocationKey, DeploymentConfigurationKey } from '../../cons import { Command, OsDistribution, ToolType } from '../../interfaces'; import { IPlatformService } from '../platformService'; import { dependencyType, ToolBase } from './toolBase'; +import { SemVerProxy } from './SemVerProxy'; const localize = nls.loadMessageBundle(); export const AzdataToolName = 'azdata'; @@ -58,7 +59,7 @@ export class AzdataTool extends ToolBase { protected getVersionFromOutput(output: string): SemVer | undefined { let version: SemVer | undefined = undefined; if (output && output.split(EOL).length > 0) { - version = new SemVer(output.split(EOL)[0].replace(/ /g, '')); + version = new SemVerProxy(output.split(EOL)[0].replace(/ /g, '')); } return version; } diff --git a/extensions/resource-deployment/src/test/SemVerProxy.test.ts b/extensions/resource-deployment/src/test/SemVerProxy.test.ts new file mode 100644 index 0000000000..d9ba5f202e --- /dev/null +++ b/extensions/resource-deployment/src/test/SemVerProxy.test.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import assert = require('assert'); +import { SemVerProxy } from '../services/tools/SemVerProxy'; + +interface Expected { + [index: string]: string | number | undefined | Error + major?: number; + minor?: number; + patch?: number; + build?: string; + raw?: string; + prerelease?: string; + version?: string | Error +} + +interface TestDefinition { + testName: string; + inputVersion: string; + expected: Expected; + +} + + +class SymVerProxyTest extends SemVerProxy { + [index: string]: any; + constructor(version: string | SemVerProxy, loose?: boolean) { + super(version, loose); + } +} + +const testDefinitions: TestDefinition[] = [ + { + testName: 'SemVerProxyTest: canonical 3 part version - Pre-existing common SymVers work as before', inputVersion: '1.2.3', expected: { + major: 1, + minor: 2, + patch: 3, + raw: '1.2.3', + version: '1.2.3' + } + }, + { + testName: 'SemVerProxyTest: canonical 4 part version', inputVersion: '1.2.3.4', expected: { + major: 1, + minor: 2, + patch: 3, + build: '4', + raw: '1.2.3+4', + version: '1.2.3.4' + } + }, + { + testName: 'SemVerProxyTest: canonical 8 part version', inputVersion: '1.2.3.4.5.6.7.8', expected: { + major: 1, + minor: 2, + patch: 3, + build: '4,5,6,7,8', + raw: '1.2.3+4.5.6.7.8', + version: '1.2.3.4.5.6.7.8' + } + }, + { + testName: 'SemVerProxyTest: canonical pre-rel version', inputVersion: '1.2.3-rc1.22.33.44+55.66.77', expected: { + major: 1, + minor: 2, + patch: 3, + build: '55,66,77', + prerelease: 'rc1,22,33,44', + raw: '1.2.3-rc1.22.33.44+55.66.77', + version: '1.2.3-rc1.22.33.44+55.66.77' + } + } +]; + +function validate(test: TestDefinition, semVerProxy: SymVerProxyTest) { + for (const key in test.expected) { + const expected = test.expected[key]; + if (expected) { + assert.equal(semVerProxy[key].toString(), expected.toString(), `validation for property ${key} failed.`); + } + } +} + +suite('SemVeryProxy Tests', function (): void { + testDefinitions.forEach((semVerTest: TestDefinition) => { + test(semVerTest.testName, () => { + const semVerProxy = new SymVerProxyTest(semVerTest.inputVersion); + validate(semVerTest, semVerProxy); + }); + }); +}); diff --git a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts index 7e53587681..bb9c0625bb 100644 --- a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts +++ b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts @@ -102,7 +102,7 @@ export class ResourceTypePickerDialog extends DialogBase { }; const versionColumn: azdata.TableColumn = { value: localize('deploymentDialog.toolVersionColumnHeader', "Version"), - width: 60 + width: 75 }; const minVersionColumn: azdata.TableColumn = { value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Required Version"), @@ -110,7 +110,7 @@ export class ResourceTypePickerDialog extends DialogBase { }; const installedPathColumn: azdata.TableColumn = { value: localize('deploymentDialog.toolDiscoveredPathColumnHeader', "Discovered Path or Additional Information"), - width: 570 + width: 580 }; this._toolsTable = view.modelBuilder.table().withProperties({ data: [],