diff --git a/package-lock.json b/package-lock.json index 951388d5..29388b20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "natural-orderby": "^2.0.3", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", - "roku-deploy": "^3.7.1", + "roku-deploy": "^3.8.0", "semver": "^7.3.5", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", @@ -3973,9 +3973,9 @@ } }, "node_modules/roku-deploy": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.7.1.tgz", - "integrity": "sha512-xXTYNr4Ug+Kr+bnhDqlJDcbuu6rg8x0MFIpA+36jbpJcqsI6ekbWzRh2QhUG6aZ4F8+zKt8jZFIkZDeyooJJfQ==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.8.0.tgz", + "integrity": "sha512-pcrNUBklhN5t1JNdYPWCpOTcbU1H869DAj2N3SlM+udL7g+cQF73ZHSd6kNOEBg7xF6nvOmrSSgaoVbguxtuUg==", "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", @@ -7326,9 +7326,9 @@ } }, "roku-deploy": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.7.1.tgz", - "integrity": "sha512-xXTYNr4Ug+Kr+bnhDqlJDcbuu6rg8x0MFIpA+36jbpJcqsI6ekbWzRh2QhUG6aZ4F8+zKt8jZFIkZDeyooJJfQ==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.8.0.tgz", + "integrity": "sha512-pcrNUBklhN5t1JNdYPWCpOTcbU1H869DAj2N3SlM+udL7g+cQF73ZHSd6kNOEBg7xF6nvOmrSSgaoVbguxtuUg==", "requires": { "chalk": "^2.4.2", "dateformat": "^3.0.3", diff --git a/package.json b/package.json index 149abe09..88de68f6 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "natural-orderby": "^2.0.3", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", - "roku-deploy": "^3.7.1", + "roku-deploy": "^3.8.0", "semver": "^7.3.5", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", diff --git a/src/debugSession/BrightScriptDebugSession.ts b/src/debugSession/BrightScriptDebugSession.ts index 2b7b5961..a65e021f 100644 --- a/src/debugSession/BrightScriptDebugSession.ts +++ b/src/debugSession/BrightScriptDebugSession.ts @@ -2,7 +2,7 @@ import * as fsExtra from 'fs-extra'; import { orderBy } from 'natural-orderby'; import * as path from 'path'; import * as request from 'request'; -import { rokuDeploy } from 'roku-deploy'; +import { rokuDeploy, CompileError } from 'roku-deploy'; import type { RokuDeploy, RokuDeployOptions } from 'roku-deploy'; import { BreakpointEvent, @@ -330,9 +330,9 @@ export class BrightScriptDebugSession extends BaseDebugSession { await this.connectAndPublish(); - this.sendEvent(new ChannelPublishedEvent({ - launchConfiguration: this.launchConfiguration - })); + this.sendEvent(new ChannelPublishedEvent( + this.launchConfiguration + )); //tell the adapter adapter that the channel has been launched. await this.rokuAdapter.activate(); @@ -356,16 +356,14 @@ export class BrightScriptDebugSession extends BaseDebugSession { } } catch (e) { //if the message is anything other than compile errors, we want to display the error - //TODO: look into the reason why we are getting the 'Invalid response code: 400' on compile errors - if (e.message !== 'compileErrors' && e.message !== 'Invalid response code: 400') { - //TODO make the debugger stop! + if (!(e instanceof CompileError)) { util.log('Encountered an issue during the publish process'); util.log((e as Error).message); this.sendErrorResponse(response, -1, (e as Error).message); - } else { - //request adapter to send errors (even empty) before ending the session - await this.rokuAdapter.sendErrors(); } + + //send any compile errors to the client + await this.rokuAdapter.sendErrors(); this.logger.error('Error. Shutting down.', e); this.shutdown(); return; @@ -399,7 +397,10 @@ export class BrightScriptDebugSession extends BaseDebugSession { let packageIsPublished = false; //publish the package to the target Roku - const publishPromise = this.rokuDeploy.publish(this.launchConfiguration as any as RokuDeployOptions).then(() => { + const publishPromise = this.rokuDeploy.publish({ + ...this.launchConfiguration, + failOnCompileError: true + } as any as RokuDeployOptions).then(() => { packageIsPublished = true; }); diff --git a/src/debugSession/Events.spec.ts b/src/debugSession/Events.spec.ts new file mode 100644 index 00000000..2aea1610 --- /dev/null +++ b/src/debugSession/Events.spec.ts @@ -0,0 +1,26 @@ +import { expect } from 'chai'; +import { isCompileFailureEvent, CompileFailureEvent, isLogOutputEvent, LogOutputEvent, isDebugServerLogOutputEvent, DebugServerLogOutputEvent, isRendezvousEvent, RendezvousEvent, isChanperfEvent, ChanperfEvent, isLaunchStartEvent, LaunchStartEvent, isPopupMessageEvent, PopupMessageEvent, isChannelPublishedEvent, ChannelPublishedEvent } from './Events'; + +describe('Events', () => { + it('is* methods work properly', () => { + //match + expect(isCompileFailureEvent(new CompileFailureEvent(null))).to.be.true; + expect(isLogOutputEvent(new LogOutputEvent(null))).to.be.true; + expect(isDebugServerLogOutputEvent(new DebugServerLogOutputEvent(null))).to.be.true; + expect(isRendezvousEvent(new RendezvousEvent(null))).to.be.true; + expect(isChanperfEvent(new ChanperfEvent(null))).to.be.true; + expect(isLaunchStartEvent(new LaunchStartEvent(null))).to.be.true; + expect(isPopupMessageEvent(new PopupMessageEvent(null, 'error'))).to.be.true; + expect(isChannelPublishedEvent(new ChannelPublishedEvent(null))).to.be.true; + + //not match + expect(isCompileFailureEvent(null)).to.be.false; + expect(isLogOutputEvent(null)).to.be.false; + expect(isDebugServerLogOutputEvent(null)).to.be.false; + expect(isRendezvousEvent(null)).to.be.false; + expect(isChanperfEvent(null)).to.be.false; + expect(isLaunchStartEvent(null)).to.be.false; + expect(isPopupMessageEvent(null)).to.be.false; + expect(isChannelPublishedEvent(null)).to.be.false; + }); +}); diff --git a/src/debugSession/Events.ts b/src/debugSession/Events.ts index 9938bb67..d8e711fd 100644 --- a/src/debugSession/Events.ts +++ b/src/debugSession/Events.ts @@ -1,108 +1,171 @@ +/* eslint-disable @typescript-eslint/no-useless-constructor */ import type { DebugProtocol } from 'vscode-debugprotocol'; import type { BrightScriptDebugCompileError } from '../CompileErrorProcessor'; import type { LaunchConfiguration } from '../LaunchConfiguration'; import type { ChanperfData } from '../ChanperfTracker'; import type { RendezvousHistory } from '../RendezvousTracker'; -export class CompileFailureEvent implements DebugProtocol.Event { - constructor(compileError: BrightScriptDebugCompileError[]) { - this.body = compileError; +export class CustomEvent implements DebugProtocol.Event { + public constructor(body: T) { + this.body = body; + this.event = this.constructor.name; } - - public body: any; + /** + * The body (payload) of the event. + */ + public body: T; + /** + * The name of the event. This name is how the client identifies the type of event and how to handle it + */ public event: string; + /** + * The type of ProtocolMessage. Hardcoded to 'event' for all custom events + */ + public type = 'event'; public seq: number; - public type: string; } -export class LogOutputEvent implements DebugProtocol.Event { - constructor(lines: string) { - this.body = lines; - this.event = 'BSLogOutputEvent'; +/** + * Emitted when compile errors were encountered during the current debug session, + * usually during the initial sideload process as the Roku is compiling the app. + */ +export class CompileFailureEvent extends CustomEvent<{ compileErrors: BrightScriptDebugCompileError[] }> { + constructor(compileErrors: BrightScriptDebugCompileError[]) { + super({ compileErrors }); } +} - public body: any; - public event: string; - public seq: number; - public type: string; +/** + * Is the object a `CompileFailureEvent` + */ +export function isCompileFailureEvent(event: any): event is CompileFailureEvent { + return !!event && event.event === CompileFailureEvent.name; +} + +/** + * A line of log ouptut from the Roku device + */ +export class LogOutputEvent extends CustomEvent<{ line: string }> { + constructor(line: string) { + super({ line }); + } } -export class DebugServerLogOutputEvent extends LogOutputEvent { - constructor(lines: string) { - super(lines); - this.event = 'BSDebugServerLogOutputEvent'; +/** + * Is the object a `LogOutputEvent` + */ +export function isLogOutputEvent(event: any): event is LogOutputEvent { + return !!event && event.event === LogOutputEvent.name; +} + +/** + * Log output from the debug server. These are logs emitted from NodeJS from the various RokuCommunity tools + */ +export class DebugServerLogOutputEvent extends CustomEvent<{ line: string }> { + constructor(line: string) { + super({ line }); } } -export class RendezvousEvent implements DebugProtocol.Event { +/** + * Is the object a `DebugServerLogOutputEvent` + */ +export function isDebugServerLogOutputEvent(event: any): event is DebugServerLogOutputEvent { + return !!event && event.event === DebugServerLogOutputEvent.name; +} + +/** + * Emitted when a rendezvous has occurred. Contains the full history of rendezvous since the start of the current debug session + */ +export class RendezvousEvent extends CustomEvent { constructor(output: RendezvousHistory) { - this.body = output; - this.event = 'BSRendezvousEvent'; + super(output); } +} - public body: RendezvousHistory; - public event: string; - public seq: number; - public type: string; +/** + * Is the object a `RendezvousEvent` + */ +export function isRendezvousEvent(event: any): event is RendezvousEvent { + return !!event && event.event === RendezvousEvent.name; } -export class ChanperfEvent implements DebugProtocol.Event { +/** + * Emitted anytime the debug session receives chanperf data. + */ +export class ChanperfEvent extends CustomEvent { constructor(output: ChanperfData) { - this.body = output; - this.event = 'BSChanperfEvent'; + super(output); } +} - public body: ChanperfData; - public event: string; - public seq: number; - public type: string; +/** + * Is the object a `ChanperfEvent` + */ +export function isChanperfEvent(event: any): event is ChanperfEvent { + return !!event && event.event === ChanperfEvent.name; } -export class LaunchStartEvent implements DebugProtocol.Event { - constructor(args: LaunchConfiguration) { - this.body = args; - this.event = 'BSLaunchStartEvent'; + +/** + * Emitted when the launch sequence first starts. This is right after the debug session receives the `launch` request, + * which happens before any zipping, sideloading, etc. + */ +export class LaunchStartEvent extends CustomEvent { + constructor(launchConfiguration: LaunchConfiguration) { + super(launchConfiguration); } +} - public body: any; - public event: string; - public seq: number; - public type: string; +/** + * Is the object a `LaunchStartEvent` + */ +export function isLaunchStartEvent(event: any): event is LaunchStartEvent { + return !!event && event.event === LaunchStartEvent.name; } -export class PopupMessageEvent implements DebugProtocol.Event { +/** + * This event indicates that the client should show a popup message with the supplied information + */ +export class PopupMessageEvent extends CustomEvent<{ message: string; severity: 'error' | 'info' | 'warn' }> { constructor(message: string, severity: 'error' | 'info' | 'warn') { - this.body = { message, severity }; - this.event = 'BSPopupMessageEvent'; + super({ message, severity }); } +} - public body: any; - public event: string; - public seq: number; - public type: string; +/** + * Is the object a `PopupMessageEvent` + */ +export function isPopupMessageEvent(event: any): event is PopupMessageEvent { + return !!event && event.event === PopupMessageEvent.name; } -export class ChannelPublishedEvent implements DebugProtocol.Event { +/** + * Emitted once the channel has been sideloaded to the channel and the session is ready to start actually debugging. + */ +export class ChannelPublishedEvent extends CustomEvent<{ launchConfiguration: LaunchConfiguration }> { constructor( - body: { - launchConfiguration: LaunchConfiguration; - } + launchConfiguration: LaunchConfiguration ) { - this.body = body ?? {}; - this.event = 'BSChannelPublishedEvent'; + super({ launchConfiguration }); } - - public body: any; - public event: string; - public seq: number; - public type: string; } +/** + * Is the object a `ChannelPublishedEvent` + */ +export function isChannelPublishedEvent(event: any): event is ChannelPublishedEvent { + return !!event && event.event === ChannelPublishedEvent.name; +} export enum StoppedEventReason { step = 'step', breakpoint = 'breakpoint', exception = 'exception', pause = 'pause', - entry = 'entry' + entry = 'entry', + goto = 'goto', + functionBreakpoint = 'function breakpoint', + dataBreakpoint = 'data breakpoint', + instructionBreakpoint = 'instruction breakpoint' } diff --git a/src/index.ts b/src/index.ts index 8e266d1b..553afdd1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export * from './managers/BreakpointManager'; export * from './LaunchConfiguration'; export * from './debugProtocol/Debugger'; export * from './debugSession/BrightScriptDebugSession'; +export * from './debugSession/Events'; export * from './ComponentLibraryServer'; export * from './CompileErrorProcessor'; export * from './debugProtocol/Constants';