roku-debug 0.22.6 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/CompileErrorProcessor.d.ts +6 -0
- package/dist/CompileErrorProcessor.js +41 -0
- package/dist/CompileErrorProcessor.js.map +1 -1
- package/dist/adapters/DebugProtocolAdapter.d.ts +1 -0
- package/dist/adapters/DebugProtocolAdapter.js +5 -0
- package/dist/adapters/DebugProtocolAdapter.js.map +1 -1
- package/dist/adapters/TelnetAdapter.d.ts +1 -0
- package/dist/adapters/TelnetAdapter.js +3 -0
- package/dist/adapters/TelnetAdapter.js.map +1 -1
- package/dist/debugSession/BrightScriptDebugSession.d.ts +22 -2
- package/dist/debugSession/BrightScriptDebugSession.js +182 -4
- package/dist/debugSession/BrightScriptDebugSession.js.map +1 -1
- package/dist/debugSession/Events.d.ts +13 -0
- package/dist/debugSession/Events.js +15 -1
- package/dist/debugSession/Events.js.map +1 -1
- package/dist/logging.js +1 -1
- package/dist/logging.js.map +1 -1
- package/dist/managers/ProjectManager.js +1 -1
- package/dist/managers/ProjectManager.js.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/package.json +3 -3
|
@@ -32,7 +32,7 @@ const ecpRegistryUtils_1 = require("./ecpRegistryUtils");
|
|
|
32
32
|
const RokuECP_1 = require("../RokuECP");
|
|
33
33
|
const Exceptions_1 = require("../Exceptions");
|
|
34
34
|
const diagnosticSource = 'roku-debug';
|
|
35
|
-
class BrightScriptDebugSession extends debugadapter_1.
|
|
35
|
+
class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
|
|
36
36
|
constructor() {
|
|
37
37
|
super();
|
|
38
38
|
this.logger = logging_1.logger.createLogger(`[session]`);
|
|
@@ -41,6 +41,8 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
41
41
|
* A sequence used to help identify log statements for requests
|
|
42
42
|
*/
|
|
43
43
|
this.idCounter = 1;
|
|
44
|
+
this.processErrorHandlersRegistered = false;
|
|
45
|
+
this.isCrashed = false;
|
|
44
46
|
//set imports as class properties so they can be spied upon during testing
|
|
45
47
|
this.rokuDeploy = roku_deploy_1.rokuDeploy;
|
|
46
48
|
this.componentLibraryServer = new ComponentLibraryServer_1.ComponentLibraryServer();
|
|
@@ -87,6 +89,120 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
87
89
|
});
|
|
88
90
|
this.fileLoggingManager = new logging_1.FileLoggingManager();
|
|
89
91
|
}
|
|
92
|
+
start(inStream, outStream) {
|
|
93
|
+
super.start(inStream, outStream);
|
|
94
|
+
// Set up DAP protocol logging as early as possible — immediately after start() so we capture
|
|
95
|
+
// the initialize request and all early DAP traffic before launchRequest config is available.
|
|
96
|
+
// The log file path is injected as ROKU_DAP_LOG_FILE by the extension's DebugAdapterDescriptorFactory,
|
|
97
|
+
// which resolves the path from the `brightscript.debug.debugAdapterProtocolLogging` workspace setting
|
|
98
|
+
// (or the equivalent launch.json property) before the debug adapter process is spawned.
|
|
99
|
+
const dapLogFile = process.env.ROKU_DAP_LOG_FILE;
|
|
100
|
+
if (dapLogFile) {
|
|
101
|
+
// Use LogLevel.Error (not Verbose) as the console threshold so DAP messages are written
|
|
102
|
+
// to the log file but are NOT forwarded to VS Code as OutputEvents, which would flood
|
|
103
|
+
// the debug console and break the extension's output parsing.
|
|
104
|
+
// Note: InternalLogger always writes ALL messages to the file stream regardless of level,
|
|
105
|
+
// so the log file will still contain everything.
|
|
106
|
+
debugadapter_1.logger.setup(debugadapter_1.Logger.LogLevel.Error, dapLogFile);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
setupProcessErrorHandlers() {
|
|
110
|
+
if (this.processErrorHandlersRegistered) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.processErrorHandlersRegistered = true;
|
|
114
|
+
const handleError = (type, error) => {
|
|
115
|
+
var _a, _b, _c, _d;
|
|
116
|
+
const logger = this.logger.createLogger(`${type}`);
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
119
|
+
logger.error(message, stack);
|
|
120
|
+
let output;
|
|
121
|
+
try {
|
|
122
|
+
const debuggerVersion = fsExtra.readJsonSync(path.resolve(__dirname, '../../package.json')).version;
|
|
123
|
+
const clientName = (_b = (_a = this.initRequestArgs) === null || _a === void 0 ? void 0 : _a.clientName) !== null && _b !== void 0 ? _b : 'unknown';
|
|
124
|
+
const clientId = (_d = (_c = this.initRequestArgs) === null || _c === void 0 ? void 0 : _c.clientID) !== null && _d !== void 0 ? _d : 'unknown';
|
|
125
|
+
const issueBodyPrefix = [
|
|
126
|
+
`**Debugger version:** ${debuggerVersion}`,
|
|
127
|
+
`**Client:** ${clientName} (${clientId})`,
|
|
128
|
+
`**Error type:** ${type}`,
|
|
129
|
+
`**Message:** ${message}`,
|
|
130
|
+
'',
|
|
131
|
+
`**Steps to reproduce:**`,
|
|
132
|
+
`<!-- Please describe what you were doing when this crash occurred -->`,
|
|
133
|
+
'',
|
|
134
|
+
'**Stack trace:**',
|
|
135
|
+
'```',
|
|
136
|
+
''
|
|
137
|
+
].join('\n');
|
|
138
|
+
const issueBodySuffix = '\n```';
|
|
139
|
+
const issueTitle = encodeURIComponent(`[crash] ${type}: ${message}`);
|
|
140
|
+
const baseUrl = 'https://github.com/RokuCommunity/roku-debug/issues/new';
|
|
141
|
+
const maxUrlLength = 2000;
|
|
142
|
+
const urlOverhead = `${baseUrl}?title=${issueTitle}&body=`.length;
|
|
143
|
+
const bodyBudget = maxUrlLength - urlOverhead;
|
|
144
|
+
const encodedPrefix = encodeURIComponent(issueBodyPrefix);
|
|
145
|
+
const encodedSuffix = encodeURIComponent(issueBodySuffix);
|
|
146
|
+
const stackBudget = bodyBudget - encodedPrefix.length - encodedSuffix.length;
|
|
147
|
+
let truncatedStack;
|
|
148
|
+
if (!stack) {
|
|
149
|
+
truncatedStack = '(no stack trace)';
|
|
150
|
+
}
|
|
151
|
+
else if (encodeURIComponent(stack).length <= stackBudget) {
|
|
152
|
+
truncatedStack = stack;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
truncatedStack = decodeURIComponent(encodeURIComponent(stack).slice(0, stackBudget)) + '\n...(truncated)';
|
|
156
|
+
}
|
|
157
|
+
const issueUrl = `${baseUrl}?title=${issueTitle}&body=${encodedPrefix}${encodeURIComponent(truncatedStack)}${encodedSuffix}`;
|
|
158
|
+
output = [
|
|
159
|
+
'',
|
|
160
|
+
'================================================================',
|
|
161
|
+
' BRIGHTSCRIPT DEBUGGER INTERNAL ERROR',
|
|
162
|
+
' This is a crash in the debug adapter, not in your application.',
|
|
163
|
+
'================================================================',
|
|
164
|
+
` Error type: ${type}`,
|
|
165
|
+
` Message: ${message}`,
|
|
166
|
+
` Debugger version: ${debuggerVersion}`,
|
|
167
|
+
` Client: ${clientName} (${clientId})`,
|
|
168
|
+
'',
|
|
169
|
+
' Stack trace:',
|
|
170
|
+
...(stack !== null && stack !== void 0 ? stack : '(no stack trace)').split('\n').map(l => ` ${l}`),
|
|
171
|
+
'',
|
|
172
|
+
' Please report this at:',
|
|
173
|
+
` ${issueUrl}`,
|
|
174
|
+
'================================================================',
|
|
175
|
+
''
|
|
176
|
+
].join('\n');
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
output = JSON.stringify({
|
|
180
|
+
name: e.name,
|
|
181
|
+
message: e.message,
|
|
182
|
+
stack: e.stack
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
void this.sendLogOutput(output).catch(() => { });
|
|
186
|
+
this.isCrashed = true;
|
|
187
|
+
this.sendEvent(new Events_1.ProcessCrashEvent({ type, message, stack }));
|
|
188
|
+
setTimeout(() => void this.shutdown(), 5000);
|
|
189
|
+
};
|
|
190
|
+
this._uncaughtExceptionHandler = (error) => handleError('uncaughtException', error);
|
|
191
|
+
this._unhandledRejectionHandler = (reason) => handleError('unhandledRejection', reason);
|
|
192
|
+
process.on('uncaughtException', this._uncaughtExceptionHandler);
|
|
193
|
+
process.on('unhandledRejection', this._unhandledRejectionHandler);
|
|
194
|
+
}
|
|
195
|
+
teardownProcessErrorHandlers() {
|
|
196
|
+
if (this._uncaughtExceptionHandler) {
|
|
197
|
+
process.removeListener('uncaughtException', this._uncaughtExceptionHandler);
|
|
198
|
+
this._uncaughtExceptionHandler = undefined;
|
|
199
|
+
}
|
|
200
|
+
if (this._unhandledRejectionHandler) {
|
|
201
|
+
process.removeListener('unhandledRejection', this._unhandledRejectionHandler);
|
|
202
|
+
this._unhandledRejectionHandler = undefined;
|
|
203
|
+
}
|
|
204
|
+
this.processErrorHandlersRegistered = false;
|
|
205
|
+
}
|
|
90
206
|
onDeviceBreakpointsChanged(eventName, data) {
|
|
91
207
|
this.logger.info('Sending verified device breakpoints to client', data);
|
|
92
208
|
//send all verified breakpoints to the client
|
|
@@ -304,6 +420,7 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
304
420
|
//send the response right away so the UI immediately shows the debugger toolbar
|
|
305
421
|
this.sendResponse(response);
|
|
306
422
|
this.launchConfiguration = this.normalizeLaunchConfig(config);
|
|
423
|
+
this.setupProcessErrorHandlers();
|
|
307
424
|
//prebake some threads for our ProjectManager to use later on (1 for the main project, and 1 for every complib)
|
|
308
425
|
BscProjectWorkerPool_1.bscProjectWorkerPool.preload(1 + ((_c = (_b = (_a = this.launchConfiguration) === null || _a === void 0 ? void 0 : _a.componentLibraries) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0));
|
|
309
426
|
//set the logLevel provided by the launch config
|
|
@@ -343,6 +460,7 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
343
460
|
this.logger.log('[launchRequest] Packaging and deploying to roku');
|
|
344
461
|
try {
|
|
345
462
|
const packageEnd = this.logger.timeStart('log', 'Packaging');
|
|
463
|
+
this.sendLaunchProgress('start', 'Packaging');
|
|
346
464
|
//build the main project and all component libraries at the same time
|
|
347
465
|
await Promise.all([
|
|
348
466
|
this.prepareMainProject(),
|
|
@@ -433,12 +551,14 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
433
551
|
});
|
|
434
552
|
//profiling supports connecting to the socket BEFORE a channel is published, so go ahead and connect now
|
|
435
553
|
await this.tryProfilingConnectOnStart();
|
|
554
|
+
this.sendLaunchProgress('update', 'Uploading to Roku');
|
|
436
555
|
await this.publish();
|
|
437
556
|
//hack for certain roku devices that lock up when this event is emitted (no idea why!).
|
|
438
557
|
if (this.launchConfiguration.emitChannelPublishedEvent) {
|
|
439
558
|
this.sendEvent(new Events_1.ChannelPublishedEvent(this.launchConfiguration));
|
|
440
559
|
}
|
|
441
560
|
//tell the adapter adapter that the channel has been launched.
|
|
561
|
+
this.sendLaunchProgress('update', 'Waiting on application');
|
|
442
562
|
await this.rokuAdapter.activate();
|
|
443
563
|
if (this.rokuAdapter.isDestroyed) {
|
|
444
564
|
throw new Error('Debug session encountered an error');
|
|
@@ -461,7 +581,7 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
461
581
|
throw error;
|
|
462
582
|
}
|
|
463
583
|
//at this point, the project has been deployed. If we need to use a deep link, launch it now.
|
|
464
|
-
if (this.launchConfiguration.deepLinkUrl) {
|
|
584
|
+
if (this.launchConfiguration.deepLinkUrl && !this.enableDebugProtocol) {
|
|
465
585
|
//wait until the first entry breakpoint has been hit
|
|
466
586
|
await this.firstRunDeferred.promise;
|
|
467
587
|
//if we are at a breakpoint, continue
|
|
@@ -484,6 +604,9 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
484
604
|
const message = (e instanceof Exceptions_1.SocketConnectionInUseError) ? e.message : ((_f = e === null || e === void 0 ? void 0 : e.stack) !== null && _f !== void 0 ? _f : e);
|
|
485
605
|
await this.shutdown(message, true);
|
|
486
606
|
}
|
|
607
|
+
else {
|
|
608
|
+
this.sendLaunchProgress('end', 'Aborted (compile error)');
|
|
609
|
+
}
|
|
487
610
|
}
|
|
488
611
|
logEnd();
|
|
489
612
|
}
|
|
@@ -491,7 +614,7 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
491
614
|
* Activate all required functionality for profiling
|
|
492
615
|
*/
|
|
493
616
|
async initializeProfiling() {
|
|
494
|
-
var _a, _b, _c;
|
|
617
|
+
var _a, _b, _c, _d, _e;
|
|
495
618
|
// Initialize PerfettoManager
|
|
496
619
|
this.perfettoManager = new PerfettoManager_1.PerfettoManager({
|
|
497
620
|
host: this.launchConfiguration.host,
|
|
@@ -521,14 +644,24 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
521
644
|
error: event.error
|
|
522
645
|
}));
|
|
523
646
|
});
|
|
524
|
-
//
|
|
647
|
+
//tracing is explicitly enabled. Turn it on
|
|
525
648
|
if (((_c = (_b = this.launchConfiguration.profiling) === null || _b === void 0 ? void 0 : _b.tracing) === null || _c === void 0 ? void 0 : _c.enable) && this.supportsPerfettoTracing) {
|
|
649
|
+
this.logger.info('Enabling perfetto tracing because it is supported by the device and enabled in the launch configuration');
|
|
526
650
|
try {
|
|
527
651
|
await this.perfettoManager.enableTracing();
|
|
528
652
|
}
|
|
529
653
|
catch (e) {
|
|
530
654
|
this.logger.error('Failed to enable perfetto tracing', e);
|
|
531
655
|
}
|
|
656
|
+
//tracing is expicitly DISabled. turn it off
|
|
657
|
+
}
|
|
658
|
+
else if (((_e = (_d = this.launchConfiguration.profiling) === null || _d === void 0 ? void 0 : _d.tracing) === null || _e === void 0 ? void 0 : _e.enable) === false && this.supportsPerfettoTracing) {
|
|
659
|
+
this.logger.info('Disabling perfetto tracing because it is disabled in the launch configuration');
|
|
660
|
+
//TODO implement a way to disable perfetto tracing on the device
|
|
661
|
+
//profiling.tracing.enabled is set to `undefined`, which means we should do nothing
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
this.logger.info('Skipping perfetto initalization because `profiling.tracing.enable` is not defined in the launch configuration');
|
|
532
665
|
}
|
|
533
666
|
}
|
|
534
667
|
/**
|
|
@@ -611,6 +744,7 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
611
744
|
//find the first compile error (i.e. first DiagnosticSeverity.Error) if there is one
|
|
612
745
|
this.compileError = diagnostics.find(x => x.severity === brighterscript_1.DiagnosticSeverity.Error);
|
|
613
746
|
if (this.compileError) {
|
|
747
|
+
this.sendLaunchProgress('end', 'Aborted (compile error)');
|
|
614
748
|
this.sendEvent(new debugadapter_1.StoppedEvent(Events_1.StoppedEventReason.exception, this.COMPILE_ERROR_THREAD_ID, `CompileError: ${this.compileError.message}`));
|
|
615
749
|
}
|
|
616
750
|
this.sendEvent(new Events_1.DiagnosticsEvent(diagnostics));
|
|
@@ -693,6 +827,9 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
693
827
|
* @param logOutput
|
|
694
828
|
*/
|
|
695
829
|
sendLogOutput(logOutput) {
|
|
830
|
+
if (this.isCrashed) {
|
|
831
|
+
return Promise.resolve();
|
|
832
|
+
}
|
|
696
833
|
this.fileLoggingManager.writeRokuDeviceLog(logOutput);
|
|
697
834
|
this.pendingSendLogPromise = this.pendingSendLogPromise.then(async () => {
|
|
698
835
|
logOutput = await this.convertBacktracePaths(logOutput);
|
|
@@ -2035,10 +2172,14 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
2035
2172
|
*/
|
|
2036
2173
|
async connectRokuAdapter() {
|
|
2037
2174
|
this.rokuAdapter.on('start', () => {
|
|
2175
|
+
this.sendLaunchProgress('end', 'Complete');
|
|
2038
2176
|
if (!this.firstRunDeferred.isCompleted) {
|
|
2039
2177
|
this.firstRunDeferred.resolve();
|
|
2040
2178
|
}
|
|
2041
2179
|
});
|
|
2180
|
+
this.rokuAdapter.on('launch-status', (message) => {
|
|
2181
|
+
this.sendLaunchProgress('update', message);
|
|
2182
|
+
});
|
|
2042
2183
|
//when the debugger suspends (pauses for debugger input)
|
|
2043
2184
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
2044
2185
|
this.rokuAdapter.on('suspend', async () => {
|
|
@@ -2295,6 +2436,35 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
2295
2436
|
//erase all cached variables
|
|
2296
2437
|
this.variables = {};
|
|
2297
2438
|
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Sends a launch progress event to the client if the client supports progress reporting.
|
|
2441
|
+
* - `'start'`: begins a new progress bar with the given message. Assigns a new progressId.
|
|
2442
|
+
* - `'update'`: updates the message on the active progress bar.
|
|
2443
|
+
* - `'end'`: dismisses the active progress bar with an optional final message.
|
|
2444
|
+
*/
|
|
2445
|
+
sendLaunchProgress(type, message) {
|
|
2446
|
+
var _a;
|
|
2447
|
+
if (!((_a = this.initRequestArgs) === null || _a === void 0 ? void 0 : _a.supportsProgressReporting)) {
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
if (type === 'start') {
|
|
2451
|
+
this.launchProgressId = `rokudebug-launch-${this.idCounter++}`;
|
|
2452
|
+
this.sendEvent(new debugadapter_1.ProgressStartEvent(this.launchProgressId, 'Launching', `${message}...`));
|
|
2453
|
+
}
|
|
2454
|
+
else if (this.launchProgressId) {
|
|
2455
|
+
if (type === 'update') {
|
|
2456
|
+
this.sendEvent(new debugadapter_1.ProgressUpdateEvent(this.launchProgressId, `${message}...`));
|
|
2457
|
+
}
|
|
2458
|
+
else {
|
|
2459
|
+
const lastId = this.launchProgressId;
|
|
2460
|
+
this.sendEvent(new debugadapter_1.ProgressUpdateEvent(lastId, message));
|
|
2461
|
+
setTimeout(() => {
|
|
2462
|
+
this.sendEvent(new debugadapter_1.ProgressEndEvent(lastId, message));
|
|
2463
|
+
}, 1000); // add a slight delay before ending the progress to improve UX
|
|
2464
|
+
this.launchProgressId = undefined;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2298
2468
|
/**
|
|
2299
2469
|
* Tells the client to re-request all variables because we've invalidated them
|
|
2300
2470
|
* @param threadId
|
|
@@ -2379,6 +2549,8 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
2379
2549
|
}
|
|
2380
2550
|
async _shutdown(errorMessage, modal = false) {
|
|
2381
2551
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
2552
|
+
// Ensure any active launch progress bar is dismissed before showing error messages or the terminated event.
|
|
2553
|
+
this.sendLaunchProgress('end', 'Complete');
|
|
2382
2554
|
//send the message FIRST before anything else. This improves the chances that the message will be displayed to the user
|
|
2383
2555
|
try {
|
|
2384
2556
|
if (errorMessage) {
|
|
@@ -2474,6 +2646,12 @@ class BrightScriptDebugSession extends debugadapter_1.DebugSession {
|
|
|
2474
2646
|
catch (e) {
|
|
2475
2647
|
this.logger.error(e);
|
|
2476
2648
|
}
|
|
2649
|
+
try {
|
|
2650
|
+
this.teardownProcessErrorHandlers();
|
|
2651
|
+
}
|
|
2652
|
+
catch (e) {
|
|
2653
|
+
this.logger.error(e);
|
|
2654
|
+
}
|
|
2477
2655
|
}
|
|
2478
2656
|
}
|
|
2479
2657
|
exports.BrightScriptDebugSession = BrightScriptDebugSession;
|