roku-debug 0.23.16 → 0.24.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/Exceptions.d.ts +4 -0
  3. package/dist/Exceptions.js.map +1 -1
  4. package/dist/FileUtils.js +4 -9
  5. package/dist/FileUtils.js.map +1 -1
  6. package/dist/LaunchConfiguration.d.ts +20 -2
  7. package/dist/PerfettoManager.d.ts +7 -1
  8. package/dist/PerfettoManager.js +11 -6
  9. package/dist/PerfettoManager.js.map +1 -1
  10. package/dist/RendezvousTracker.d.ts +2 -2
  11. package/dist/RendezvousTracker.js +14 -41
  12. package/dist/RendezvousTracker.js.map +1 -1
  13. package/dist/RokuECP.d.ts +11 -4
  14. package/dist/RokuECP.js +48 -62
  15. package/dist/RokuECP.js.map +1 -1
  16. package/dist/SceneGraphDebugCommandController.d.ts +28 -2
  17. package/dist/SceneGraphDebugCommandController.js +100 -4
  18. package/dist/SceneGraphDebugCommandController.js.map +1 -1
  19. package/dist/adapters/DebugProtocolAdapter.d.ts +6 -0
  20. package/dist/adapters/DebugProtocolAdapter.js +30 -11
  21. package/dist/adapters/DebugProtocolAdapter.js.map +1 -1
  22. package/dist/adapters/TelnetAdapter.d.ts +6 -0
  23. package/dist/adapters/TelnetAdapter.js +21 -13
  24. package/dist/adapters/TelnetAdapter.js.map +1 -1
  25. package/dist/adapters/TelnetRequestPipeline.d.ts +3 -4
  26. package/dist/adapters/TelnetRequestPipeline.js.map +1 -1
  27. package/dist/debugProtocol/DebugProtocolClientReplaySession.js +1 -1
  28. package/dist/debugProtocol/DebugProtocolClientReplaySession.js.map +1 -1
  29. package/dist/debugProtocol/client/DebugProtocolClient.d.ts +13 -2
  30. package/dist/debugProtocol/client/DebugProtocolClient.js +21 -14
  31. package/dist/debugProtocol/client/DebugProtocolClient.js.map +1 -1
  32. package/dist/debugProtocol/client/DebugProtocolClientPlugin.d.ts +2 -3
  33. package/dist/debugSession/BrightScriptDebugSession.d.ts +68 -9
  34. package/dist/debugSession/BrightScriptDebugSession.js +286 -110
  35. package/dist/debugSession/BrightScriptDebugSession.js.map +1 -1
  36. package/dist/debugSession/Events.d.ts +5 -5
  37. package/dist/debugSession/Events.js +15 -2
  38. package/dist/debugSession/Events.js.map +1 -1
  39. package/dist/interfaces.d.ts +8 -1
  40. package/dist/interfaces.js.map +1 -1
  41. package/dist/managers/BreakpointManager.d.ts +9 -5
  42. package/dist/managers/BreakpointManager.js +22 -11
  43. package/dist/managers/BreakpointManager.js.map +1 -1
  44. package/dist/managers/LocationManager.js +2 -2
  45. package/dist/managers/LocationManager.js.map +1 -1
  46. package/dist/managers/ProjectManager.d.ts +56 -1
  47. package/dist/managers/ProjectManager.js +146 -23
  48. package/dist/managers/ProjectManager.js.map +1 -1
  49. package/dist/util.d.ts +46 -2
  50. package/dist/util.js +71 -1
  51. package/dist/util.js.map +1 -1
  52. package/package.json +5 -5
@@ -313,6 +313,13 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
313
313
  this.sendEvent(new debugadapter_1.BreakpointEvent(eventName, event));
314
314
  }
315
315
  }
316
+ /**
317
+ * A short human-readable identifier for the target device, safe for log and error messages
318
+ * (never includes credentials like the rceToken)
319
+ */
320
+ get deviceLabel() {
321
+ return util_1.util.getDeviceLabel(this.launchConfiguration.device);
322
+ }
316
323
  get enableDebugProtocol() {
317
324
  var _a;
318
325
  return (_a = this.launchConfiguration) === null || _a === void 0 ? void 0 : _a.enableDebugProtocol;
@@ -477,9 +484,23 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
477
484
  normalizeLaunchConfig(config) {
478
485
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
479
486
  var _w, _x;
487
+ //`device` is the canonical way to address the target device; `host` is a deprecated alias.
488
+ //this is the ONLY place the debugger reads the top-level `host` field: whatever was supplied
489
+ //is resolved to a concrete device config here, and everything downstream uses `device`.
490
+ if (!config.device && config.host) {
491
+ config.device = { host: config.host };
492
+ }
493
+ //the deprecated field is now consumed. Delete it so the runtime object matches the resolved
494
+ //type and nothing downstream (including the configs echoed back to the client) carries it.
495
+ delete config.host;
496
+ //an RCE device config without a token picks one up from the environment (the extension
497
+ //injects ROKU_RCE_TOKEN into this process so the token does not have to travel through the
498
+ //launch config over DAP). The custom events that echo this config back to the client scrub
499
+ //the token out again (see Events.ts), so it never rides the DAP wire in either direction.
500
+ config.device = util_1.util.hydrateRceTokenFromEnv(config.device);
480
501
  (_a = config.cwd) !== null && _a !== void 0 ? _a : (config.cwd = process.cwd());
481
502
  (_b = config.outDir) !== null && _b !== void 0 ? _b : (config.outDir = (0, FileUtils_1.standardizePath) `${config.cwd}/out`);
482
- (_c = config.stagingDir) !== null && _c !== void 0 ? _c : (config.stagingDir = (0, FileUtils_1.standardizePath) `${config.outDir}/.roku-deploy-staging`);
503
+ (_c = config.stagingDir) !== null && _c !== void 0 ? _c : (config.stagingDir = util_1.util.getStagingDir({ outDir: config.outDir, cwd: config.cwd }));
483
504
  (_d = config.componentLibrariesPort) !== null && _d !== void 0 ? _d : (config.componentLibrariesPort = 8080);
484
505
  (_e = config.packagePort) !== null && _e !== void 0 ? _e : (config.packagePort = 80);
485
506
  (_f = config.remotePort) !== null && _f !== void 0 ? _f : (config.remotePort = 8060);
@@ -505,11 +526,16 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
505
526
  return config;
506
527
  }
507
528
  async launchRequest(response, config) {
508
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
529
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
509
530
  const logEnd = this.logger.timeStart('log', '[launchRequest] launch');
510
531
  try {
511
532
  this.resetSessionState();
512
533
  this.launchConfiguration = this.normalizeLaunchConfig(config);
534
+ //fail fast when the launch config supplied no device addressing at all, rather than
535
+ //failing later with a confusing dns or connection error for an undefined host
536
+ if (!this.launchConfiguration.device) {
537
+ return await this.shutdown(`Launch config does not specify a target device. Please supply the 'device' option (or the deprecated 'host' option).`);
538
+ }
513
539
  this.setupProcessErrorHandlers();
514
540
  //prebake some threads for our ProjectManager to use later on (1 for the main project, and 1 for every complib)
515
541
  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));
@@ -518,12 +544,13 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
518
544
  logging_1.logger.logLevel = this.launchConfiguration.logLevel;
519
545
  }
