From 243dedcc08dc22f05198a21be10a281882b61067 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 8 Jul 2020 12:23:31 -0700 Subject: [PATCH 1/5] Add external API part 1 --- .vscode/launch.json | 30 ++----- package-lock.json | 11 +++ package.json | 20 +++++ src/features/ExternalApi.ts | 144 ++++++++++++++++++++++++++++++ src/main.ts | 8 +- src/process.ts | 6 +- src/session.ts | 14 ++- src/utils.ts | 4 + test/features/ExternalApi.test.ts | 85 ++++++++++++++++++ 9 files changed, 288 insertions(+), 34 deletions(-) create mode 100644 src/features/ExternalApi.ts create mode 100644 test/features/ExternalApi.test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 155db9a086..3d821aa8b5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out/src/**/*.js"], + "outFiles": ["${workspaceFolder}/out/src/**/*.js"], "preLaunchTask": "BuildAll" }, { @@ -17,10 +17,10 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": [ "--extensionDevelopmentPath=${workspaceRoot}" ], + "args": [ "--extensionDevelopmentPath=${workspaceFolder}" ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out/src/**/*.js"], + "outFiles": ["${workspaceFolder}/out/src/**/*.js"], "preLaunchTask": "Build" }, { @@ -28,25 +28,11 @@ "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], - "stopOnEntry": false, - "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out/test/**/*.js"], - "preLaunchTask": "Build", - "skipFiles": [ - "${workspaceFolder}/node_modules/**/*", - "${workspaceFolder}/lib/**/*", - "/private/var/folders/**/*", - "/**/*" - ] - }, - { - "name": "Attach", - "type": "node", - "request": "attach", - "address": "localhost", - "port": 5858, - "sourceMaps": false + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/testRunner.js" ], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "Build" } ] } diff --git a/package-lock.json b/package-lock.json index 4dcae089e6..60a479ec69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -160,6 +160,12 @@ "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", "dev": true }, + "@types/uuid": { + "version": "8.0.0", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/uuid/-/@types/uuid-8.0.0.tgz", + "integrity": "sha1-FlquSBmtIXShdHbb5m/uvVSVVsA=", + "dev": true + }, "@types/vscode": { "version": "1.43.0", "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/@types/vscode/-/@types/vscode-1.43.0.tgz", @@ -2081,6 +2087,11 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "uuid": { + "version": "8.2.0", + "resolved": "https://botbuilder.myget.org/F/botframework-cli/npm/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha1-yxDdaxGOLa2n0M2XMLp0F8k9kg4=" + }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", diff --git a/package.json b/package.json index 3c4eaee935..9178125be0 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,15 @@ "onCommand:PowerShell.RestartSession", "onCommand:PowerShell.EnableISEMode", "onCommand:PowerShell.DisableISEMode", + "onCommand:PowerShell.RegisterExternalExtension", + "onCommand:PowerShell.UnregisterExternalExtension", + "onCommand:PowerShell.GetPowerShellVersionDetails", "onView:PowerShellCommands" ], "dependencies": { "node-fetch": "^2.6.0", "semver": "^7.3.2", + "uuid": "^8.2.0", "vscode-extension-telemetry": "~0.1.6", "vscode-languageclient": "~6.1.3" }, @@ -57,6 +61,7 @@ "@types/rewire": "~2.5.28", "@types/semver": "~7.2.0", "@types/sinon": "~9.0.4", + "@types/uuid": "^8.0.0", "@types/vscode": "1.43.0", "mocha": "~5.2.0", "mocha-junit-reporter": "~2.0.0", @@ -292,6 +297,21 @@ "light": "resources/light/MovePanelBottom.svg", "dark": "resources/dark/MovePanelBottom.svg" } + }, + { + "command": "PowerShell.RegisterExternalExtension", + "title": "Register an external extension", + "category": "PowerShell" + }, + { + "command": "PowerShell.UnregisterExternalExtension", + "title": "Unregister an external extension", + "category": "PowerShell" + }, + { + "command": "PowerShell.GetPowerShellVersionDetails", + "title": "Get details about the PowerShell version that the PowerShell extension is using", + "category": "PowerShell" } ], "menus": { diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts new file mode 100644 index 0000000000..5dd5301c9a --- /dev/null +++ b/src/features/ExternalApi.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +import * as vscode from "vscode"; +import { v4 as uuidv4 } from 'uuid'; +import { LanguageClient } from "vscode-languageclient"; +import { IFeature } from "../feature"; +import { Logger } from "../logging"; +import { SessionManager } from "../session"; + +interface IExternalExtension { + readonly id: string; + readonly apiVersion: string; +} + +export interface IExternalPowerShellDetails { + exePath: string; + version: string; + displayName: string; + architecture: string; +} + +export class ExternalApiFeature implements IFeature { + private commands: vscode.Disposable[]; + private languageClient: LanguageClient; + private static readonly registeredExternalExtension: Map = new Map(); + + constructor(private sessionManager: SessionManager, private log: Logger) { + this.commands = [ + /* + DESCRIPTION: + Registers your extension to allow usage of the external API. The returns + a session UUID that will need to be passed in to subsequent API calls. + + USAGE: + vscode.commands.executeCommand( + "PowerShell.RegisterExternalExtension", + "ms-vscode.PesterTestExplorer" // the name of the extension using us + "v1"); // API Version. + + RETURNS: + string session uuid + */ + vscode.commands.registerCommand("PowerShell.RegisterExternalExtension", (id: string, apiVersion: string = 'v1'): string => { + log.writeDiagnostic(`Registering extension '${id}' for use with API version '${apiVersion}'.`); + + for (const [_, externalExtension] of ExternalApiFeature.registeredExternalExtension) { + if (externalExtension.id === id) { + const message = `The extension '${id}' is already registered.`; + log.writeWarning(message); + throw new Error(message); + } + } + + if (!vscode.extensions.all.some(ext => ext.id === id)) { + throw new Error(`No extension installed with id '${id}'. You must use a valid extension id.`); + } + + // If we're in development mode, we allow these to be used for testing purposes. + if (!sessionManager.InDevelopmentMode && (id === "ms-vscode.PowerShell" || id === "ms-vscode.PowerShell-Preview")) { + throw new Error("You can't use the PowerShell extension's id in this registration."); + } + + const uuid = uuidv4(); + ExternalApiFeature.registeredExternalExtension.set(uuid, { + id, + apiVersion + }); + return uuid; + }), + + /* + DESCRIPTION: + Unregisters a session that an extension has. This returns + true if it succeeds or throws if it fails. + + USAGE: + vscode.commands.executeCommand( + "PowerShell.UnregisterExternalExtension", + "uuid"); // the uuid from above for tracking purposes + + RETURNS: + true if it worked, otherwise throws an error. + */ + vscode.commands.registerCommand("PowerShell.UnregisterExternalExtension", (uuid: string): boolean => { + log.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); + if (!uuid && !ExternalApiFeature.registeredExternalExtension.delete(uuid)) { + throw new Error(`No extension registered with session UUID: ${uuid}`); + } + return true; + }), + + /* + DESCRIPTION: + This will fetch the version details of the PowerShell used to start + PowerShell Editor Services in the PowerShell extension. + + USAGE: + vscode.commands.executeCommand( + "PowerShell.GetPowerShellVersionDetails", + "uuid"); // the uuid from above for tracking purposes + + RETURNS: + An IPowerShellVersionDetails which consists of: + { + version: string; + displayVersion: string; + edition: string; + architecture: string; + } + */ + vscode.commands.registerCommand("PowerShell.GetPowerShellVersionDetails", async (uuid: string): Promise => { + if (!uuid && !ExternalApiFeature.registeredExternalExtension.has(uuid)) { + throw new Error( + "UUID provided was invalid, make sure you execute the 'PowerShell.GetPowerShellVersionDetails' command and pass in the UUID that it returns to subsequent command executions."); + } + + // TODO: When we have more than one API version, make sure to include a check here. + const extension = ExternalApiFeature.registeredExternalExtension.get(uuid); + log.writeDiagnostic(`Extension '${extension.id}' used command 'PowerShell.GetPowerShellVersionDetails'.`); + + await sessionManager.waitUntilStarted(); + const versionDetails = sessionManager.getPowerShellVersionDetails(); + + return { + exePath: sessionManager.PowerShellExeDetails.exePath, + version: versionDetails.version, + displayName: sessionManager.PowerShellExeDetails.displayName, // comes from the Session Menu + architecture: versionDetails.architecture + }; + }), + ] + } + + public dispose() { + for (const command of this.commands) { + command.dispose(); + } + } + + public setLanguageClient(languageclient: LanguageClient) { + this.languageClient = languageclient; + } +} diff --git a/src/main.ts b/src/main.ts index a51407300e..b8c681973b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,12 +13,10 @@ import { CodeActionsFeature } from "./features/CodeActions"; import { ConsoleFeature } from "./features/Console"; import { CustomViewsFeature } from "./features/CustomViews"; import { DebugSessionFeature } from "./features/DebugSession"; -import { PickPSHostProcessFeature } from "./features/DebugSession"; -import { PickRunspaceFeature } from "./features/DebugSession"; -import { SpecifyScriptArgsFeature } from "./features/DebugSession"; import { ExamplesFeature } from "./features/Examples"; import { ExpandAliasFeature } from "./features/ExpandAlias"; import { ExtensionCommandsFeature } from "./features/ExtensionCommands"; +import { ExternalApiFeature } from "./features/ExternalApi"; import { FindModuleFeature } from "./features/FindModule"; import { GenerateBugReportFeature } from "./features/GenerateBugReport"; import { GetCommandsFeature } from "./features/GetCommands"; @@ -27,14 +25,15 @@ import { ISECompatibilityFeature } from "./features/ISECompatibility"; import { NewFileOrProjectFeature } from "./features/NewFileOrProject"; import { OpenInISEFeature } from "./features/OpenInISE"; import { PesterTestsFeature } from "./features/PesterTests"; +import { PickPSHostProcessFeature, PickRunspaceFeature } from "./features/DebugSession"; import { RemoteFilesFeature } from "./features/RemoteFiles"; import { RunCodeFeature } from "./features/RunCode"; import { ShowHelpFeature } from "./features/ShowHelp"; +import { SpecifyScriptArgsFeature } from "./features/DebugSession"; import { Logger, LogLevel } from "./logging"; import { SessionManager } from "./session"; import Settings = require("./settings"); import { PowerShellLanguageId } from "./utils"; -import utils = require("./utils"); // The most reliable way to get the name and version of the current extension. // tslint:disable-next-line: no-var-requires @@ -157,6 +156,7 @@ export function activate(context: vscode.ExtensionContext): void { new HelpCompletionFeature(logger), new CustomViewsFeature(), new PickRunspaceFeature(), + new ExternalApiFeature(sessionManager, logger) ]; sessionManager.setExtensionFeatures(extensionFeatures); diff --git a/src/process.ts b/src/process.ts index 7f9f08b185..347dacd06f 100644 --- a/src/process.ts +++ b/src/process.ts @@ -179,10 +179,6 @@ export class PowerShellProcess { return true; } - private sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - private async waitForSessionFile(): Promise { // Determine how many tries by dividing by 2000 thus checking every 2 seconds. const numOfTries = this.sessionSettings.developer.waitForSessionFileTimeoutSeconds / 2; @@ -203,7 +199,7 @@ export class PowerShellProcess { } // Wait a bit and try again - await this.sleep(2000); + await utils.sleep(2000); } const err = "Timed out waiting for session file to appear."; diff --git a/src/session.ts b/src/session.ts index bdd34f703f..9e9ca64260 100644 --- a/src/session.ts +++ b/src/session.ts @@ -54,6 +54,7 @@ export class SessionManager implements Middleware { private sessionSettings: Settings.ISettings = undefined; private sessionDetails: utils.IEditorServicesSessionDetails; private bundledModulesPath: string; + private started: boolean = false; // Initialized by the start() method, since this requires settings private powershellExeFinder: PowerShellExeFinder; @@ -61,7 +62,7 @@ export class SessionManager implements Middleware { // When in development mode, VS Code's session ID is a fake // value of "someValue.machineId". Use that to detect dev // mode for now until Microsoft/vscode#10272 gets implemented. - private readonly inDevelopmentMode = + public readonly InDevelopmentMode = vscode.env.sessionId === "someValue.sessionId"; constructor( @@ -167,7 +168,7 @@ export class SessionManager implements Middleware { this.bundledModulesPath = path.resolve(__dirname, this.sessionSettings.bundledModulesPath); - if (this.inDevelopmentMode) { + if (this.InDevelopmentMode) { const devBundledModulesPath = path.resolve( __dirname, @@ -274,6 +275,12 @@ export class SessionManager implements Middleware { return this.debugSessionProcess; } + public async waitUntilStarted(): Promise { + while(!this.started) { + await utils.sleep(300); + } + } + // ----- LanguageClient middleware methods ----- public resolveCodeLens( @@ -549,8 +556,9 @@ export class SessionManager implements Middleware { .then( async (versionDetails) => { this.versionDetails = versionDetails; + this.started = true; - if (!this.inDevelopmentMode) { + if (!this.InDevelopmentMode) { this.telemetryReporter.sendTelemetryEvent("powershellVersionCheck", { powershellVersion: versionDetails.version }); } diff --git a/src/utils.ts b/src/utils.ts index bf3c107a66..6319b34c76 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -96,6 +96,10 @@ export function getTimestampString() { return `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}]`; } +export function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + export const isMacOS: boolean = process.platform === "darwin"; export const isWindows: boolean = process.platform === "win32"; export const isLinux: boolean = !isMacOS && !isWindows; diff --git a/test/features/ExternalApi.test.ts b/test/features/ExternalApi.test.ts new file mode 100644 index 0000000000..085377174e --- /dev/null +++ b/test/features/ExternalApi.test.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { beforeEach, afterEach } from "mocha"; +import { IExternalPowerShellDetails } from "../../src/features/ExternalApi"; + +const testExtensionId = "ms-vscode.powershell-preview"; + +suite("ExternalApi feature - Registration API", () => { + test("It can register and unregister an extension", async () => { + const sessionId: string = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", testExtensionId); + assert.notStrictEqual(sessionId , ""); + assert.notStrictEqual(sessionId , null); + assert.strictEqual( + await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId), + true); + }); + + test("It can register and unregister an extension with a version", async () => { + const sessionId: string = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", "ms-vscode.powershell-preview", "v2"); + assert.notStrictEqual(sessionId , ""); + assert.notStrictEqual(sessionId , null); + assert.strictEqual( + await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId), + true); + }); + + /* + NEGATIVE TESTS + */ + test("API fails if not registered", async () => { + assert.rejects( + async () => await vscode.commands.executeCommand("PowerShell.GetPowerShellVersionDetails"), + "UUID provided was invalid, make sure you execute the 'PowerShell.RegisterExternalExtension' command and pass in the UUID that it returns to subsequent command executions."); + }); + + test("It can't register the same extension twice", async () => { + const sessionId: string = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", testExtensionId); + try { + assert.rejects( + async () => await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", testExtensionId), + `The extension '${testExtensionId}' is already registered.`); + } finally { + await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId); + } + }); + + test("It can't unregister an extension that isn't registered", async () => { + assert.rejects( + async () => await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", "not-real"), + `No extension registered with session UUID: not-real`); + }); +}); + +suite("ExternalApi feature - Other APIs", () => { + let sessionId: string; + + beforeEach(async () => { + sessionId = await vscode.commands.executeCommand("PowerShell.RegisterExternalExtension", "ms-vscode.powershell-preview"); + }); + + afterEach(async () => { + await vscode.commands.executeCommand("PowerShell.UnregisterExternalExtension", sessionId); + }); + + test("It can get PowerShell version details", async () => { + const versionDetails: IExternalPowerShellDetails = await vscode.commands.executeCommand("PowerShell.GetPowerShellVersionDetails", sessionId); + + assert.notStrictEqual(versionDetails.architecture, ""); + assert.notStrictEqual(versionDetails.architecture, null); + + assert.notStrictEqual(versionDetails.displayName, ""); + assert.notStrictEqual(versionDetails.displayName, null); + + assert.notStrictEqual(versionDetails.exePath, ""); + assert.notStrictEqual(versionDetails.exePath, null); + + assert.notStrictEqual(versionDetails.version, ""); + assert.notStrictEqual(versionDetails.version, null); + + // Start up can take some time... so set the time out to 30s + }).timeout(30000); +}); From 53beb42df678b601531989108e1d69af9afc305a Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 8 Jul 2020 13:05:41 -0700 Subject: [PATCH 2/5] use default parameters --- src/features/ExternalApi.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts index 5dd5301c9a..e23d1c832b 100644 --- a/src/features/ExternalApi.ts +++ b/src/features/ExternalApi.ts @@ -82,9 +82,9 @@ export class ExternalApiFeature implements IFeature { RETURNS: true if it worked, otherwise throws an error. */ - vscode.commands.registerCommand("PowerShell.UnregisterExternalExtension", (uuid: string): boolean => { + vscode.commands.registerCommand("PowerShell.UnregisterExternalExtension", (uuid: string = ""): boolean => { log.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); - if (!uuid && !ExternalApiFeature.registeredExternalExtension.delete(uuid)) { + if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) { throw new Error(`No extension registered with session UUID: ${uuid}`); } return true; @@ -109,8 +109,8 @@ export class ExternalApiFeature implements IFeature { architecture: string; } */ - vscode.commands.registerCommand("PowerShell.GetPowerShellVersionDetails", async (uuid: string): Promise => { - if (!uuid && !ExternalApiFeature.registeredExternalExtension.has(uuid)) { + vscode.commands.registerCommand("PowerShell.GetPowerShellVersionDetails", async (uuid: string = ""): Promise => { + if (!ExternalApiFeature.registeredExternalExtension.has(uuid)) { throw new Error( "UUID provided was invalid, make sure you execute the 'PowerShell.GetPowerShellVersionDetails' command and pass in the UUID that it returns to subsequent command executions."); } From 64fa2570d5cef3cf6214143fb351dec8957fa686 Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Wed, 8 Jul 2020 13:44:13 -0700 Subject: [PATCH 3/5] most of robs feedback --- src/features/ExternalApi.ts | 123 +++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/src/features/ExternalApi.ts b/src/features/ExternalApi.ts index e23d1c832b..d662d39c66 100644 --- a/src/features/ExternalApi.ts +++ b/src/features/ExternalApi.ts @@ -8,11 +8,6 @@ import { IFeature } from "../feature"; import { Logger } from "../logging"; import { SessionManager } from "../session"; -interface IExternalExtension { - readonly id: string; - readonly apiVersion: string; -} - export interface IExternalPowerShellDetails { exePath: string; version: string; @@ -41,33 +36,8 @@ export class ExternalApiFeature implements IFeature { RETURNS: string session uuid */ - vscode.commands.registerCommand("PowerShell.RegisterExternalExtension", (id: string, apiVersion: string = 'v1'): string => { - log.writeDiagnostic(`Registering extension '${id}' for use with API version '${apiVersion}'.`); - - for (const [_, externalExtension] of ExternalApiFeature.registeredExternalExtension) { - if (externalExtension.id === id) { - const message = `The extension '${id}' is already registered.`; - log.writeWarning(message); - throw new Error(message); - } - } - - if (!vscode.extensions.all.some(ext => ext.id === id)) { - throw new Error(`No extension installed with id '${id}'. You must use a valid extension id.`); - } - - // If we're in development mode, we allow these to be used for testing purposes. - if (!sessionManager.InDevelopmentMode && (id === "ms-vscode.PowerShell" || id === "ms-vscode.PowerShell-Preview")) { - throw new Error("You can't use the PowerShell extension's id in this registration."); - } - - const uuid = uuidv4(); - ExternalApiFeature.registeredExternalExtension.set(uuid, { - id, - apiVersion - }); - return uuid; - }), + vscode.commands.registerCommand("PowerShell.RegisterExternalExtension", (id: string, apiVersion: string = 'v1'): string => + this.registerExternalExtension(id, apiVersion)), /* DESCRIPTION: @@ -82,13 +52,8 @@ export class ExternalApiFeature implements IFeature { RETURNS: true if it worked, otherwise throws an error. */ - vscode.commands.registerCommand("PowerShell.UnregisterExternalExtension", (uuid: string = ""): boolean => { - log.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); - if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) { - throw new Error(`No extension registered with session UUID: ${uuid}`); - } - return true; - }), + vscode.commands.registerCommand("PowerShell.UnregisterExternalExtension", (uuid: string = ""): boolean => + this.unregisterExternalExtension(uuid)), /* DESCRIPTION: @@ -109,27 +74,66 @@ export class ExternalApiFeature implements IFeature { architecture: string; } */ - vscode.commands.registerCommand("PowerShell.GetPowerShellVersionDetails", async (uuid: string = ""): Promise => { - if (!ExternalApiFeature.registeredExternalExtension.has(uuid)) { - throw new Error( - "UUID provided was invalid, make sure you execute the 'PowerShell.GetPowerShellVersionDetails' command and pass in the UUID that it returns to subsequent command executions."); - } + vscode.commands.registerCommand("PowerShell.GetPowerShellVersionDetails", (uuid: string = ""): Promise => + this.getPowerShellVersionDetails(uuid)), + ] + } - // TODO: When we have more than one API version, make sure to include a check here. - const extension = ExternalApiFeature.registeredExternalExtension.get(uuid); - log.writeDiagnostic(`Extension '${extension.id}' used command 'PowerShell.GetPowerShellVersionDetails'.`); + private registerExternalExtension(id: string, apiVersion: string = 'v1'): string { + this.log.writeDiagnostic(`Registering extension '${id}' for use with API version '${apiVersion}'.`); - await sessionManager.waitUntilStarted(); - const versionDetails = sessionManager.getPowerShellVersionDetails(); + for (const [_, externalExtension] of ExternalApiFeature.registeredExternalExtension) { + if (externalExtension.id === id) { + const message = `The extension '${id}' is already registered.`; + this.log.writeWarning(message); + throw new Error(message); + } + } - return { - exePath: sessionManager.PowerShellExeDetails.exePath, - version: versionDetails.version, - displayName: sessionManager.PowerShellExeDetails.displayName, // comes from the Session Menu - architecture: versionDetails.architecture - }; - }), - ] + if (!vscode.extensions.all.some(ext => ext.id === id)) { + throw new Error(`No extension installed with id '${id}'. You must use a valid extension id.`); + } + + // If we're in development mode, we allow these to be used for testing purposes. + if (!this.sessionManager.InDevelopmentMode && (id === "ms-vscode.PowerShell" || id === "ms-vscode.PowerShell-Preview")) { + throw new Error("You can't use the PowerShell extension's id in this registration."); + } + + const uuid = uuidv4(); + ExternalApiFeature.registeredExternalExtension.set(uuid, { + id, + apiVersion + }); + return uuid; + } + + private unregisterExternalExtension(uuid: string = ""): boolean { + this.log.writeDiagnostic(`Unregistering extension with session UUID: ${uuid}`); + if (!ExternalApiFeature.registeredExternalExtension.delete(uuid)) { + throw new Error(`No extension registered with session UUID: ${uuid}`); + } + return true; + } + + private async getPowerShellVersionDetails(uuid: string = ""): Promise { + if (!ExternalApiFeature.registeredExternalExtension.has(uuid)) { + throw new Error( + "UUID provided was invalid, make sure you execute the 'PowerShell.GetPowerShellVersionDetails' command and pass in the UUID that it returns to subsequent command executions."); + } + + // TODO: When we have more than one API version, make sure to include a check here. + const extension = ExternalApiFeature.registeredExternalExtension.get(uuid); + this.log.writeDiagnostic(`Extension '${extension.id}' used command 'PowerShell.GetPowerShellVersionDetails'.`); + + await this.sessionManager.waitUntilStarted(); + const versionDetails = this.sessionManager.getPowerShellVersionDetails(); + + return { + exePath: this.sessionManager.PowerShellExeDetails.exePath, + version: versionDetails.version, + displayName: this.sessionManager.PowerShellExeDetails.displayName, // comes from the Session Menu + architecture: versionDetails.architecture + }; } public dispose() { @@ -142,3 +146,8 @@ export class ExternalApiFeature implements IFeature { this.languageClient = languageclient; } } + +interface IExternalExtension { + readonly id: string; + readonly apiVersion: string; +} From a468b47cfd7ebcd31e3846078acb0264b369c978 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 8 Jul 2020 13:53:56 -0700 Subject: [PATCH 4/5] add signature Co-authored-by: Robert Holt --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 6319b34c76..5f4e9d10fb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -96,7 +96,7 @@ export function getTimestampString() { return `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}]`; } -export function sleep(ms: number) { +export function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } From 435cb390f96cc57df7eb461c24547a8e5d72792f Mon Sep 17 00:00:00 2001 From: Tyler Leonhardt Date: Mon, 13 Jul 2020 10:04:06 -0700 Subject: [PATCH 5/5] continue on error --- .vsts-ci/templates/ci-general.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.vsts-ci/templates/ci-general.yml b/.vsts-ci/templates/ci-general.yml index c3b24a7397..ebd96d11af 100644 --- a/.vsts-ci/templates/ci-general.yml +++ b/.vsts-ci/templates/ci-general.yml @@ -11,6 +11,7 @@ steps: # Using `prependpath` to update the PATH just for this build. Write-Host "##vso[task.prependpath]$powerShellPath" + continueOnError: true displayName: Install PowerShell Daily - pwsh: '$PSVersionTable' displayName: Display PowerShell version information