diff --git a/package.json b/package.json
index 54247c94..7c7793d5 100644
--- a/package.json
+++ b/package.json
@@ -116,74 +116,109 @@
"configuration": {
"title": "Power Query SDK",
"properties": {
+ "powerquery.sdk.features.autoDetection": {
+ "scope": "machine-overridable",
+ "type": "boolean",
+ "description": "%extension.pqtest.config.features.autoDetection.description%",
+ "default": true,
+ "order": 9
+ },
"powerquery.sdk.project.autoDetection": {
"scope": "machine-overridable",
"type": "boolean",
"description": "%extension.pqtest.config.features.autoDetection.description%",
"default": true,
+ "order": 10,
"deprecationMessage": "Deprecated: Please use powerquery.sdk.features.autoDetection instead.",
"markdownDeprecationMessage": "**Deprecated**: Please use `#powerquery.sdk.features.autoDetection#` instead."
},
- "powerquery.sdk.pqtest.location": {
+ "powerquery.sdk.features.useServiceHost": {
"scope": "machine-overridable",
+ "type": "boolean",
+ "default": false,
+ "order": 10,
+ "description": "%extension.pqtest.config.features.useServiceHost%"
+ },
+ "powerquery.sdk.defaultExtension": {
+ "scope": "window",
"type": "string",
- "deprecationMessage": "Deprecated: Please use powerquery.sdk.tools.location instead.",
- "description": "%extension.pqtest.config.pqtest.location.description%"
+ "order": 29,
+ "description": "%extension.pqtest.config.pqtest.extension.description%"
+ },
+ "powerquery.sdk.defaultQueryFile": {
+ "scope": "window",
+ "type": "string",
+ "order": 29,
+ "description": "%extension.pqtest.config.pqtest.queryFile.description%"
},
"powerquery.sdk.pqtest.extension": {
"scope": "window",
"type": "string",
+ "order": 30,
"deprecationMessage": "Deprecated: Please use powerquery.sdk.defaultExtension instead.",
"description": "%extension.pqtest.config.pqtest.extension.description%"
},
"powerquery.sdk.pqtest.queryFile": {
"scope": "window",
"type": "string",
+ "order": 30,
"deprecationMessage": "Deprecated: Please use powerquery.sdk.defaultQueryFile instead.",
"description": "%extension.pqtest.config.pqtest.queryFile.description%"
},
"powerquery.sdk.externals.msbuildPath": {
"scope": "machine-overridable",
"type": "string",
+ "order": 50,
"description": "%extension.pqtest.config.externals.msbuildPath.description%"
},
"powerquery.sdk.externals.nugetPath": {
"scope": "machine-overridable",
"type": "string",
+ "order": 50,
"description": "%extension.pqtest.config.externals.nugetPath.description%"
},
+ "powerquery.sdk.externals.versionTag": {
+ "scope": "machine-overridable",
+ "type": "string",
+ "enum": [
+ "Recommended",
+ "Latest",
+ "Custom"
+ ],
+ "enumDescriptions": [
+ "%extension.pqtest.config.externals.versionTag.recommended.description%",
+ "%extension.pqtest.config.externals.versionTag.latest.description%",
+ "%extension.pqtest.config.externals.versionTag.customized.description%"
+ ],
+ "description": "%extension.pqtest.config.externals.versionTag.description%",
+ "default": "Recommended",
+ "order": 50
+ },
"powerquery.sdk.externals.nugetFeed": {
"scope": "window",
"type": "string",
+ "order": 50,
"description": "%extension.pqtest.config.externals.nugetFeed.description%",
"default": ""
},
"powerquery.sdk.tools.location": {
"scope": "machine-overridable",
"type": "string",
+ "order": 69,
"description": "%extension.pqtest.config.pqtest.location.description%"
},
- "powerquery.sdk.defaultExtension": {
- "scope": "window",
- "type": "string",
- "description": "%extension.pqtest.config.pqtest.extension.description%"
- },
- "powerquery.sdk.defaultQueryFile": {
- "scope": "window",
- "type": "string",
- "description": "%extension.pqtest.config.pqtest.queryFile.description%"
- },
- "powerquery.sdk.features.autoDetection": {
+ "powerquery.sdk.pqtest.location": {
"scope": "machine-overridable",
- "type": "boolean",
- "description": "%extension.pqtest.config.features.autoDetection.description%",
- "default": true
+ "type": "string",
+ "order": 70,
+ "deprecationMessage": "Deprecated: Please use powerquery.sdk.tools.location instead.",
+ "description": "%extension.pqtest.config.pqtest.location.description%"
},
- "powerquery.sdk.features.useServiceHost": {
+ "powerquery.sdk.tools.version": {
"scope": "machine-overridable",
- "type": "boolean",
- "default": false,
- "description": "%extension.pqtest.config.features.useServiceHost%"
+ "type": "string",
+ "order": 71,
+ "description": "%extension.pqtest.config.pqtest.version.description%"
}
}
},
diff --git a/package.nls.json b/package.nls.json
index 87827693..2dfda400 100644
--- a/package.nls.json
+++ b/package.nls.json
@@ -12,9 +12,14 @@
"extension.pqtest.config.externals.msbuildPath.description": "Local path to msbuild.exe installation folder.",
"extension.pqtest.config.externals.nugetPath.description": "Local path to nuget.exe installation folder.",
"extension.pqtest.config.externals.nugetFeed.description": "Suggested nuget feed URL.",
+ "extension.pqtest.config.externals.versionTag.description": "The version tag of the PQ SDK tools to be downloaded.",
+ "extension.pqtest.config.externals.versionTag.recommended.description": "The stable version",
+ "extension.pqtest.config.externals.versionTag.latest.description": "The latest version",
+ "extension.pqtest.config.externals.versionTag.customized.description": "Use a user provided version value",
"extension.pqtest.config.features.autoDetection.description": "When set to false, the SDK does not try to automatically detect connector workspaces and prompt to create a settings file.",
"extension.pqtest.config.features.useServiceHost": "Try the new feature using a reusable engine service host other than the command lines",
"extension.pqtest.config.pqtest.location.description": "Local path to PQTest installation folder.",
+ "extension.pqtest.config.pqtest.version.description": "The local PQ SDK tools version.",
"extension.pqtest.config.pqtest.extension.description": "Specify connector extension source modules (.mez/.pqm).
This option can be specified more than once.",
"extension.pqtest.config.pqtest.queryFile.description": "Query file containing section document or M expression (.m/.pq).",
"extension.pqtest.taskDefinitions.properties.operation.description": "The operation to run",
diff --git a/src/GlobalEventBus.ts b/src/GlobalEventBus.ts
index e9367af1..71fb6ce3 100644
--- a/src/GlobalEventBus.ts
+++ b/src/GlobalEventBus.ts
@@ -35,6 +35,8 @@ export const GlobalEvents = Object.freeze({
ConfigDidChangePowerQueryTestLocation: "ConfigDidChangePowerQueryTestLocation" as const,
ConfigDidChangePQTestExtension: "ConfigDidChangePQTestExtension" as const,
ConfigDidChangePQTestQuery: "ConfigDidChangePQTestQuery" as const,
+ ConfigDidChangeExternalVersionTag: "ConfigDidChangeExternalVersionTag" as const,
+ ConfigDidChangePqTestVersion: "ConfigDidChangePqTestVersion" as const,
}),
});
type GlobalEventTypes = ExtractEventTypes;
@@ -122,6 +124,18 @@ export class GlobalEventBus extends DisposableEventEmitter imp
void vscode.commands.executeCommand("workbench.action.reloadWindow");
}
})();
+ } else if (
+ evt.affectsConfiguration(
+ `${ExtensionConstants.ConfigNames.PowerQuerySdk.name}.${ExtensionConstants.ConfigNames.PowerQuerySdk.properties.externalsVersionTag}`,
+ )
+ ) {
+ this.emit(GlobalEvents.VSCodeEvents.ConfigDidChangeExternalVersionTag);
+ } else if (
+ evt.affectsConfiguration(
+ `${ExtensionConstants.ConfigNames.PowerQuerySdk.name}.${ExtensionConstants.ConfigNames.PowerQuerySdk.properties.pqTestVersion}`,
+ )
+ ) {
+ this.emit(GlobalEvents.VSCodeEvents.ConfigDidChangePqTestVersion);
}
} else if (evt.affectsConfiguration(ExtensionConstants.ConfigNames.PowerQuery.name)) {
if (
diff --git a/src/commands/LifecycleCommands.ts b/src/commands/LifecycleCommands.ts
index f4870fe0..a8fb8f26 100644
--- a/src/commands/LifecycleCommands.ts
+++ b/src/commands/LifecycleCommands.ts
@@ -19,8 +19,6 @@ import {
WorkspaceFolder,
} from "vscode";
-import { GlobalEventBus } from "../GlobalEventBus";
-
import {
AuthenticationKind,
CreateAuthState,
@@ -37,6 +35,7 @@ import {
substitutedWorkspaceFolderBasenameIfNeeded,
updateCurrentLocalPqModeIfNeeded,
} from "../utils/vscodes";
+import { GlobalEventBus, GlobalEvents } from "../GlobalEventBus";
import { InputStep, MultiStepInput } from "../common/MultiStepInput";
import { PqServiceHostClient, PqServiceHostServerNotReady } from "../pqTestConnector/PqServiceHostClient";
import { PqTestResultViewPanel, SimplePqTestResultViewBroker } from "../panels/PqTestResultViewPanel";
@@ -71,6 +70,7 @@ export class LifecycleCommands implements IDisposable {
private isSuggestingSetupCurrentWorkspace: boolean = false;
private readonly initPqSdkTool$deferred: Promise;
private checkAndTryToUpdatePqTestDeferred$: Promise | undefined;
+ private currentPqTestVersion: string = ExtensionConstants.SuggestedPqTestNugetVersion;
constructor(
private readonly vscExtCtx: ExtensionContext,
@@ -79,6 +79,32 @@ export class LifecycleCommands implements IDisposable {
private readonly pqTestService: IPQTestService,
private readonly outputChannel: PqSdkOutputChannel,
) {
+ globalEventBus.on(GlobalEvents.VSCodeEvents.ConfigDidChangeExternalVersionTag, () => {
+ // when externalVersionTag changed, we need to re-invoke manuallyUpdatePqTest,
+ // and it will
+ // re-infer the version we expected
+ // stop the exiting running one if any
+ // start the new one if needed
+ void this.manuallyUpdatePqTest();
+ });
+
+ globalEventBus.on(GlobalEvents.VSCodeEvents.ConfigDidChangePqTestVersion, () => {
+ // when PqTestVersion changed in the mode non-customized version tag, users might have it updated
+ // thus we need to compare it with current expected version and reset it back the one we expected
+ if (
+ ExtensionConfigurations.externalsVersionTag !== "Custom" &&
+ this.currentPqTestVersion !== ExtensionConfigurations.PQTestVersion
+ ) {
+ void ExtensionConfigurations.setPQTestVersion(this.currentPqTestVersion);
+ } else if (
+ ExtensionConfigurations.externalsVersionTag == "Custom" &&
+ this.currentPqTestVersion !== ExtensionConfigurations.PQTestVersion
+ ) {
+ // need to trigger debounced manuallyUpdatePqTest
+ void this.debouncedManuallyUpdatePqTest();
+ }
+ });
+
vscExtCtx.subscriptions.push(
vscode.commands.registerCommand(LifecycleCommands.SeizePqTestCommand, this.manuallyUpdatePqTest.bind(this)),
vscode.commands.registerCommand(
@@ -167,7 +193,7 @@ export class LifecycleCommands implements IDisposable {
}
private intervalTask(): void {
- // this task gonna be invoked repeatedly, thus make sure it is as lite as possible
+ // this task would be invoked repeatedly, thus make sure it is as lite as possible
void this.promptSettingIncorrectOrInvokeInfoTaskIfNeeded();
}
@@ -486,21 +512,25 @@ export class LifecycleCommands implements IDisposable {
private async doCheckAndTryToUpdatePqTest(skipQueryDialog: boolean = false): Promise {
try {
- let pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
+ const pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
const maybeNewVersion: string | undefined =
await this.pqSdkNugetPackageService.findNullableNewPqSdkVersion();
+ // have to use || over here, we want to turn empty string into a valid version
+ // '' ?? other -> '', '' || other -> other
+ const theNextVersion: string = maybeNewVersion || ExtensionConstants.SuggestedPqTestNugetVersion;
+
// we should not update to the latest unless the latest nuget doesn't exist on start
// users might just want to use the previous one purposely
// therefore do not try to update when, like, pqTestLocation.indexOf(maybeNewVersion) === -1
if (
!pqTestLocation ||
!this.pqTestService.pqTestReady ||
- !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(maybeNewVersion)
+ !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(theNextVersion)
) {
- const pqTestExecutableFullPath: string | undefined =
- await this.pqSdkNugetPackageService.updatePqSdkFromNuget(maybeNewVersion);
+ let pqTestExecutableFullPath: string | undefined =
+ await this.pqSdkNugetPackageService.updatePqSdkFromNuget(theNextVersion);
if (!pqTestExecutableFullPath && !skipQueryDialog) {
const pqTestLocationUrls: Uri[] | undefined = await vscode.window.showOpenDialog({
@@ -514,22 +544,12 @@ export class LifecycleCommands implements IDisposable {
});
if (pqTestLocationUrls?.[0]) {
- pqTestLocation = pqTestLocationUrls[0].fsPath;
+ pqTestExecutableFullPath = pqTestLocationUrls[0].fsPath;
}
}
if (pqTestExecutableFullPath) {
- // convert pqTestLocation of exe to its dirname
- pqTestLocation = path.dirname(pqTestExecutableFullPath);
- const histPqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
- const newPqTestLocation: string = pqTestLocation;
-
- await ExtensionConfigurations.setPQTestLocation(newPqTestLocation);
-
- if (histPqTestLocation === newPqTestLocation) {
- // update the pqtest location by force in case it equals the previous one
- this.pqTestService.onPowerQueryTestLocationChanged();
- }
+ await this.doUpdatePqTestLocationAndStartItIfNeeded(pqTestExecutableFullPath, theNextVersion);
}
}
@@ -552,7 +572,9 @@ export class LifecycleCommands implements IDisposable {
/**
* check and only update pqTest if needed like: not ready, not existing, the latest one doesn't exist either
- * @param skipQueryDialog
+ * and this method should be invoked only once
+ *
+ * @param skipQueryDialog skip to pop up a dialog to let users fill in the pqTest.exe path
* @private
*/
private checkAndTryToUpdatePqTest(skipQueryDialog: boolean = false): Promise {
@@ -563,8 +585,28 @@ export class LifecycleCommands implements IDisposable {
return this.checkAndTryToUpdatePqTestDeferred$;
}
+ private async doUpdatePqTestLocationAndStartItIfNeeded(
+ pqTestExecutableFullPath: string,
+ theNextVersion: string,
+ ): Promise {
+ // convert pqTestLocation of exe to its dirname
+ const newPqTestLocation: string = path.dirname(pqTestExecutableFullPath);
+ const histPqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
+
+ await ExtensionConfigurations.setPQTestLocation(newPqTestLocation);
+ this.currentPqTestVersion = theNextVersion;
+ await ExtensionConfigurations.setPQTestVersion(theNextVersion);
+
+ if (histPqTestLocation === newPqTestLocation) {
+ // update the pqtest location by force in case it equals the previous one
+ this.pqTestService.onPowerQueryTestLocationChanged();
+ }
+ }
+
/**
- * eagerly update the pqTest as long as currently it is not configured to the latest
+ * Eagerly update the pqTest as long as currently it is not configured to the latest
+ * This method could be invoked multiple times instead
+ *
* @param maybeNextVersion
*/
public async manuallyUpdatePqTest(maybeNextVersion?: string): Promise {
@@ -573,32 +615,27 @@ export class LifecycleCommands implements IDisposable {
maybeNextVersion = await this.pqSdkNugetPackageService.findNullableNewPqSdkVersion();
}
- let pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
+ // have to use || over here, we want to turn empty string into a valid version
+ // '' ?? other -> '', '' || other -> other
+ const theNextVersion: string = maybeNextVersion || ExtensionConstants.SuggestedPqTestNugetVersion;
+
+ const pqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
// determine whether we should trigger to seize or not
if (
- !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(maybeNextVersion) ||
+ !this.pqSdkNugetPackageService.nugetPqSdkExistsSync(theNextVersion) ||
!pqTestLocation ||
// when manually update, we should eagerly update as long as current path is not of the latest version
// like,
// users might want to switch back to the latest some time after
// they temporarily switch back to the previous version
- (maybeNextVersion && pqTestLocation.indexOf(maybeNextVersion) === -1)
+ pqTestLocation.indexOf(theNextVersion) === -1
) {
const pqTestExecutableFullPath: string | undefined =
- await this.pqSdkNugetPackageService.updatePqSdkFromNuget(maybeNextVersion);
+ await this.pqSdkNugetPackageService.updatePqSdkFromNuget(theNextVersion);
if (pqTestExecutableFullPath) {
- pqTestLocation = path.dirname(pqTestExecutableFullPath);
- const histPqTestLocation: string | undefined = ExtensionConfigurations.PQTestLocation;
- const newPqTestLocation: string = pqTestLocation;
-
- await ExtensionConfigurations.setPQTestLocation(newPqTestLocation);
-
- if (histPqTestLocation === newPqTestLocation) {
- // update the pqtest location by force in case it equals the previous one
- this.pqTestService.onPowerQueryTestLocationChanged();
- }
+ await this.doUpdatePqTestLocationAndStartItIfNeeded(pqTestExecutableFullPath, theNextVersion);
}
}
@@ -641,6 +678,11 @@ export class LifecycleCommands implements IDisposable {
return undefined;
}
+ public debouncedManuallyUpdatePqTest: (maybeNextVersion?: string) => Promise = debounce(
+ (maybeNextVersion?: string) => this.manuallyUpdatePqTest(maybeNextVersion),
+ 2e3,
+ ).bind(this) as typeof this.manuallyUpdatePqTest;
+
public async generateOneNewProject(): Promise {
const newProjName: string | undefined = await vscode.window.showInputBox({
title: extensionI18n["PQSdk.lifecycle.command.new.project.title"],
@@ -660,7 +702,7 @@ export class LifecycleCommands implements IDisposable {
const firstWorkspaceFolder: WorkspaceFolder | undefined = getFirstWorkspaceFolder();
if (firstWorkspaceFolder) {
- // we gotta workspace and let's generate files into the first workspace
+ // we got the workspace and let's generate files into the first workspace
const targetFolder: string = this.doGenerateOneProjectIntoOneFolderFromTemplates(
firstWorkspaceFolder.uri.fsPath,
newProjName,
diff --git a/src/common/PqSdkNugetPackageService.ts b/src/common/PqSdkNugetPackageService.ts
index c7cfb1ce..142ed61f 100644
--- a/src/common/PqSdkNugetPackageService.ts
+++ b/src/common/PqSdkNugetPackageService.ts
@@ -48,7 +48,9 @@ export class PqSdkNugetPackageService {
/**
* Return a list of nuget versions less or equal to `options.maximumNugetVersion` if it got specified
- * Otherwise, return the whole list.
+ * Otherwise,
+ * return the latest version in `Latest` version tag mode
+ * return the closest version in `Customized` version tag mode
* @param options
*/
public async findNullableNewPqSdkVersion(
@@ -58,9 +60,15 @@ export class PqSdkNugetPackageService {
): Promise {
let sortedNugetVersions: NugetVersions[];
- // always restrain the version found beneath the MaximumPqTestNugetVersion
- if (!options.maximumNugetVersion) {
- options.maximumNugetVersion = this.nullableMaximumPqTestNugetVersion;
+ if (ExtensionConfigurations.externalsVersionTag === "Recommended") {
+ // force limiting the maximum versions of the nuget list result
+ if (!options.maximumNugetVersion) {
+ // always restrain the version found beneath the MaximumPqTestNugetVersion
+ options.maximumNugetVersion = this.nullableMaximumPqTestNugetVersion;
+ }
+ } else {
+ // in other cases, always force returning the whole list of the nuget versions
+ options = {};
}
if (ExtensionConfigurations.nugetPath) {
@@ -78,7 +86,21 @@ export class PqSdkNugetPackageService {
);
}
- if (sortedNugetVersions.length && !sortedNugetVersions[sortedNugetVersions.length - 1].isZero()) {
+ if (ExtensionConfigurations.externalsVersionTag === "Custom") {
+ // need to find the closest version among the result list:
+ const expectedNugetVersion: NugetVersions = NugetVersions.createFromFuzzyVersionString(
+ ExtensionConfigurations.PQTestVersion || ExtensionConstants.SuggestedPqTestNugetVersion,
+ );
+
+ const closestVersion: NugetVersions = NugetVersions.findClosetAmong(
+ sortedNugetVersions,
+ expectedNugetVersion,
+ );
+
+ return closestVersion === NugetVersions.ZERO_VERSION
+ ? ExtensionConstants.SuggestedPqTestNugetVersion
+ : closestVersion.toString();
+ } else if (sortedNugetVersions.length && !sortedNugetVersions[sortedNugetVersions.length - 1].isZero()) {
return sortedNugetVersions[sortedNugetVersions.length - 1].toString();
} else {
return undefined;
diff --git a/src/common/nuget/NugetCommandService.ts b/src/common/nuget/NugetCommandService.ts
index 24f2bc41..5ae4c5c2 100644
--- a/src/common/nuget/NugetCommandService.ts
+++ b/src/common/nuget/NugetCommandService.ts
@@ -9,6 +9,7 @@ import * as fs from "fs";
import * as path from "path";
import * as process from "process";
+import { assertNotNull } from "../../utils/assertUtils";
import { ExtensionConstants } from "../../constants/PowerQuerySdkExtension";
import { NugetVersions } from "../../utils/NugetVersions";
import type { PqSdkOutputChannel } from "../../features/PqSdkOutputChannel";
@@ -147,10 +148,11 @@ export class NugetCommandService {
).sort(NugetVersions.compare);
if (options.maximumNugetVersion) {
+ const maximumNugetVersion: NugetVersions = assertNotNull(options.maximumNugetVersion);
+
// filter out any version gt maximumNugetVersion in sortedNugetVersions
sortedNugetVersions = sortedNugetVersions.filter(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- (one: NugetVersions) => one.compare(options.maximumNugetVersion!) <= 0,
+ (one: NugetVersions) => one.compare(maximumNugetVersion) <= 0,
);
}
diff --git a/src/common/nuget/NugetLiteHttpService.ts b/src/common/nuget/NugetLiteHttpService.ts
index 0f8f4177..6507c08d 100644
--- a/src/common/nuget/NugetLiteHttpService.ts
+++ b/src/common/nuget/NugetLiteHttpService.ts
@@ -14,6 +14,7 @@ import { promisify } from "util";
import { StreamZipAsync } from "node-stream-zip";
import axios, { AxiosInstance, AxiosResponse } from "axios";
+import { assertNotNull } from "../../utils/assertUtils";
import { makeOneTmpDir } from "../../utils/osUtils";
import { NugetVersions } from "../../utils/NugetVersions";
import { tryRemoveDirectoryRecursively } from "../../utils/files";
@@ -125,10 +126,11 @@ export class NugetLiteHttpService {
.sort(NugetVersions.compare);
if (options.maximumNugetVersion) {
+ const maximumNugetVersion: NugetVersions = assertNotNull(options.maximumNugetVersion);
+
// filter out any version gt maximumNugetVersion in sortedNugetVersions
sortedNugetVersions = sortedNugetVersions.filter(
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- (one: NugetVersions) => one.compare(options.maximumNugetVersion!) <= 0,
+ (one: NugetVersions) => one.compare(maximumNugetVersion) <= 0,
);
}
diff --git a/src/constants/PowerQuerySdkConfiguration.ts b/src/constants/PowerQuerySdkConfiguration.ts
index 3b256037..cec009f7 100644
--- a/src/constants/PowerQuerySdkConfiguration.ts
+++ b/src/constants/PowerQuerySdkConfiguration.ts
@@ -8,7 +8,7 @@
import * as path from "path";
import * as vscode from "vscode";
-import { ExtensionConstants, PqModeType } from "./PowerQuerySdkExtension";
+import { ExtensionConstants, PqModeType, SdkExternalsVersionTags } from "./PowerQuerySdkExtension";
// eslint-disable-next-line @typescript-eslint/typedef
export const ExtensionConfigurations = {
@@ -145,6 +145,16 @@ export const ExtensionConfigurations = {
return config.get(ExtensionConstants.ConfigNames.PowerQuerySdk.properties.externalsNugetFeed);
},
+ get externalsVersionTag(): SdkExternalsVersionTags {
+ const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(
+ ExtensionConstants.ConfigNames.PowerQuerySdk.name,
+ );
+
+ return config.get(
+ ExtensionConstants.ConfigNames.PowerQuerySdk.properties.externalsVersionTag,
+ ) as SdkExternalsVersionTags;
+ },
+
setPQTestLocation(
pqTestLocation: string | undefined,
configurationTarget: vscode.ConfigurationTarget | boolean | null = vscode.ConfigurationTarget.Global,
@@ -173,6 +183,30 @@ export const ExtensionConfigurations = {
);
},
+ setPQTestVersion(
+ pqTestVersion: string | undefined,
+ configurationTarget: vscode.ConfigurationTarget | boolean | null = vscode.ConfigurationTarget.Global,
+ ): Thenable {
+ // we should not cache it
+ const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(
+ ExtensionConstants.ConfigNames.PowerQuerySdk.name,
+ );
+
+ return config.update(
+ ExtensionConstants.ConfigNames.PowerQuerySdk.properties.pqTestVersion,
+ pqTestVersion,
+ configurationTarget,
+ );
+ },
+ get PQTestVersion(): string | undefined {
+ // we should not cache it
+ const config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(
+ ExtensionConstants.ConfigNames.PowerQuerySdk.name,
+ );
+
+ return config.get(ExtensionConstants.ConfigNames.PowerQuerySdk.properties.pqTestVersion);
+ },
+
setDefaultExtensionLocation(
PQTestExtensionFileLocation: string,
configurationTarget: vscode.ConfigurationTarget | boolean = vscode.ConfigurationTarget.Workspace,
diff --git a/src/constants/PowerQuerySdkExtension.ts b/src/constants/PowerQuerySdkExtension.ts
index 75826be5..de8c4190 100644
--- a/src/constants/PowerQuerySdkExtension.ts
+++ b/src/constants/PowerQuerySdkExtension.ts
@@ -11,6 +11,7 @@ import * as os from "os";
const ExtensionId: string = "vscode-powerquery-sdk";
export type PqModeType = "Power Query" | "SDK";
+export type SdkExternalsVersionTags = "Recommended" | "Latest" | "Custom";
// eslint-disable-next-line @typescript-eslint/typedef
const ConfigNames = {
@@ -35,7 +36,9 @@ const ConfigNames = {
externalsMsbuildPath: "externals.msbuildPath" as const,
externalsNugetPath: "externals.nugetPath" as const,
externalsNugetFeed: "externals.nugetFeed" as const,
+ externalsVersionTag: "externals.versionTag" as const,
pqTestLocation: "tools.location" as const,
+ pqTestVersion: "tools.version" as const,
defaultExtensionLocation: "defaultExtension" as const,
defaultQueryFileLocation: "defaultQueryFile" as const,
featureUseServiceHost: "features.useServiceHost" as const,
diff --git a/src/utils/NugetVersions.ts b/src/utils/NugetVersions.ts
index 50692182..ed1ca780 100644
--- a/src/utils/NugetVersions.ts
+++ b/src/utils/NugetVersions.ts
@@ -134,6 +134,48 @@ export class NugetVersions {
);
}
+ public static distance(l: NugetVersions, r: NugetVersions): number {
+ const majorDist: number = NugetVersions.compareIdentifiers(l.major, r.major) * 1e6;
+
+ if (majorDist !== 0) {
+ return majorDist;
+ } else {
+ const minorDist: number = NugetVersions.compareIdentifiers(l.minor, r.minor) * 1e3;
+
+ if (minorDist !== 0) {
+ return minorDist;
+ }
+
+ return NugetVersions.compareIdentifiers(l.patch, r.patch);
+ }
+ }
+
+ public static findClosetAmong(sortedVersionArr: NugetVersions[], expectedVersion: NugetVersions): NugetVersions {
+ if (sortedVersionArr.length === 0) {
+ return NugetVersions.ZERO_VERSION;
+ } else if (sortedVersionArr.length === 1) {
+ return sortedVersionArr[0];
+ }
+
+ let closestVersion: NugetVersions = sortedVersionArr[0];
+ let minDistance: number = NugetVersions.distance(expectedVersion, closestVersion);
+
+ for (let i: number = 1; i < sortedVersionArr.length; i++) {
+ const distance: number = NugetVersions.distance(expectedVersion, sortedVersionArr[i]);
+
+ if (distance < 0) {
+ break;
+ }
+
+ if (distance <= minDistance) {
+ closestVersion = sortedVersionArr[i];
+ minDistance = distance;
+ }
+ }
+
+ return closestVersion;
+ }
+
private static compareIdentifiers(l: string, r: string): number {
const isLNumber: boolean = NumericRegExp.test(l);
const isRNumber: boolean = NumericRegExp.test(r);
diff --git a/src/utils/assertUtils.ts b/src/utils/assertUtils.ts
new file mode 100644
index 00000000..320381a0
--- /dev/null
+++ b/src/utils/assertUtils.ts
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the MIT license found in the
+ * LICENSE file in the root of this projects source tree.
+ */
+
+import * as assert from "assert";
+
+export function assertNotNull(value: T | undefined, errorMessage: string = "Found an unexpected nullable value"): T {
+ assert.ok(Boolean(value), errorMessage);
+
+ return value as T;
+}
diff --git a/unit-tests/common/nuget/NugetVersions.spec.ts b/unit-tests/common/nuget/NugetVersions.spec.ts
index c81fbc1d..88ffc43b 100644
--- a/unit-tests/common/nuget/NugetVersions.spec.ts
+++ b/unit-tests/common/nuget/NugetVersions.spec.ts
@@ -10,6 +10,31 @@ import { NugetVersions } from "../../../src/utils/NugetVersions";
const expect = chai.expect;
+const dummyNugetVersionsArrV1 = [
+ NugetVersions.createFromReleasedVersionString("2.2.1"),
+ NugetVersions.createFromReleasedVersionString("1.1.0"),
+ NugetVersions.createFromReleasedVersionString("1.3.0"),
+ NugetVersions.createFromReleasedVersionString("1.2.0"),
+ NugetVersions.createFromReleasedVersionString("2.0.0"),
+ NugetVersions.createFromReleasedVersionString("2.1.1"),
+ NugetVersions.createFromReleasedVersionString("2.1.0"),
+ NugetVersions.createFromReleasedVersionString("1.2.1"),
+ NugetVersions.createFromReleasedVersionString("1.0.0"),
+ NugetVersions.createFromReleasedVersionString("2.2.0"),
+].sort(NugetVersions.compare);
+
+const dummyNugetVersionsArrV2 = [
+ NugetVersions.createFromReleasedVersionString("2.116.201"),
+ NugetVersions.createFromReleasedVersionString("2.114.4"),
+ NugetVersions.createFromReleasedVersionString("2.112.4"),
+ NugetVersions.createFromReleasedVersionString("2.111.5"),
+ NugetVersions.createFromReleasedVersionString("2.111.3"),
+ NugetVersions.createFromReleasedVersionString("2.110.3"),
+ NugetVersions.createFromReleasedVersionString("2.110.2"),
+ NugetVersions.createFromReleasedVersionString("2.110.1"),
+ NugetVersions.createFromReleasedVersionString("2.109.6"),
+].sort(NugetVersions.compare);
+
describe("NugetVersions.spec unit testes", () => {
it("createFromReleasedVersionString v1", () => {
const nugetVersions = NugetVersions.createFromReleasedVersionString("2.107.1");
@@ -68,4 +93,98 @@ describe("NugetVersions.spec unit testes", () => {
expect(nugetVersionsJunior.compare(nugetVersions)).lt(0);
expect(nugetVersionsSenior.compare(nugetVersions)).gt(0);
});
+
+ it("findClosestVersion v1", () => {
+ let expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("1.2.1");
+ let closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV1, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("1");
+ expect(closestNuGetVersion.minor).eq("2");
+ expect(closestNuGetVersion.patch).eq("1");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("1.3.3");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV1, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("1");
+ expect(closestNuGetVersion.minor).eq("3");
+ expect(closestNuGetVersion.patch).eq("0");
+ });
+
+ it("findClosestVersion v2", () => {
+ let expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.114.4");
+ let closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("114");
+ expect(closestNuGetVersion.patch).eq("4");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("116");
+ expect(closestNuGetVersion.patch).eq("201");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("116");
+ expect(closestNuGetVersion.patch).eq("201");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.1");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("109");
+ expect(closestNuGetVersion.patch).eq("6");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.11");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("109");
+ expect(closestNuGetVersion.patch).eq("6");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("110");
+ expect(closestNuGetVersion.patch).eq("3");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110.");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("110");
+ expect(closestNuGetVersion.patch).eq("3");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110.x");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("110");
+ expect(closestNuGetVersion.patch).eq("3");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.110.4");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("110");
+ expect(closestNuGetVersion.patch).eq("3");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.115");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("114");
+ expect(closestNuGetVersion.patch).eq("4");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.115.1000000000000000000");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("114");
+ expect(closestNuGetVersion.patch).eq("4");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.116");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("116");
+ expect(closestNuGetVersion.patch).eq("201");
+
+ expectedNugetVersion = NugetVersions.createFromFuzzyVersionString("2.116.201");
+ closestNuGetVersion = NugetVersions.findClosetAmong(dummyNugetVersionsArrV2, expectedNugetVersion);
+ expect(closestNuGetVersion.major).eq("2");
+ expect(closestNuGetVersion.minor).eq("116");
+ expect(closestNuGetVersion.patch).eq("201");
+ });
});