From 9ed6b1689a70d8f519eff248e2b68e77230db795 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 1 Mar 2024 16:33:52 -0500 Subject: [PATCH] Add some enhanced launch settings to support more diverse projects (#538) * Add support for some new launch settings * better error serializing * Fix stagingFolderPath reference * Support `packageTask` * Fix indentation * Remove `publishTask` as it's not necessary for this flow * Remove publishTask from package.json * Add `stagingDir` launch option * Add `emitChannelPublishedEvent` launch option * roku-debug@0.21.5 and roku-deploy@3.12.0 --- package-lock.json | 52 +++++++------- package.json | 93 +++++++++++++++++++++++++- src/DebugConfigurationProvider.spec.ts | 4 +- src/DebugConfigurationProvider.ts | 18 ++++- src/extension.ts | 50 +++++++++++++- 5 files changed, 184 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6df71b3e..4770f5ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,8 +32,8 @@ "node-ssdp": "^4.0.0", "postman-request": "^2.88.1-postman.32", "pretty-bytes": "^5.6.0", - "roku-debug": "^0.21.3", - "roku-deploy": "^3.11.2", + "roku-debug": "^0.21.5", + "roku-deploy": "^3.12.0", "roku-test-automation": "2.0.0-beta.22", "semver": "^7.1.3", "source-map": "^0.7.3", @@ -2464,9 +2464,9 @@ } }, "node_modules/brighterscript": { - "version": "0.65.22", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.22.tgz", - "integrity": "sha512-rDSdO33xRnFGB9nr54awTUucpd4oZpZmDMvVUSAb6jmFU1jz+SYhwV9ezc8U5m3Kswap7uV+BmS1O69QZ9MuSw==", + "version": "0.65.23", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.23.tgz", + "integrity": "sha512-oIekpbevsdZoQR93sarPCzeCuMBCyq6x4UdVO9D63ip8Yqytlh4dtcwrbPNIGXCaJw1KBdLhU13+VcbSrn9oUg==", "dependencies": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -2490,7 +2490,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.11.2", + "roku-deploy": "^3.11.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", @@ -8765,13 +8765,13 @@ } }, "node_modules/roku-debug": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/roku-debug/-/roku-debug-0.21.3.tgz", - "integrity": "sha512-vPsj1lrNl/DF6Hr2dbNeOPNl39LI+K0E12+78DDM0pKfAAJro0wpKGd2SNKz+2YCJF33eyfIUOn1BcjKRHCUKg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/roku-debug/-/roku-debug-0.21.5.tgz", + "integrity": "sha512-wNneZwRT0xKRGzWC4koO1dgOrnzEJEm+EnAffJRPS0hkRoqpSgh7zNOJcUyw5aNhDO7dtYMTRnewJ3RtYsO5ZA==", "dependencies": { "@rokucommunity/logger": "^0.3.3", "@types/request": "^2.48.8", - "brighterscript": "^0.65.19", + "brighterscript": "^0.65.23", "dateformat": "^4.6.3", "debounce": "^1.2.1", "eol": "^0.9.1", @@ -8784,7 +8784,7 @@ "postman-request": "^2.88.1-postman.32", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", - "roku-deploy": "^3.11.2", + "roku-deploy": "^3.12.0", "semver": "^7.5.4", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", @@ -8862,9 +8862,9 @@ } }, "node_modules/roku-deploy": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.11.2.tgz", - "integrity": "sha512-3JDlnbTxv6Xk5GVolQoA3+d34MLZXXwZWMySprHwazZoWLP3LvulYHP92YvFOJAo/aI4IZp/TFA8kR82IrmHKA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.0.tgz", + "integrity": "sha512-YiCZeQ+sEmFW9ZfXtMNH+/CBSHQ5deNZYWONM+s6gCEQsrz7kCMFPj5YEdgfqW+d2b8G1ve9GELHcSt2FsfM8g==", "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -12944,9 +12944,9 @@ } }, "brighterscript": { - "version": "0.65.22", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.22.tgz", - "integrity": "sha512-rDSdO33xRnFGB9nr54awTUucpd4oZpZmDMvVUSAb6jmFU1jz+SYhwV9ezc8U5m3Kswap7uV+BmS1O69QZ9MuSw==", + "version": "0.65.23", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.65.23.tgz", + "integrity": "sha512-oIekpbevsdZoQR93sarPCzeCuMBCyq6x4UdVO9D63ip8Yqytlh4dtcwrbPNIGXCaJw1KBdLhU13+VcbSrn9oUg==", "requires": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -12970,7 +12970,7 @@ "parse-ms": "^2.1.0", "readline": "^1.3.0", "require-relative": "^0.8.7", - "roku-deploy": "^3.11.2", + "roku-deploy": "^3.11.3", "serialize-error": "^7.0.1", "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", @@ -17737,13 +17737,13 @@ } }, "roku-debug": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/roku-debug/-/roku-debug-0.21.3.tgz", - "integrity": "sha512-vPsj1lrNl/DF6Hr2dbNeOPNl39LI+K0E12+78DDM0pKfAAJro0wpKGd2SNKz+2YCJF33eyfIUOn1BcjKRHCUKg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/roku-debug/-/roku-debug-0.21.5.tgz", + "integrity": "sha512-wNneZwRT0xKRGzWC4koO1dgOrnzEJEm+EnAffJRPS0hkRoqpSgh7zNOJcUyw5aNhDO7dtYMTRnewJ3RtYsO5ZA==", "requires": { "@rokucommunity/logger": "^0.3.3", "@types/request": "^2.48.8", - "brighterscript": "^0.65.19", + "brighterscript": "^0.65.23", "dateformat": "^4.6.3", "debounce": "^1.2.1", "eol": "^0.9.1", @@ -17756,7 +17756,7 @@ "postman-request": "^2.88.1-postman.32", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", - "roku-deploy": "^3.11.2", + "roku-deploy": "^3.12.0", "semver": "^7.5.4", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", @@ -17816,9 +17816,9 @@ } }, "roku-deploy": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.11.2.tgz", - "integrity": "sha512-3JDlnbTxv6Xk5GVolQoA3+d34MLZXXwZWMySprHwazZoWLP3LvulYHP92YvFOJAo/aI4IZp/TFA8kR82IrmHKA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.0.tgz", + "integrity": "sha512-YiCZeQ+sEmFW9ZfXtMNH+/CBSHQ5deNZYWONM+s6gCEQsrz7kCMFPj5YEdgfqW+d2b8G1ve9GELHcSt2FsfM8g==", "requires": { "chalk": "^2.4.2", "dateformat": "^3.0.3", diff --git a/package.json b/package.json index abbb4200..ebc5f699 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,8 @@ "node-ssdp": "^4.0.0", "postman-request": "^2.88.1-postman.32", "pretty-bytes": "^5.6.0", - "roku-debug": "^0.21.3", - "roku-deploy": "^3.11.2", + "roku-debug": "^0.21.5", + "roku-deploy": "^3.12.0", "roku-test-automation": "2.0.0-beta.22", "semver": "^7.1.3", "source-map": "^0.7.3", @@ -745,6 +745,11 @@ "description": "The folder where the the build artifacts are placed (like the staging folder and .zip files of the apps)", "default": "${workspaceFolder}/out" }, + "stagingDir": { + "type": "string", + "description": "The folder where the vscode will copy all the project files to before creating the zip", + "default": "${workspaceFolder}/out/.roku-deploy-staging" + }, "stopOnEntry": { "type": "boolean", "description": "Should the debugger stop on the first line of the program after launch.", @@ -867,6 +872,11 @@ "description": "If true, the debugger will read any available sourcemaps and attempt to convert debugger locations into their original source locations.", "scope": "resource" }, + "emitChannelPublishedEvent": { + "type": "boolean", + "description": "Should the ChannelPublishedEvent be emitted. This is a hack for when certain roku devices become locked up as a result of this event being emitted. You probably don't need to set this", + "default": true + }, "packagePort": { "type": "number", "description": "The port used for package-related requests. This is mainly used for things like emulators, or when your roku is behind a firewall with a port-forward.", @@ -882,6 +892,38 @@ "deprecationMessage": "Use `fileLogging` option instead", "description": "A path to a file where all brightscript console output will be written. If null or empty, file logging will be disabled." }, + "packageTask": { + "type": "string", + "description": "Task to run instead of roku-deploy to produce the .zip file that will be uploaded to the Roku." + }, + "packagePath": { + "type": "string", + "description": "Path to the .zip that will be uploaded to the Roku" + }, + "packageUploadOverrides": { + "type": "object", + "description": "Overrides for values used during the roku-deploy zip upload process, like the route and various form data. You probably don't need to change these...", + "default": { + "route": "plugin_install", + "formData": {} + }, + "required": [], + "properties": { + "route": { + "type": "string", + "description": "The route to use for uploading to the Roku device. Defaults to 'plugin_install'", + "default": "plugin_install" + }, + "formData": { + "type": "object", + "description": "A dictionary of form fields to be included in the package upload request. Set a value to null to delete from the form", + "additionalProperties": true, + "default": { + "someformfield": "someFormValue" + } + } + } + }, "enableDebugProtocol": { "type": "boolean", "default": false, @@ -2098,6 +2140,12 @@ "default": "${workspaceFolder}/out", "scope": "resource" }, + "brightscript.debug.stagingDir": { + "type": "string", + "description": "The folder where the vscode will copy all the project files to before creating the zip", + "default": "${workspaceFolder}/out/.roku-deploy-staging", + "scope": "resource" + }, "brightscript.debug.stopOnEntry": { "type": "boolean", "description": "Should the debugger stop on the first line of the program after launch.", @@ -2412,6 +2460,47 @@ } ], "scope": "resource" + }, + "brightscript.debug.emitChannelPublishedEvent": { + "type": "boolean", + "description": "Should the ChannelPublishedEvent be emitted. This is a hack for when certain roku devices become locked up as a result of this event being emitted. You probably don't need to set this", + "default": true, + "scope": "resource" + }, + "brightscript.debug.packageTask": { + "type": "string", + "description": "Task to run instead of roku-deploy to produce the .zip file that will be uploaded to the Roku.", + "scope": "resource" + }, + "brightscript.debug.packagePath": { + "type": "string", + "description": "Path to the .zip that will be uploaded to the Roku", + "scope": "resource" + }, + "brightscript.debug.packageUploadOverrides": { + "type": "object", + "description": "Overrides for values used during the roku-deploy zip upload process, like the route and various form data. You probably don't need to change these...", + "default": { + "route": "plugin_install", + "formData": {} + }, + "required": [], + "properties": { + "route": { + "type": "string", + "description": "The route to use for uploading to the Roku device. Defaults to 'plugin_install'", + "default": "plugin_install" + }, + "formData": { + "type": "object", + "description": "A dictionary of form fields to be included in the package upload request. Set a value to null to delete from the form", + "additionalProperties": true, + "default": { + "someformfield": "someFormValue" + } + } + }, + "scope": "resource" } } }, diff --git a/src/DebugConfigurationProvider.spec.ts b/src/DebugConfigurationProvider.spec.ts index dadbdc70..d63d3309 100644 --- a/src/DebugConfigurationProvider.spec.ts +++ b/src/DebugConfigurationProvider.spec.ts @@ -294,11 +294,11 @@ describe('BrightScriptConfigurationProvider', () => { let config = await processEnvFile(folder, { envFile: '${workspaceFolder}/.env', rootDir: '${env:PASSWORD}', - stagingFolderPath: '${env:PASSWORD}' + stagingDir: '${env:PASSWORD}' }); expect(config.rootDir).to.equal('password'); - expect(config.stagingFolderPath).to.equal('password'); + expect(config.stagingDir).to.equal('password'); }); it('does not replace text outside of the ${} syntax', async () => { diff --git a/src/DebugConfigurationProvider.ts b/src/DebugConfigurationProvider.ts index 3e930fca..e7de2cb3 100644 --- a/src/DebugConfigurationProvider.ts +++ b/src/DebugConfigurationProvider.ts @@ -90,7 +90,7 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio result = this.processUserWorkspaceSettings(config); //force a specific stagingDir because sometimes this conflicts with bsconfig.json - result.stagingDir = path.join('${outDir}/.roku-deploy-staging'); + result.stagingDir ??= path.join('${outDir}/.roku-deploy-staging'); result.stagingFolderPath = result.stagingDir; result = await this.sanitizeConfiguration(result, folder); @@ -319,6 +319,15 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio config.rendezvousTracking = config.rendezvousTracking === false ? false : true; config.deleteDevChannelBeforeInstall = config.deleteDevChannelBeforeInstall === true; config.sceneGraphDebugCommandsPort = config.sceneGraphDebugCommandsPort ? config.sceneGraphDebugCommandsPort : this.configDefaults.sceneGraphDebugCommandsPort; + + //if packageTask is defined, make sure there's actually a task with that name defined + if (config.packageTask) { + const targetTask = (await vscode.tasks.fetchTasks()).find(x => x.name === config.packageTask); + if (!targetTask) { + throw new Error(`Cannot find task '${config.packageTask}' for launch option 'packageTask'`); + } + } + if (typeof config.remoteControlMode === 'boolean') { config.remoteControlMode = { activateOnSessionStart: config.remoteControlMode, @@ -378,6 +387,13 @@ export class BrightScriptDebugConfigurationProvider implements DebugConfiguratio config.debugRootDir = this.util.ensureTrailingSlash(config.debugRootDir); } + if (config.packagePath?.includes('${workspaceFolder}')) { + config.packagePath = path.normalize(config.packagePath.replace('${workspaceFolder}', folderUri.fsPath)); + } + if (config.packagePath?.includes('${outDir}')) { + config.packagePath = path.normalize(config.packagePath.replace('${outDir}', config.outDir)); + } + if (!config.rootDir) { console.log('No rootDir specified: defaulting to ${workspaceFolder}'); //use the current workspace folder diff --git a/src/extension.ts b/src/extension.ts index 738a6922..e18cf065 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,7 +22,8 @@ import { languageServerManager } from './LanguageServerManager'; import { TelemetryManager } from './managers/TelemetryManager'; import { RemoteControlManager } from './managers/RemoteControlManager'; import { WhatsNewManager } from './managers/WhatsNewManager'; -import { isChannelPublishedEvent, isChanperfEvent, isDiagnosticsEvent, isDebugServerLogOutputEvent, isLaunchStartEvent, isRendezvousEvent } from 'roku-debug'; +import type { CustomRequestEvent } from 'roku-debug'; +import { isChannelPublishedEvent, isChanperfEvent, isDiagnosticsEvent, isDebugServerLogOutputEvent, isLaunchStartEvent, isRendezvousEvent, isCustomRequestEvent, isExecuteTaskCustomRequest, ClientToServerCustomEventName } from 'roku-debug'; import { RtaManager } from './managers/RtaManager'; import { WebviewViewProviderManager } from './managers/WebviewViewProviderManager'; import { ViewProviderId } from './viewProviders/ViewProviderId'; @@ -222,7 +223,7 @@ export class Extension { //await languageServerPromise; } - private async debugSessionCustomEventHandler(e: any, context: vscode.ExtensionContext, docLinkProvider: LogDocumentLinkProvider, logOutputManager: LogOutputManager, rendezvousViewProvider: RendezvousViewProvider) { + private async debugSessionCustomEventHandler(e: vscode.DebugSessionCustomEvent, context: vscode.ExtensionContext, docLinkProvider: LogDocumentLinkProvider, logOutputManager: LogOutputManager, rendezvousViewProvider: RendezvousViewProvider) { if (isLaunchStartEvent(e)) { const config = e.body as BrightScriptLaunchConfiguration; await docLinkProvider.setLaunchConfig(config); @@ -239,6 +240,8 @@ export class Extension { } else if (isRendezvousEvent(e)) { rendezvousViewProvider.onDidReceiveDebugSessionCustomEvent(e); + } else if (isCustomRequestEvent(e)) { + await this.processCustomRequestEvent(e, e.session); } else if (isChanperfEvent(e)) { if (!e.body.error) { this.chanperfStatusBar.text = `$(dashboard)cpu: ${e.body.cpu.total}%, mem: ${prettyBytes(e.body.memory.total).replace(/ /g, '')}`; @@ -278,6 +281,49 @@ export class Extension { } } + private async processCustomRequestEvent(event: CustomRequestEvent, session: vscode.DebugSession) { + try { + let response: any; + if (isExecuteTaskCustomRequest(event)) { + response = await this.executeTask(event.body.task); + } + await session.customRequest(ClientToServerCustomEventName.customRequestEventResponse, { + requestId: event.body.requestId, + ...response ?? {} + }); + } catch (e) { + await session.customRequest(ClientToServerCustomEventName.customRequestEventResponse, { + requestId: e.body.requestId, + error: { + message: e?.message, + stack: e?.stack + } + }); + } + } + + private async executeTask(taskName: string) { + const tasks = await vscode.tasks.fetchTasks(); + const targetTask = tasks.find(x => x.name === taskName); + if (!targetTask) { + throw new Error(`Cannot find task '$taskName}'`); + } + let execution: vscode.TaskExecution; + let taskFinished = new Promise((resolve, reject) => { + //monitor all ended tasks to see when our task ends + const disposable = vscode.tasks.onDidEndTask((e) => { + if (e.execution === execution) { + disposable.dispose(); + resolve(); + } + }); + }); + + execution = await vscode.tasks.executeTask(targetTask); + console.log(execution); + await taskFinished; + } + /** * Writes text to a logfile if enabled */