520
546
  this.sendLaunchProgress('start', 'Finding device on network');
521
- //do a DNS lookup for the host to fix issues with roku rejecting ECP
547
+ //do a DNS lookup for the host to fix issues with roku rejecting ECP. Only local devices
548
+ //are addressed by host; other device types (like the Roku Cloud Emulator) pass through unchanged
522
549
  try {
523
- this.launchConfiguration.host = await util_1.util.dnsLookup(this.launchConfiguration.host);
550
+ this.launchConfiguration.device = await this.rokuDeploy.withDnsResolvedHost(this.launchConfiguration.device);
524
551
  }
525
552
  catch (e) {
526
- return this.shutdown(`Could not resolve ip address for host '${this.launchConfiguration.host}'`);
553
+ return this.shutdown(`Could not resolve ip address for host '${this.deviceLabel}'`);
527
554
  }
528
555
  // fetch device info if not supplied via launch config
529
556
  try {
@@ -531,20 +558,20 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
531
558
  this.deviceInfo = roku_deploy_1.rokuDeploy.enhanceDeviceInfo(this.launchConfiguration.deviceInfo);
532
559
  }
533
560
  else {
534
- this.deviceInfo = await roku_deploy_1.rokuDeploy.getDeviceInfo({ host: this.launchConfiguration.host, remotePort: this.launchConfiguration.remotePort, enhance: true, timeout: 4000 });
561
+ this.deviceInfo = await roku_deploy_1.rokuDeploy.getDeviceInfo({ device: this.launchConfiguration.device, ecpPort: this.launchConfiguration.remotePort, enhance: true, timeout: 4000 });
535
562
  }
536
563
  if (this.deviceInfo.ecpSettingMode === 'limited') {
537
- return await this.shutdown(`Please ensure 'Settings' > 'System' > 'Advanced system settings' > 'Control by mobile apps' is set to "Enabled" or "Permissive" on your Roku device to allow the debugger to communicate properly. Current mode: Limited (device: ${this.launchConfiguration.host})`);
564
+ return await this.shutdown(`To allow the debugger to communicate properly, please ensure on the Roku device that 'Settings' > 'System' > 'Advanced system settings' > 'Control by mobile apps' is set to "Enabled" or "Permissive". Current mode: Limited (device: ${this.deviceLabel})`);
538
565
  }
539
566
  }
540
567
  catch (e) {
541
568
  if (e instanceof roku_deploy_1.EcpNetworkAccessModeDisabledError) {
542
- return this.shutdown(`Please ensure 'Settings' > 'System' > 'Advanced system settings' > 'Control by mobile apps' is set to "Enabled" or "Permissive" on your Roku device to allow the debugger to communicate properly. Current mode: Disabled (device: ${this.launchConfiguration.host})`);
569
+ return this.shutdown(`To allow the debugger to communicate properly, please ensure on the Roku device that 'Settings' > 'System' > 'Advanced system settings' > 'Control by mobile apps' is set to "Enabled" or "Permissive". Current mode: Disabled (device: ${this.deviceLabel})`);
543
570
  }
544
- return this.shutdown(`Unable to connect to roku at '${this.launchConfiguration.host}'. Verify the IP address is correct and that the device is powered on and connected to same network as this computer.`);
571
+ return this.shutdown(`Unable to connect to roku at '${this.deviceLabel}'. Verify the device address is correct and that the device is powered on and reachable.`);
545
572
  }
546
573
  if (this.deviceInfo && !this.deviceInfo.developerEnabled) {
547
- return await this.shutdown(`Developer mode is not enabled for host '${this.launchConfiguration.host}'.`);
574
+ return await this.shutdown(`Developer mode is not enabled for device '${this.deviceLabel}'.`);
548
575
  }
549
576
  // everything is ready, send the response to the launch request so the UI can update and configuration can begin
550
577
  this.sendResponse(response);
@@ -571,10 +598,10 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
571
598
  }
572
599
  packageEnd();
573
600
  if (this.enableDebugProtocol) {
574
- util_1.util.log(`Connecting to Roku via the BrightScript debug protocol at ${this.launchConfiguration.host}:${this.launchConfiguration.controlPort}`);
601
+ util_1.util.log(`Connecting to Roku via the BrightScript debug protocol at ${this.deviceLabel}:${this.launchConfiguration.controlPort}`);
575
602
  }
576
603
  else {
577
- util_1.util.log(`Connecting to Roku via telnet at ${this.launchConfiguration.host}:${this.launchConfiguration.brightScriptConsolePort}`);
604
+ util_1.util.log(`Connecting to Roku via telnet at ${this.deviceLabel}:${this.launchConfiguration.brightScriptConsolePort}`);
578
605
  }
579
606
  //activate rendezvous tracking (if enabled). Log the error and move on if it crashes, this shouldn't bring down the session.
580
607
  try {
@@ -588,6 +615,15 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
588
615
  this.sendLaunchProgress('update', 'Connecting to debug server');
589
616
  const connectAdapterEnd = this.logger.timeStart('log', 'Connect adapter');
590
617
  this.createRokuAdapter(this.rendezvousTracker);
618
+ // Manipulating complibs on-device autolaunches the dev app and often triggers compile errors,
619
+ // so if we have at least one installable complib, delete the dev app and any complibs to avoid all that.
620
+ if ((_j = this.launchConfiguration.componentLibraries) === null || _j === void 0 ? void 0 : _j.some(x => x.install)) {
621
+ this.sendLaunchProgress('update', 'Removing existing dev app and component libraries');
622
+ await roku_deploy_1.rokuDeploy.deleteAllSideloadedPlugins({
623
+ device: this.launchConfiguration.device,
624
+ password: this.launchConfiguration.password
625
+ });
626
+ }
591
627
  await this.connectRokuAdapter();
592
628
  connectAdapterEnd();
593
629
  // Capabilities that depend on the adapter or device version. The exception-breakpoint
@@ -615,8 +651,8 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
615
651
  util_1.util.log('Encountered an issue during the launch process');
616
652
  util_1.util.log(e === null || e === void 0 ? void 0 : e.stack);
617
653
  //send any compile errors to the client
618
- await ((_j = this.rokuAdapter) === null || _j === void 0 ? void 0 : _j.sendErrors());
619
- const message = (e instanceof Exceptions_1.SocketConnectionInUseError) ? e.message : ((_k = e === null || e === void 0 ? void 0 : e.stack) !== null && _k !== void 0 ? _k : e);
654
+ await ((_k = this.rokuAdapter) === null || _k === void 0 ? void 0 : _k.sendErrors());
655
+ const message = (e instanceof Exceptions_1.SocketConnectionInUseError) ? e.message : ((_l = e === null || e === void 0 ? void 0 : e.stack) !== null && _l !== void 0 ? _l : e);
620
656
  await this.shutdown(message, true);
621
657
  }
622
658
  else {
@@ -633,7 +669,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
633
669
  try {
634
670
  await this.runAutomaticSceneGraphCommands(this.launchConfiguration.autoRunSgDebugCommands);
635
671
  //press the home button to ensure we're at the home screen
636
- await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
672
+ await this.rokuDeploy.keyPress({ device: this.launchConfiguration.device, key: 'Home', ecpPort: this.launchConfiguration.remotePort });
637
673
  //pass the log level down thought the adapter to the RendezvousTracker and ChanperfTracker
638
674
  this.rokuAdapter.setConsoleOutput(this.launchConfiguration.consoleOutput);
639
675
  //pass along the console output
@@ -649,7 +685,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
649
685
  }
650
686
  this.rokuAdapter.on('device-unresponsive', async (data) => {
651
687
  const stopDebuggerAction = 'Stop Debugger';
652
- const message = `Roku device ${this.launchConfiguration.host} is not responding and may not recover.` +
688
+ const message = `Roku device ${this.deviceLabel} is not responding and may not recover.` +
653
689
  (data.lastCommand ? `\n\nActive command:\n"${util_1.util.truncate(data.lastCommand, 30)}"` : '');
654
690
  this.logger.log(message, data);
655
691
  const response = await this.showPopupMessage(message, 'warn', false, [stopDebuggerAction]);
@@ -697,10 +733,20 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
697
733
  //profiling supports connecting to the socket BEFORE a channel is published, so go ahead and connect now
698
734
  await this.tryProfilingConnectOnStart();
699
735
  //all setBreakpoints requests have arrived by this point (configurationDone is the DAP signal
700
- //that the client has finished sending configuration). Inject the STOPs and seal zips now.
736
+ //that the client has finished sending configuration). Validate breakpoints across every project
737
+ //in one pass (and inject the STOPs for telnet) - validating per project would fail every other
738
+ //project's breakpoints. Must finish before postfixing renames the complib files.
739
+ await this.writeBreakpoints();
740
+ //postfix each complib's own files now (still no zips. those are sealed after cross-project
741
+ //references are rewritten).
742
+ await this.postfixComponentLibraries(this.launchConfiguration.componentLibraries);
743
+ //now that EVERY project (main + all complibs) is postfixed, rewrite cross-project `Library`
744
+ //references to point at the postfixed file names. Must run before any project zip is sealed.
745
+ await this.projectManager.applyLibraryReferencePostfixes();
746
+ //now zip the main project, also zip and upload installable complibs, and start the webserver for non-installed complibs.
701
747
  await Promise.all([
702
- this.packageMainProject(),
703
- this.packageAndHostComponentLibraries(this.launchConfiguration.componentLibraries, this.launchConfiguration.componentLibrariesPort)
748
+ this.zipMainProject(),
749
+ this.zipServeAndInstallComponentLibraries(this.launchConfiguration.componentLibraries, this.launchConfiguration.componentLibrariesPort)
704
750
  ]);
705
751
  this.sendLaunchProgress('update', 'Uploading to Roku');
706
752
  await this.publish();
@@ -717,13 +763,13 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
717
763
  if (!error) {
718
764
  if (this.rokuAdapter.connected) {
719
765
  this.logger.info('Host connection was established before the main public process was completed');
720
- this.logger.log(`deployed to Roku@${this.launchConfiguration.host}`);
766
+ this.logger.log(`deployed to Roku@${this.deviceLabel}`);
721
767
  }
722
768
  else {
723
769
  this.logger.info('Main public process was completed but we are still waiting for a connection to the host');
724
770
  this.rokuAdapter.on('connected', (status) => {
725
771
  if (status) {
726
- this.logger.log(`deployed to Roku@${this.launchConfiguration.host}`);
772
+ this.logger.log(`deployed to Roku@${this.deviceLabel}`);
727
773
  }
728
774
  });
729
775
  }
@@ -738,7 +784,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
738
784
  //if we are at a breakpoint, continue
739
785
  await this.rokuAdapter.continue();
740
786
  //kill the app on the roku
741
- // await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
787
+ // await this.rokuDeploy.keyPress({ device: this.launchConfiguration.device, key: 'Home', ecpPort: this.launchConfiguration.remotePort });
742
788
  //convert a hostname to an ip address
743
789
  const deepLinkUrl = await util_1.util.resolveUrl(this.launchConfiguration.deepLinkUrl);
744
790
  //send the deep link http request
@@ -764,10 +810,10 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
764
810
  * Activate all required functionality for profiling
765
811
  */
766
812
  async initializeProfiling() {
767
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
813
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
768
814
  // Initialize PerfettoManager
769
815
  this.perfettoManager = new PerfettoManager_1.PerfettoManager({
770
- host: this.launchConfiguration.host,
816
+ device: this.launchConfiguration.device,
771
817
  rootDir: this.launchConfiguration.rootDir,
772
818
  remotePort: this.launchConfiguration.remotePort,
773
819
  ...(_a = this.launchConfiguration.profiling) === null || _a === void 0 ? void 0 : _a.tracing
@@ -798,20 +844,20 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
798
844
  if (((_c = (_b = this.launchConfiguration.profiling) === null || _b === void 0 ? void 0 : _b.tracing) === null || _c === void 0 ? void 0 : _c.enable) && this.supportsPerfettoTracing) {
799
845
  this.logger.info('Enabling perfetto tracing because it is supported by the device and enabled in the launch configuration');
800
846
  try {
801
- await this.perfettoManager.enableTracing();
847
+ await ((_e = (_d = this.perfettoManager) === null || _d === void 0 ? void 0 : _d.enableTracing) === null || _e === void 0 ? void 0 : _e.call(_d));
802
848
  }
803
849
  catch (e) {
804
850
  this.logger.error('Failed to enable perfetto tracing', e);
805
851
  }
806
852
  //tracing is expicitly DISabled. turn it off
807
853
  }
808
- 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) {
854
+ else if (((_g = (_f = this.launchConfiguration.profiling) === null || _f === void 0 ? void 0 : _f.tracing) === null || _g === void 0 ? void 0 : _g.enable) === false && this.supportsPerfettoTracing) {
809
855
  this.logger.info('Disabling perfetto tracing because it is disabled in the launch configuration');
810
856
  //TODO implement a way to disable perfetto tracing on the device
811
857
  //tracing was requested but the device firmware does not meet the minimum requirement
812
858
  }
813
- else if (((_g = (_f = this.launchConfiguration.profiling) === null || _f === void 0 ? void 0 : _f.tracing) === null || _g === void 0 ? void 0 : _g.enable) && !this.supportsPerfettoTracing) {
814
- const firmwareVersion = (_j = (_h = this.deviceInfo) === null || _h === void 0 ? void 0 : _h.softwareVersion) !== null && _j !== void 0 ? _j : 'unknown';
859
+ else if (((_j = (_h = this.launchConfiguration.profiling) === null || _h === void 0 ? void 0 : _h.tracing) === null || _j === void 0 ? void 0 : _j.enable) && !this.supportsPerfettoTracing) {
860
+ const firmwareVersion = (_l = (_k = this.deviceInfo) === null || _k === void 0 ? void 0 : _k.softwareVersion) !== null && _l !== void 0 ? _l : 'unknown';
815
861
  const message = `Perfetto profiling is not available: device firmware ${firmwareVersion} is below the minimum required version (15.2). The Perfetto profiling buttons will not be available during this session.`;
816
862
  this.logger.warn(message);
817
863
  this.showPopupMessage(message, 'warn').catch(e => this.logger.error('Failed to show Perfetto unavailable notification', e));
@@ -825,17 +871,17 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
825
871
  * If profiling was marked "connectOnStart", try connecting right away
826
872
  */
827
873
  async tryProfilingConnectOnStart() {
828
- var _a, _b, _c, _d, _e, _f;
874
+ var _a, _b, _c, _d, _e, _f, _g, _h;
829
875
  if (((_b = (_a = this.launchConfiguration.profiling) === null || _a === void 0 ? void 0 : _a.tracing) === null || _b === void 0 ? void 0 : _b.connectOnStart) && this.supportsPerfettoTracing) {
830
876
  try {
831
- await this.perfettoManager.startTracing();
877
+ await ((_d = (_c = this.perfettoManager) === null || _c === void 0 ? void 0 : _c.startTracing) === null || _d === void 0 ? void 0 : _d.call(_c));
832
878
  }
833
879
  catch (e) {
834
880
  this.logger.error('Failed to start perfetto tracing on start', e);
835
881
  }
836
882
  }
837
- else if (((_d = (_c = this.launchConfiguration.profiling) === null || _c === void 0 ? void 0 : _c.tracing) === null || _d === void 0 ? void 0 : _d.connectOnStart) && !this.supportsPerfettoTracing) {
838
- const firmwareVersion = (_f = (_e = this.deviceInfo) === null || _e === void 0 ? void 0 : _e.softwareVersion) !== null && _f !== void 0 ? _f : 'unknown';
883
+ else if (((_f = (_e = this.launchConfiguration.profiling) === null || _e === void 0 ? void 0 : _e.tracing) === null || _f === void 0 ? void 0 : _f.connectOnStart) && !this.supportsPerfettoTracing) {
884
+ const firmwareVersion = (_h = (_g = this.deviceInfo) === null || _g === void 0 ? void 0 : _g.softwareVersion) !== null && _h !== void 0 ? _h : 'unknown';
839
885
  const message = `Perfetto profiling is not available: device firmware ${firmwareVersion} is below the minimum required version (15.2). Tracing will not start automatically.`;
840
886
  this.logger.warn(message);
841
887
  this.showPopupMessage(message, 'warn').catch(e => this.logger.error('Failed to show Perfetto unavailable notification', e));
@@ -915,14 +961,17 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
915
961
  this.sendEvent(new Events_1.DiagnosticsEvent(diagnostics));
916
962
  }
917
963
  async publish() {
918
- var _a, _b;
964
+ var _a, _b, _c;
919
965
  const uploadingEnd = this.logger.timeStart('log', 'Uploading zip');
920
966
  let packageIsPublished = false;
921
967
  //delete any currently installed dev channel (if enabled to do so)
922
968
  try {
923
969
  if (this.launchConfiguration.deleteDevChannelBeforeInstall === true) {
924
- await this.rokuDeploy.deleteInstalledChannel({
925
- ...this.launchConfiguration
970
+ await this.rokuDeploy.deleteDevChannel({
971
+ device: this.launchConfiguration.device,
972
+ password: this.launchConfiguration.password,
973
+ username: this.launchConfiguration.username,
974
+ packagePort: this.launchConfiguration.packagePort
926
975
  });
927
976
  }
928
977
  }
@@ -937,25 +986,28 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
937
986
  }
938
987
  const isConnected = this.rokuAdapter.once('app-ready');
939
988
  const options = {
940
- ...this.launchConfiguration,
941
- //typing fix
942
- logLevel: logging_1.LogLevelPriority[this.logger.logLevel],
989
+ device: this.launchConfiguration.device,
990
+ password: this.launchConfiguration.password,
991
+ username: this.launchConfiguration.username,
992
+ packagePort: this.launchConfiguration.packagePort,
993
+ ecpPort: this.launchConfiguration.remotePort,
994
+ //sideload the zip that was already built from the staging folder (or supplied via packagePath)
995
+ zip: (_c = this.launchConfiguration.packagePath) !== null && _c !== void 0 ? _c : util_1.util.getOutputZipPath({ outDir: this.launchConfiguration.outDir }),
943
996
  // enable the debug protocol if true
944
997
  remoteDebug: this.enableDebugProtocol,
945
998
  //necessary for capturing compile errors from the protocol (has no effect on telnet)
946
999
  remoteDebugConnectEarly: false,
947
1000
  //we don't want to fail if there were compile errors...we'll let our compile error processor handle that
948
1001
  failOnCompileError: true,
1002
+ //deleting the dev channel (when enabled) was already handled at the start of this function
1003
+ deleteDevChannel: false,
1004
+ //the device was already sent to the home screen during configurationDone
1005
+ close: false,
949
1006
  //pass any upload form overrides the client may have configured
950
1007
  packageUploadOverrides: this.launchConfiguration.packageUploadOverrides
951
1008
  };
952
- //if packagePath is specified, use that info instead of outDir and outFile
953
- if (this.launchConfiguration.packagePath) {
954
- options.outDir = path.dirname(this.launchConfiguration.packagePath);
955
- options.outFile = path.basename(this.launchConfiguration.packagePath);
956
- }
957
1009
  //publish the package to the target Roku
958
- const publishPromise = this.rokuDeploy.publish(options).then(() => {
1010
+ const publishPromise = this.rokuDeploy.sideload(options).then(() => {
959
1011
  packageIsPublished = true;
960
1012
  }).catch(async (e) => {
961
1013
  var _a, _b;
@@ -1118,7 +1170,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1118
1170
  }
1119
1171
  async runAutomaticSceneGraphCommands(commands) {
1120
1172
  if (commands) {
1121
- let connection = new SceneGraphDebugCommandController_1.SceneGraphDebugCommandController(this.launchConfiguration.host, this.launchConfiguration.sceneGraphDebugCommandsPort);
1173
+ let connection = new SceneGraphDebugCommandController_1.SceneGraphDebugCommandController(this.launchConfiguration.device, this.launchConfiguration.sceneGraphDebugCommandsPort);
1122
1174
  try {
1123
1175
  await connection.connect();
1124
1176
  for (let command of this.launchConfiguration.autoRunSgDebugCommands) {
@@ -1186,28 +1238,29 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1186
1238
  await this.handleEntryBreakpoint();
1187
1239
  }
1188
1240
  /**
1189
- * Inject breakpoint STOP statements into the staged main project and create the zip package.
1190
- * Runs after the DAP `InitializedEvent` so client-side `setBreakpoints` requests have landed
1191
- * before any STOPs are written to the staged .brs files (telnet path) and before the zip is sealed.
1241
+ * Validate every breakpoint against the staged projects and (telnet only) write STOP statements into the
1242
+ * staged .brs files. Runs after the DAP `InitializedEvent` so client-side `setBreakpoints` requests have
1243
+ * landed before any STOPs are written. Validates the main project and every component library in ONE pass:
1244
+ * a breakpoint is only failed when it can't be placed in any project, so validating per project would fail
1245
+ * every other project's breakpoints. Must complete before `postfixComponentLibraries` renames the complib
1246
+ * files so telnet STOPs are written to the original file names.
1192
1247
  */
1193
- async packageMainProject() {
1194
- var _a;
1248
+ async writeBreakpoints() {
1195
1249
  //add breakpoint lines to source files and then publish
1196
1250
  util_1.util.log('Adding stop statements for active breakpoints');
1197
1251
  //validate breakpoints for all debugger types (and write `stop` statements for telnet — decided internally)
1198
- await this.breakpointManager.validateAndWriteBreakpointsForProject(this.projectManager.mainProject);
1252
+ await this.breakpointManager.validateAndWriteBreakpointsForProjects(this.projectManager.getAllProjects());
1253
+ }
1254
+ /**
1255
+ * Create the main project's zip package (or run the configured packageTask). Must run AFTER
1256
+ * `applyLibraryReferencePostfixes` so any rewritten `Library` statements are included in the zip.
1257
+ */
1258
+ async zipMainProject() {
1259
+ var _a;
1199
1260
  if (this.launchConfiguration.packageTask) {
1200
1261
  util_1.util.log(`Executing task '${this.launchConfiguration.packageTask}' to assemble the app`);
1201
1262
  await this.sendCustomRequest('executeTask', { task: this.launchConfiguration.packageTask });
1202
- const options = {
1203
- ...this.launchConfiguration
1204
- };
1205
- //if packagePath is specified, use that info instead of outDir and outFile
1206
- if (this.launchConfiguration.packagePath) {
1207
- options.outDir = path.dirname(this.launchConfiguration.packagePath);
1208
- options.outFile = path.basename(this.launchConfiguration.packagePath);
1209
- }
1210
- const packagePath = (_a = this.launchConfiguration.packagePath) !== null && _a !== void 0 ? _a : roku_deploy_1.rokuDeploy.getOutputZipFilePath(options);
1263
+ const packagePath = (_a = this.launchConfiguration.packagePath) !== null && _a !== void 0 ? _a : util_1.util.getOutputZipPath({ outDir: this.launchConfiguration.outDir });
1211
1264
  if (!fsExtra.pathExistsSync(packagePath)) {
1212
1265
  return this.shutdown(`Cancelling debug session. Package does not exist at '${packagePath}'`);
1213
1266
  }
@@ -1223,6 +1276,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1223
1276
  * @param command name of the command to execute
1224
1277
  */
1225
1278
  async customRequest(command, response, args) {
1279
+ var _a, _b, _c, _d, _e, _f;
1226
1280
  if (command === 'rendezvous.clearHistory') {
1227
1281
  this.rokuAdapter.clearRendezvousHistory();
1228
1282
  }
@@ -1236,11 +1290,11 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1236
1290
  this.emit('popupMessageEventResponse', args);
1237
1291
  }
1238
1292
  else if (command === 'captureHeapSnapshot') {
1239
- this.perfettoManager.captureHeapSnapshot().catch((e) => this.logger.error('Failed to capture heap snapshot', e));
1293
+ (_b = (_a = this.perfettoManager) === null || _a === void 0 ? void 0 : _a.captureHeapSnapshot) === null || _b === void 0 ? void 0 : _b.call(_a).catch((e) => this.logger.error('Failed to capture heap snapshot', e));
1240
1294
  }
1241
1295
  else if (command === 'startPerfettoTracing') {
1242
1296
  try {
1243
- await this.perfettoManager.startTracing();
1297
+ await ((_d = (_c = this.perfettoManager) === null || _c === void 0 ? void 0 : _c.startTracing) === null || _d === void 0 ? void 0 : _d.call(_c));
1244
1298
  }
1245
1299
  catch (e) {
1246
1300
  response.success = false;
@@ -1249,7 +1303,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1249
1303
  }
1250
1304
  else if (command === 'stopPerfettoTracing') {
1251
1305
  try {
1252
- await this.perfettoManager.stopTracing();
1306
+ await ((_f = (_e = this.perfettoManager) === null || _e === void 0 ? void 0 : _e.stopTracing) === null || _f === void 0 ? void 0 : _f.call(_e));
1253
1307
  }
1254
1308
  catch (e) {
1255
1309
  response.success = false;
@@ -1291,32 +1345,43 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1291
1345
  await Promise.all(this.projectManager.componentLibraryProjects.map(compLibProject => compLibProject.stage()));
1292
1346
  }
1293
1347
  /**
1294
- * Inject breakpoint STOPs into the staged complibs, seal their zips, install installable ones,
1295
- * and start static file hosting. Runs in `configurationDoneRequest` (after `InitializedEvent`)
1296
- * so client-side `setBreakpoints` requests have landed before the staged .brs files are written
1297
- * and the zips are sealed.
1348
+ * Postfix each staged complib's own files (rename its `.brs` files with the `__lib<index>` postfix and
1349
+ * rewrite its `uri=` references). Runs in `configurationDoneRequest` AFTER `writeBreakpoints` so any
1350
+ * telnet STOPs have already been written to the original file names. Kept separate from zipping so the
1351
+ * cross-project `Library` reference rewrite can run after EVERY project is postfixed but before any zip
1352
+ * is sealed.
1298
1353
  */
1299
- async packageAndHostComponentLibraries(componentLibraries, port) {
1354
+ async postfixComponentLibraries(componentLibraries) {
1355
+ if (!componentLibraries || componentLibraries.length === 0) {
1356
+ return;
1357
+ }
1358
+ //postfix each complib's own files in parallel
1359
+ await Promise.all(this.projectManager.componentLibraryProjects.map(compLibProject => compLibProject.postfixFiles()));
1360
+ }
1361
+ /**
1362
+ * Seal every component library's zip, install the installable ones (in order), and start static file
1363
+ * hosting. Must run AFTER `applyLibraryReferencePostfixes` so each zip contains the rewritten `Library`
1364
+ * statements.
1365
+ */
1366
+ async zipServeAndInstallComponentLibraries(componentLibraries, port) {
1367
+ var _a;
1300
1368
  if (!componentLibraries || componentLibraries.length === 0) {
1301
1369
  return;
1302
1370
  }
1303
1371
  const componentLibrariesOutDir = (0, FileUtils_1.standardizePath) `${this.launchConfiguration.outDir}/component-libraries`;
1304
- // Add breakpoint lines to the staging files and before publishing
1305
- util_1.util.log('Adding stop statements for active breakpoints in Component Libraries');
1306
- //validate breakpoints (and write STOPs for telnet), postfix, and zip each complib in parallel — each targets distinct files
1307
- const packagePromises = this.projectManager.componentLibraryProjects.map(async (compLibProject) => {
1308
- await this.breakpointManager.validateAndWriteBreakpointsForProject(compLibProject);
1309
- await compLibProject.postfixFiles();
1310
- await compLibProject.zipPackage({ retainStagingFolder: true });
1311
- });
1312
1372
  const needToDeleteComplibs = this.projectManager.componentLibraryProjects.some(x => x.install);
1313
1373
  if (needToDeleteComplibs) {
1314
- await roku_deploy_1.rokuDeploy.deleteAllComponentLibraries({
1315
- host: this.launchConfiguration.host,
1316
- password: this.launchConfiguration.password,
1317
- username: this.launchConfiguration.username || 'rokudev'
1318
- });
1319
- }
1374
+ //deleteAllComponentLibraries pauses compile-error reporting (to swallow deletion-induced errors) and
1375
+ //leaves it paused. Now that deletion is done and we're about to put libraries back on the device, settle
1376
+ //(draining any lingering deletion output) and resume reporting so real install errors are surfaced.
1377
+ await this.deleteAllComponentLibraries();
1378
+ }
1379
+ //zip each complib in parallel
1380
+ const packagePromises = this.projectManager.componentLibraryProjects.map(compLibProject => compLibProject.zipPackage({ retainStagingFolder: true }));
1381
+ //install component libraries strictly in their declared order (which must be dependency order:
1382
+ //a library may only depend on libraries declared before it). Each install is fully awaited before the
1383
+ //next begins, and a failure aborts the launch. Installing out of order, or continuing past a failed
1384
+ //install, leaves the device with dangling `Library` references that fail to compile.
1320
1385
  for (let i = 0; i < this.projectManager.componentLibraryProjects.length; i++) {
1321
1386
  const compLibProject = this.projectManager.componentLibraryProjects[i];
1322
1387
  if (compLibProject.install === true) {
@@ -1326,25 +1391,29 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1326
1391
  await this.sendCustomRequest('executeTask', { task: componentLibraries[i].packageTask });
1327
1392
  }
1328
1393
  const options = {
1329
- host: this.launchConfiguration.host,
1394
+ device: this.launchConfiguration.device,
1330
1395
  password: this.launchConfiguration.password,
1331
1396
  username: this.launchConfiguration.username || 'rokudev',
1332
- logLevel: logging_1.LogLevelPriority[this.logger.logLevel],
1397
+ packagePort: this.launchConfiguration.packagePort,
1398
+ ecpPort: this.launchConfiguration.remotePort,
1399
+ zip: (_a = componentLibraries[i].packagePath) !== null && _a !== void 0 ? _a : util_1.util.getOutputZipPath({ outDir: compLibProject.outDir, outFile: compLibProject.outFile }),
1333
1400
  failOnCompileError: true,
1334
- outDir: compLibProject.outDir,
1335
- outFile: compLibProject.outFile,
1336
1401
  appType: 'dcl',
1402
+ //installing a component library should never close or delete the sideloaded channel
1403
+ close: false,
1404
+ deleteDevChannel: false,
1337
1405
  packageUploadOverrides: componentLibraries[i].packageUploadOverrides || {}
1338
1406
  };
1339
- if (componentLibraries[i].packagePath) {
1340
- options.outDir = path.dirname(componentLibraries[i].packagePath);
1341
- options.outFile = path.basename(componentLibraries[i].packagePath);
1342
- }
1407
+ util_1.util.log(`Installing component library ${i} (${compLibProject.outFile})`);
1343
1408
  try {
1344
- await roku_deploy_1.rokuDeploy.publish(options);
1409
+ await roku_deploy_1.rokuDeploy.sideload(options);
1410
+ util_1.util.log(`Installed component library ${i} (${compLibProject.outFile})`);
1345
1411
  }
1346
1412
  catch (error) {
1347
- this.logger.error(`Error installing component library ${i}`, error);
1413
+ //do NOT continue installing further libraries (or publishing the main app) - a failed install
1414
+ //here means later libraries and the main app would reference a library that isn't on the device
1415
+ this.logger.error(`Error installing component library ${i} (${compLibProject.outFile})`, error);
1416
+ throw error;
1348
1417
  }
1349
1418
  }
1350
1419
  }
@@ -1357,6 +1426,106 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1357
1426
  hostingPromise
1358
1427
  ]);
1359
1428
  }
1429
+ /**
1430
+ * Delete every component library installed on the device.
1431
+ *
1432
+ * A complib can only depend on complibs the user declared BEFORE it, so the user's configured order is already
1433
+ * dependency order. Deleting in the REVERSE of that order therefore deletes each complib before the ones it
1434
+ * depends on, avoiding compile errors entirely. We use that reverse order as the delete priority.
1435
+ *
1436
+ * Anything still installed that the user did NOT configure (e.g. leftovers from a previous session) has no known
1437
+ * order, so we fall back to compile-error tolerance for those: deleting a complib while another still references
1438
+ * it fails with a compile error, which we ignore and retry later (after its dependent is gone). We finish when
1439
+ * every complib is gone, and fail if the only complibs left are ones that can never be deleted.
1440
+ */
1441
+ async deleteAllComponentLibraries() {
1442
+ var _a;
1443
+ const deviceOptions = {
1444
+ device: this.launchConfiguration.device,
1445
+ password: this.launchConfiguration.password,
1446
+ username: this.launchConfiguration.username || 'rokudev'
1447
+ };
1448
+ //The user's configured complibs, in REVERSE declaration order (dependents before their dependencies). A
1449
+ //complib's position here is its delete priority; complibs the user didn't configure aren't in this list.
1450
+ const deletePriority = this.projectManager.componentLibraryProjects
1451
+ .map(complib => complib.outFile)
1452
+ .reverse();
1453
+ //sort key for a complib: its index in `deletePriority`, or Infinity (delete last) if it wasn't configured
1454
+ const priorityOf = (fileName) => {
1455
+ const index = deletePriority.indexOf(fileName);
1456
+ return index === -1 ? Infinity : index;
1457
+ };
1458
+ const maxAttempts = 5;
1459
+ //how many times we've tried to delete each complib, and the ones we've given up on after maxAttempts
1460
+ const attempts = new Map();
1461
+ const failed = new Set();
1462
+ while (true) {
1463
+ //re-fetch the installed complibs after each iteration: deleting one complib can cascade-delete others, so never delete a stale entry
1464
+ const installed = (await roku_deploy_1.rokuDeploy.listSideloadedPlugins(deviceOptions)).filter(x => x.appType === 'dcl');
1465
+ const attemptCount = (complib) => { var _a; return (_a = attempts.get(complib.archiveFileName)) !== null && _a !== void 0 ? _a : 0; };
1466
+ //of the complibs we haven't given up on, pick the next to delete: fewest attempts first (spreads retries
1467
+ //evenly so a dependent gets deleted before we circle back to a complib that previously failed), and among
1468
+ //equal attempts, follow the reverse-configured delete priority (dependents before dependencies).
1469
+ const candidates = installed.filter(complib => !failed.has(complib.archiveFileName));
1470
+ const next = candidates.sort((a, b) => attemptCount(a) - attemptCount(b) ||
1471
+ priorityOf(a.archiveFileName) - priorityOf(b.archiveFileName))[0];
1472
+ //nothing left to try — done if the device is clear, otherwise hard-fail with whatever remains
1473
+ if (!next) {
1474
+ if (installed.length > 0) {
1475
+ throw new Error(`Failed to delete existing component libraries on device; ${installed.length} still installed: ${installed.map(x => x.archiveFileName).join(', ')}`);
1476
+ }
1477
+ return;
1478
+ }
1479
+ const fileName = next.archiveFileName;
1480
+ attempts.set(fileName, ((_a = attempts.get(fileName)) !== null && _a !== void 0 ? _a : 0) + 1);
1481
+ try {
1482
+ await roku_deploy_1.rokuDeploy.deleteComponentLibrary({ ...deviceOptions, fileName: fileName });
1483
+ }
1484
+ catch (error) {
1485
+ //re-throw anything that isn't a dependency compile error (auth, network, etc.)
1486
+ if (!this.isComponentLibraryDependencyCompileError(error)) {
1487
+ throw error;
1488
+ }
1489
+ //Deleting a complib that a dependent still references produces a compile error - but the delete may
1490
+ //STILL have succeeded (the device reports both a compile error and 'Delete Succeeded'). If it actually
1491
+ //deleted, treat it as done. Only if it did NOT succeed do we defer and retry it later.
1492
+ if (this.wasComponentLibraryDeleteSuccessful(error)) {
1493
+ this.logger.trace(`Deleted '${fileName}' (device reported a compile error but the delete succeeded)`);
1494
+ }
1495
+ else {
1496
+ //another not-yet-deleted complib still depends on this one. Give up on it once it's out of
1497
+ //attempts; otherwise leave it pending so we retry after its dependents are gone.
1498
+ if (attempts.get(fileName) >= maxAttempts) {
1499
+ failed.add(fileName);
1500
+ }
1501
+ this.logger.log(`Deferring delete of '${fileName}'; another component library still depends on it`);
1502
+ }
1503
+ }
1504
+ //the Roku doesn't appreciate back-to-back deletes; give it a moment between requests
1505
+ await util_1.util.sleep(10);
1506
+ }
1507
+ }
1508
+ /**
1509
+ * Is this error the device reporting a compile failure (which, during complib deletion, means another
1510
+ * installed complib still references the one we tried to delete)?
1511
+ */
1512
+ isComponentLibraryDependencyCompileError(error) {
1513
+ var _a;
1514
+ const message = `${(_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : ''}`;
1515
+ return /compile error|compilation failed/i.test(message);
1516
+ }
1517
+ /**
1518
+ * Did the component-library delete actually succeed, even though the device also reported a compile error?
1519
+ * When we delete a complib that a dependent still references, the device reports BOTH a compile error AND a
1520
+ * `Delete Succeeded` message - meaning the complib really was removed. roku-deploy attaches the parsed device
1521
+ * messages (`{ errors, infos, successes }`) to the thrown error as `rokuMessages`; we look for the success there.
1522
+ * Absence of that success message means the delete did NOT happen, so it should be treated as a failure/retry.
1523
+ */
1524
+ wasComponentLibraryDeleteSuccessful(error) {
1525
+ var _a, _b;
1526
+ const successes = (_b = (_a = error === null || error === void 0 ? void 0 : error.rokuMessages) === null || _a === void 0 ? void 0 : _a.successes) !== null && _b !== void 0 ? _b : [];
1527
+ return successes.some(message => /delete succeeded/i.test(message));
1528
+ }
1360
1529
  sourceRequest(response, args) {
1361
1530
  this.logger.log('sourceRequest');
1362
1531
  let old = this.sendResponse;
@@ -1877,7 +2046,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
1877
2046
  else if (v.type === '$$Registry') {
1878
2047
  // This is a special scope variable used to load registry data via an ECP call
1879
2048
  // Send the registry ECP call for the `dev` app as side loaded apps are always `dev`
1880
- await (0, ecpRegistryUtils_1.populateVariableFromRegistryEcp)({ host: this.launchConfiguration.host, remotePort: this.launchConfiguration.remotePort, appId: 'dev' }, v, this.variables, this.getEvaluateRefId.bind(this));
2049
+ await (0, ecpRegistryUtils_1.populateVariableFromRegistryEcp)({ remotePort: this.launchConfiguration.remotePort, device: this.launchConfiguration.device, appId: 'dev' }, v, this.variables, this.getEvaluateRefId.bind(this));
1881
2050
  }
1882
2051
  }
1883
2052
  catch (error) {
@@ -2548,14 +2717,14 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
2548
2717
  */
2549
2718
  async disconnectRequest(response, args, request) {
2550
2719
  //return to the home screen — best effort. The device may already be powered off or unreachable
2551
- //at disconnect time; without a guard pressHomeButton rejects (EHOSTDOWN / ECONNREFUSED / etc)
2720
+ //at disconnect time; without a guard the home key press rejects (EHOSTDOWN / ECONNREFUSED / etc)
2552
2721
  //and because @vscode/debugadapter dispatches this method without awaiting the returned Promise,
2553
2722
  //that rejection escapes as an unhandledRejection and crashes the DAP process.
2554
2723
  //See https://github.com/rokucommunity/vscode-brightscript-language/issues/807
2555
2724
  // https://github.com/rokucommunity/roku-debug/issues/332
2556
2725
  if (!this.enableDebugProtocol) {
2557
2726
  try {
2558
- await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
2727
+ await this.rokuDeploy.keyPress({ device: this.launchConfiguration.device, key: 'Home', ecpPort: this.launchConfiguration.remotePort });
2559
2728
  }
2560
2729
  catch (e) {
2561
2730
  this.logger.warn('Failed to press home button during disconnect; device may be unreachable', e);
@@ -2594,8 +2763,8 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
2594
2763
  }
2595
2764
  try {
2596
2765
  let appStateResult = await RokuECP_1.rokuECP.getAppState({
2597
- host: this.launchConfiguration.host,
2598
2766
  remotePort: this.launchConfiguration.remotePort,
2767
+ device: this.launchConfiguration.device,
2599
2768
  appId: 'dev',
2600
2769
  requestOptions: { timeout: 300 }
2601
2770
  });
@@ -2606,8 +2775,8 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
2606
2775
  // If the app supports Instant Resume and is running in the background or the app does not support Instant Resume and is running, sending this command terminates the app.
2607
2776
  // This means that we might need to send this command twice to terminate the app.
2608
2777
  await RokuECP_1.rokuECP.exitApp({
2609
- host: this.launchConfiguration.host,
2610
2778
  remotePort: this.launchConfiguration.remotePort,
2779
+ device: this.launchConfiguration.device,
2611
2780
  appId: 'dev',
2612
2781
  requestOptions: { timeout: 300 }
2613
2782
  });
@@ -2918,9 +3087,12 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
2918
3087
  else {
2919
3088
  const lastId = this.launchProgressId;
2920
3089
  this.sendEvent(new debugadapter_1.ProgressUpdateEvent(lastId, message));
2921
- setTimeout(() => {
3090
+ const endTimer = setTimeout(() => { var _a; return (_a = this.flushLaunchProgressEnd) === null || _a === void 0 ? void 0 : _a.call(this); }, 1000); // add a slight delay before ending the progress to improve UX
3091
+ this.flushLaunchProgressEnd = () => {
3092
+ clearTimeout(endTimer);
3093
+ this.flushLaunchProgressEnd = undefined;
2922
3094
  this.sendEvent(new debugadapter_1.ProgressEndEvent(lastId, message));
2923
- }, 1000); // add a slight delay before ending the progress to improve UX
3095
+ };
2924
3096
  this.launchProgressId = undefined;
2925
3097
  }
2926
3098
  }
@@ -3001,7 +3173,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
3001
3173
  var _a;
3002
3174
  if (this.shutdownPromise === undefined) {
3003
3175
  this.logger.log('[shutdown] Beginning shutdown sequence', errorMessage);
3004
- //Backstop: if the graceful shutdown hangs (e.g. pressHomeButton against an unreachable
3176
+ //Backstop: if the graceful shutdown hangs (e.g. a home key press against an unreachable
3005
3177
  //device), force-exit anyway so we never leave an orphaned adapter running forever
3006
3178
  const forceExitTimer = setTimeout(() => {
3007
3179
  this.logger.error('[shutdown] graceful shutdown timed out; forcing exit');
@@ -3018,9 +3190,13 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
3018
3190
  return this.shutdownPromise;
3019
3191
  }
3020
3192
  async _shutdown(errorMessage, modal = false) {
3021
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
3193
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
3022
3194
  // Ensure any active launch progress bar is dismissed before showing error messages or the terminated event.
3023
3195
  this.sendLaunchProgress('end', 'Complete');
3196
+ // 'end' defers its ProgressEndEvent for UX; deliver it right now (whether from the line above or from an
3197
+ // earlier 'end' whose delay has not elapsed yet) - the adapter exits before a pending timer would fire,
3198
+ // which would leave the client's progress notification stuck open
3199
+ (_a = this.flushLaunchProgressEnd) === null || _a === void 0 ? void 0 : _a.call(this);
3024
3200
  //send the message FIRST before anything else. This improves the chances that the message will be displayed to the user
3025
3201
  try {
3026
3202
  if (errorMessage) {
@@ -3035,13 +3211,13 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
3035
3211
  }
3036
3212
  // stop perfetto tracing if it's running
3037
3213
  try {
3038
- await this.perfettoManager.stopTracing();
3214
+ await ((_c = (_b = this.perfettoManager) === null || _b === void 0 ? void 0 : _b.stopTracing) === null || _c === void 0 ? void 0 : _c.call(_b));
3039
3215
  }
3040
3216
  catch (e) {
3041
3217
  this.logger.error('Error stopping perfetto tracing', e);
3042
3218
  }
3043
3219
  try {
3044
- await ((_b = (_a = this.perfettoManager) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a));
3220
+ await ((_e = (_d = this.perfettoManager) === null || _d === void 0 ? void 0 : _d.dispose) === null || _e === void 0 ? void 0 : _e.call(_d));
3045
3221
  }
3046
3222
  catch (e) {
3047
3223
  this.logger.error('Error disposing perfetto manager', e);
@@ -3049,11 +3225,11 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
3049
3225
  //close the debugger connection
3050
3226
  try {
3051
3227
  this.logger.log('Destroy rokuAdapter');
3052
- await ((_d = (_c = this.rokuAdapter) === null || _c === void 0 ? void 0 : _c.destroy) === null || _d === void 0 ? void 0 : _d.call(_c));
3228
+ await ((_g = (_f = this.rokuAdapter) === null || _f === void 0 ? void 0 : _f.destroy) === null || _g === void 0 ? void 0 : _g.call(_f));
3053
3229
  //press the home button to return to the home screen
3054
3230
  try {
3055
3231
  this.logger.log('Press home button');
3056
- await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
3232
+ await this.rokuDeploy.keyPress({ device: this.launchConfiguration.device, key: 'Home', ecpPort: this.launchConfiguration.remotePort });
3057
3233
  }
3058
3234
  catch (e) {
3059
3235
  this.logger.error(e);
@@ -3063,25 +3239,25 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
3063
3239
  this.logger.error(e);
3064
3240
  }
3065
3241
  try {
3066
- (_f = (_e = this.projectManager) === null || _e === void 0 ? void 0 : _e.dispose) === null || _f === void 0 ? void 0 : _f.call(_e);
3242
+ (_j = (_h = this.projectManager) === null || _h === void 0 ? void 0 : _h.dispose) === null || _j === void 0 ? void 0 : _j.call(_h);
3067
3243
  }
3068
3244
  catch (e) {
3069
3245
  this.logger.error(e);
3070
3246
  }
3071
3247
  try {
3072
- (_g = this.componentLibraryServer) === null || _g === void 0 ? void 0 : _g.stop();
3248
+ (_k = this.componentLibraryServer) === null || _k === void 0 ? void 0 : _k.stop();
3073
3249
  }
3074
3250
  catch (e) {
3075
3251
  this.logger.error(e);
3076
3252
  }
3077
3253
  try {
3078
- await ((_j = (_h = this.rendezvousTracker) === null || _h === void 0 ? void 0 : _h.destroy) === null || _j === void 0 ? void 0 : _j.call(_h));
3254
+ await ((_m = (_l = this.rendezvousTracker) === null || _l === void 0 ? void 0 : _l.destroy) === null || _m === void 0 ? void 0 : _m.call(_l));
3079
3255
  }
3080
3256
  catch (e) {
3081
3257
  this.logger.error(e);
3082
3258
  }
3083
3259
  try {
3084
- await ((_l = (_k = this.sourceMapManager) === null || _k === void 0 ? void 0 : _k.destroy) === null || _l === void 0 ? void 0 : _l.call(_k));
3260
+ await ((_p = (_o = this.sourceMapManager) === null || _o === void 0 ? void 0 : _o.destroy) === null || _p === void 0 ? void 0 : _p.call(_o));
3085
3261
  }
3086
3262
  catch (e) {
3087
3263
  this.logger.error(e);
@@ -3089,7 +3265,7 @@ class BrightScriptDebugSession extends debugadapter_1.LoggingDebugSession {
3089
3265
  try {
3090
3266
  //if configured, delete the staging directory
3091
3267
  if (!this.launchConfiguration.retainStagingFolder) {
3092
- const stagingDirs = (_o = (_m = this.projectManager) === null || _m === void 0 ? void 0 : _m.getStagingDirs()) !== null && _o !== void 0 ? _o : [];
3268
+ const stagingDirs = (_r = (_q = this.projectManager) === null || _q === void 0 ? void 0 : _q.getStagingDirs()) !== null && _r !== void 0 ? _r : [];
3093
3269
  this.logger.info('deleting staging folders', stagingDirs);
3094
3270
  for (let stagingDir of stagingDirs) {
3095
3271
  try {