• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / vscode-brightscript-language / #2721

08 Sep 2022 06:10PM UTC coverage: 41.691% (-0.2%) from 41.897%
#2721

push

TwitchBronBron
2.35.2

477 of 1427 branches covered (33.43%)

Branch coverage included in aggregate %.

1126 of 2418 relevant lines covered (46.57%)

7.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.29
/src/DebugConfigurationProvider.ts
1
/* eslint-disable no-template-curly-in-string */
2
import { util as bslangUtil } from 'brighterscript';
1✔
3
import * as dotenv from 'dotenv';
1✔
4
import * as path from 'path';
1✔
5
import * as fsExtra from 'fs-extra';
1✔
6
import type { FileEntry } from 'roku-deploy';
7
import { DefaultFiles } from 'roku-deploy';
1✔
8
import * as rta from 'roku-test-automation';
1✔
9
import type {
10
    CancellationToken,
11
    DebugConfigurationProvider,
12
    ExtensionContext,
13
    WorkspaceFolder
14
} from 'vscode';
15
import * as vscode from 'vscode';
1✔
16
import type { LaunchConfiguration } from 'roku-debug';
17
import { fileUtils } from 'roku-debug';
1✔
18
import { util } from './util';
1✔
19
import type { TelemetryManager } from './managers/TelemetryManager';
20

21
export class BrightScriptDebugConfigurationProvider implements DebugConfigurationProvider {
1✔
22

23
    public constructor(
24
        private context: ExtensionContext,
164✔
25
        private activeDeviceManager: any,
164✔
26
        private telemetryManager: TelemetryManager
164✔
27
    ) {
28
        this.context = context;
164✔
29
        this.activeDeviceManager = activeDeviceManager;
164✔
30

31
        this.configDefaults = {
164✔
32
            type: 'brightscript',
33
            name: 'BrightScript Debug: Launch',
34
            host: '${promptForHost}',
35
            password: '${promptForPassword}',
36
            consoleOutput: 'normal',
37
            request: 'launch',
38
            stopOnEntry: false,
39
            outDir: '${workspaceFolder}/out/',
40
            retainDeploymentArchive: true,
41
            injectRaleTrackerTask: false,
42
            injectRdbOnDeviceComponent: false,
43
            disableScreenSaver: true,
44
            retainStagingFolder: false,
45
            enableVariablesPanel: true,
46
            enableDebuggerAutoRecovery: false,
47
            stopDebuggerOnAppExit: false,
48
            autoRunSgDebugCommands: [],
49
            files: [...DefaultFiles],
50
            enableSourceMaps: true,
51
            packagePort: 80,
52
            enableDebugProtocol: false,
53
            remotePort: 8060
54
        };
55

56
        let config: any = vscode.workspace.getConfiguration('brightscript') || {};
164!
57
        this.showDeviceInfoMessages = config.deviceDiscovery?.showInfoMessages;
164!
58

59
        vscode.workspace.onDidChangeConfiguration((e) => {
164✔
60
            let config: any = vscode.workspace.getConfiguration('brightscript') || {};
×
61
            this.showDeviceInfoMessages = config.deviceDiscovery?.showInfoMessages;
×
62
        });
63
    }
64

65
    //make unit testing easier by adding these imports properties
66
    public fsExtra = fsExtra;
164✔
67
    public util = util;
164✔
68

69
    private configDefaults: any;
70
    private showDeviceInfoMessages: boolean;
71

72
    /**
73
     * Massage a debug configuration just before a debug session is being launched,
74
     * e.g. add all missing attributes to the debug configuration.
75
     */
76
    public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, config: any, token?: CancellationToken): Promise<BrightScriptLaunchConfiguration> {
77
        //send telemetry about this debug session (don't worry, it gets sanitized...we're just checking if certain features are being used)
78
        this.telemetryManager?.sendStartDebugSessionEvent(config);
6!
79

80
        //force a specific staging folder path because sometimes this conflicts with bsconfig.json
81
        config.stagingFolderPath = path.join('${outDir}/.roku-deploy-staging');
6✔
82

83
        // Process the different parts of the config
84
        config = this.processUserWorkspaceSettings(config);
6✔
85

86
        config = await this.sanitizeConfiguration(config, folder);
6✔
87
        config = await this.processEnvFile(folder, config);
6✔
88
        config = await this.processHostParameter(config);
5✔
89
        config = await this.processPasswordParameter(config);
5✔
90
        config = await this.processDeepLinkUrlParameter(config);
5✔
91
        config = await this.processLogfilePath(folder, config);
5✔
92

93
        await this.context.workspaceState.update('enableDebuggerAutoRecovery', config.enableDebuggerAutoRecovery);
5✔
94

95
        return config;
5✔
96
    }
97

98
    /**
99
     * There are several debug-level config values that can be stored in user settings, so get those
100
     */
101
    private processUserWorkspaceSettings(config: BrightScriptLaunchConfiguration) {
102
        //get baseline brightscript.debug user/project settings
103
        let userWorkspaceDebugSettings = {
6✔
104
            enableSourceMaps: true,
105
            enableDebugProtocol: false,
106
            //merge in all of the brightscript.debug properties
107
            ...vscode.workspace.getConfiguration('brightscript.debug') ?? {}
18!
108
        };
109
        //merge the user/workspace settings in with the config (the config wins on conflict)
110
        config = Object.assign(userWorkspaceDebugSettings, config ?? <any>{});
6!
111
        return config;
6✔
112
    }
113

114
    /**
115
     * Takes the launch.json config and applies any defaults to missing values and sanitizes some of the more complex options
116
     * @param config current config object
117
     */
118
    private async sanitizeConfiguration(config: BrightScriptLaunchConfiguration, folder: WorkspaceFolder): Promise<BrightScriptLaunchConfiguration> {
119
        let defaultFilesArray: FileEntry[] = [
6✔
120
            'manifest',
121
            'source/**/*.*',
122
            'components/**/*.*',
123
            'images/**/*.*'
124
        ];
125
        let userWorkspaceSettings: any = vscode.workspace.getConfiguration('brightscript') || {};
6!
126

127
        //make sure we have an object
128
        config = {
6✔
129

130
            //the workspace settings are the baseline
131
            ...userWorkspaceSettings,
132
            //override with any debug-specific settings
133
            ...config
134
        };
135

136
        let folderUri: vscode.Uri;
137
        //use the workspace folder provided
138
        if (folder) {
6!
139
            folderUri = folder.uri;
6✔
140

141
            //if there's only one workspace, use that workspace's folder path
142
        } else if (vscode.workspace.workspaceFolders.length === 1) {
×
143
            folderUri = vscode.workspace.workspaceFolders[0].uri;
×
144
        } else {
145
            //there are multiple workspaces, ask the user to specify which one they want to use
146
            let workspaceFolder = await vscode.window.showWorkspaceFolderPick();
×
147
            if (workspaceFolder) {
×
148
                folderUri = workspaceFolder.uri;
×
149
            }
150
        }
151

152
        if (!folderUri) {
6!
153
            //cancel this whole thing because we can't continue without the user specifying a workspace folder
154
            throw new Error('Cannot determine which workspace to use for brightscript debugging');
×
155
        }
156

157
        //load the brsconfig settings (if available)
158
        let brsconfig = this.getBrsConfig(folderUri);
6✔
159
        if (brsconfig) {
6✔
160
            config = { ...brsconfig, ...config };
4✔
161
        }
162

163
        config.rootDir = this.util.ensureTrailingSlash(config.rootDir ? config.rootDir : '${workspaceFolder}');
6!
164

165
        //Check for depreciated Items
166
        if (config.debugRootDir) {
6!
167
            if (config.sourceDirs) {
×
168
                throw new Error('Cannot set both debugRootDir AND sourceDirs');
×
169
            } else {
170
                config.sourceDirs = [this.util.ensureTrailingSlash(config.debugRootDir)];
×
171
            }
172
        } else if (config.sourceDirs) {
6!
173
            let dirs: string[] = [];
×
174

175
            for (let dir of config.sourceDirs) {
×
176
                dirs.push(this.util.ensureTrailingSlash(dir));
×
177
            }
178
            config.sourceDirs = dirs;
×
179
        } else if (!config.sourceDirs) {
6!
180
            config.sourceDirs = [];
6✔
181
        }
182

183
        if (config.componentLibraries) {
6!
184
            config.componentLibrariesOutDir = this.util.ensureTrailingSlash(config.componentLibrariesOutDir ? config.componentLibrariesOutDir : '${workspaceFolder}/libs');
×
185

186
            for (let library of config.componentLibraries as any) {
×
187
                library.rootDir = this.util.ensureTrailingSlash(library.rootDir);
×
188
                library.files = library.files ? library.files : [...DefaultFiles];
×
189
            }
190
        } else {
191
            //create an empty array so it's easier to reason with downstream
192
            config.componentLibraries = [];
6✔
193
        }
194
        config.componentLibrariesPort = config.componentLibrariesPort ? config.componentLibrariesPort : 8080;
6!
195

196
        // Pass along files needed by RDB to roku-debug
197
        config.rdbFilesBasePath = rta.utils.getDeviceFilesPath();
6✔
198

199
        // Apply any defaults to missing values
200
        config.type = config.type ? config.type : this.configDefaults.type;
6✔
201
        config.name = config.name ? config.name : this.configDefaults.name;
6!
202
        config.host = config.host ? config.host : this.configDefaults.host;
6✔
203
        config.password = config.password ? config.password : this.configDefaults.password;
6✔
204
        config.consoleOutput = config.consoleOutput ? config.consoleOutput : this.configDefaults.consoleOutput;
6!
205
        config.autoRunSgDebugCommands = config.autoRunSgDebugCommands ? config.autoRunSgDebugCommands : this.configDefaults.autoRunSgDebugCommands;
6!
206
        config.request = config.request ? config.request : this.configDefaults.request;
6!
207
        config.stopOnEntry = config.stopOnEntry ?? this.configDefaults.stopOnEntry;
6!
208
        config.outDir = this.util.ensureTrailingSlash(config.outDir ? config.outDir : this.configDefaults.outDir);
6!
209
        config.retainDeploymentArchive = config.retainDeploymentArchive === false ? false : this.configDefaults.retainDeploymentArchive;
6!
210
        config.injectRaleTrackerTask = config.injectRaleTrackerTask === true ? true : this.configDefaults.injectRaleTrackerTask;
6!
211
        config.injectRdbOnDeviceComponent = config.injectRdbOnDeviceComponent === true ? true : this.configDefaults.injectRdbOnDeviceComponent;
6!
212
        config.disableScreenSaver = config.disableScreenSaver === false ? false : this.configDefaults.disableScreenSaver;
6!
213
        config.retainStagingFolder = config.retainStagingFolder ?? this.configDefaults.retainStagingFolder;
6!
214
        config.enableVariablesPanel = 'enableVariablesPanel' in config ? config.enableVariablesPanel : this.configDefaults.enableVariablesPanel;
6!
215
        config.enableDebuggerAutoRecovery = config.enableDebuggerAutoRecovery === true ? true : this.configDefaults.enableDebuggerAutoRecovery;
6!
216
        config.stopDebuggerOnAppExit = config.stopDebuggerOnAppExit === true ? true : this.configDefaults.stopDebuggerOnAppExit;
6✔
217
        config.files = config.files ? config.files : this.configDefaults.files;
6!
218
        config.enableSourceMaps = config.enableSourceMaps === false ? false : this.configDefaults.enableSourceMaps;
6!
219
        config.packagePort = config.packagePort ? config.packagePort : this.configDefaults.packagePort;
6✔
220
        config.remotePort = config.remotePort ? config.remotePort : this.configDefaults.remotePort;
6✔
221
        config.logfilePath = config.logfilePath ?? null;
6!
222

223
        if (config.request !== 'launch') {
6!
224
            await vscode.window.showErrorMessage(`roku-debug only supports the 'launch' request type`);
×
225
        }
226

227
        // Check for the existence of the tracker task file in auto injection is enabled
228
        if (config.injectRaleTrackerTask && await this.util.fileExists(config.raleTrackerTaskFileLocation) === false) {
6!
229
            await vscode.window.showErrorMessage(`injectRaleTrackerTask was set to true but could not find TrackerTask.xml at:\n${config.raleTrackerTaskFileLocation}`);
×
230
        }
231

232
        //for rootDir, replace workspaceFolder now to avoid issues in vscode itself
233
        if (config.rootDir.includes('${workspaceFolder}')) {
6!
234
            config.rootDir = path.normalize(config.rootDir.replace('${workspaceFolder}', folderUri.fsPath));
6✔
235
        }
236

237
        //for outDir, replace workspaceFolder now
238
        if (config.outDir.includes('${workspaceFolder}')) {
6!
239
            config.outDir = path.normalize(config.outDir.replace('${workspaceFolder}', folderUri.fsPath));
6✔
240
        }
241

242
        if (config.stagingFolderPath.includes('${outDir}')) {
6!
243
            config.stagingFolderPath = path.normalize(config.stagingFolderPath.replace('${outDir}', config.outDir));
6✔
244
        }
245
        if (config.stagingFolderPath.includes('${workspaceFolder}')) {
6!
246
            config.stagingFolderPath = path.normalize(config.stagingFolderPath.replace('${workspaceFolder}', folderUri.fsPath));
×
247
        }
248

249
        // Make sure that directory paths end in a trailing slash
250
        if (config.debugRootDir) {
6!
251
            config.debugRootDir = this.util.ensureTrailingSlash(config.debugRootDir);
×
252
        }
253

254
        if (!config.rootDir) {
6!
255
            console.log('No rootDir specified: defaulting to ${workspaceFolder}');
×
256
            //use the current workspace folder
257
            config.rootDir = folderUri.fsPath;
×
258
        }
259

260
        return config;
6✔
261
    }
262

263
    public async processLogfilePath(folder: WorkspaceFolder | undefined, config: BrightScriptLaunchConfiguration) {
264
        if (config?.logfilePath?.trim()) {
15✔
265
            config.logfilePath = config.logfilePath.trim();
5✔
266
            if (config.logfilePath.includes('${workspaceFolder}')) {
5✔
267
                config.logfilePath = config.logfilePath.replace('${workspaceFolder}', folder.uri.fsPath);
1✔
268
            }
269

270
            try {
5✔
271
                config.logfilePath = fileUtils.standardizePath(config.logfilePath);
5✔
272
                //create the logfile folder structure if not exist
273
                fsExtra.ensureDirSync(path.dirname(config.logfilePath));
5✔
274

275
                //create the log file if it doesn't exist
276
                if (!fsExtra.pathExistsSync(config.logfilePath)) {
5✔
277
                    fsExtra.createFileSync(config.logfilePath);
4✔
278
                }
279
                await this.context.workspaceState.update('logfilePath', config.logfilePath);
5✔
280
            } catch (e) {
281
                throw new Error(`Could not create logfile at "${config.logfilePath}"`);
×
282
            }
283
        }
284
        return config;
15✔
285
    }
286

287
    /**
288
     * Reads the manifest file and updates any config values that are mapped to it
289
     * @param folder current workspace folder
290
     * @param config current config object
291
     */
292
    private async processEnvFile(folder: WorkspaceFolder | undefined, config: BrightScriptLaunchConfiguration): Promise<BrightScriptLaunchConfiguration> {
293
        //process .env file if present
294
        if (config.envFile) {
13✔
295
            let envFilePath = config.envFile;
10✔
296
            //resolve ${workspaceFolder} so we can actually load the .env file now
297
            if (config.envFile.includes('${workspaceFolder}')) {
10✔
298
                envFilePath = config.envFile.replace('${workspaceFolder}', folder.uri.fsPath);
8✔
299
            }
300
            if (await this.util.fileExists(envFilePath) === false) {
10✔
301
                throw new Error(`Cannot find .env file at "${envFilePath}`);
3✔
302
            }
303
            //parse the .env file
304
            let envConfig = dotenv.parse(await this.fsExtra.readFile(envFilePath));
7✔
305

306
            // temporarily convert entire config to string for any envConfig replacements.
307
            let configString = JSON.stringify(config);
7✔
308
            let match: RegExpMatchArray;
309
            let regexp = /\$\{env:([\w\d_]*)\}/g;
7✔
310
            let updatedConfigString = configString;
7✔
311

312
            // apply any defined values to env placeholders
313
            while ((match = regexp.exec(configString))) {
7✔
314
                let environmentVariableName = match[1];
7✔
315
                let environmentVariableValue = envConfig[environmentVariableName];
7✔
316

317
                if (environmentVariableValue) {
7✔
318
                    updatedConfigString = updatedConfigString.replace(match[0], environmentVariableValue);
5✔
319
                }
320
            }
321

322
            config = JSON.parse(updatedConfigString);
7✔
323

324
            let configDefaults = {
7✔
325
                rootDir: config.rootDir,
326
                ...this.configDefaults
327
            };
328

329
            // apply any default values to env placeholders
330
            for (let key in config) {
7✔
331
                let configValue = config[key];
98✔
332
                let match: RegExpMatchArray;
333
                //replace all environment variable placeholders with their values
334
                while ((match = regexp.exec(configValue))) {
98✔
335
                    let environmentVariableName = match[1];
2✔
336
                    let environmentVariableValue = envConfig[environmentVariableName];
2✔
337
                    configValue = configDefaults[key];
2✔
338
                    console.log(`The configuration value for ${key} was not found in the env file under the name ${environmentVariableName}. Defaulting the value to: ${configValue}`);
2✔
339
                }
340
                config[key] = configValue;
98✔
341
            }
342
        }
343
        return config;
10✔
344
    }
345

346
    /**
347
     * Validates the host parameter in the config and opens an input ui if set to ${promptForHost}
348
     * @param config  current config object
349
     */
350
    private async processHostParameter(config: BrightScriptLaunchConfiguration): Promise<BrightScriptLaunchConfiguration> {
351
        let showInputBox = false;
5✔
352

353
        if (config.host.trim() === '${promptForHost}' || (config?.deepLinkUrl?.includes('${promptForHost}'))) {
5!
354
            if (this.activeDeviceManager.enabled) {
×
355
                if (this.activeDeviceManager.firstRequestForDevices && !this.activeDeviceManager.getCacheStats().keys) {
×
356
                    let deviceWaitTime = 5000;
×
357
                    if (this.showDeviceInfoMessages) {
×
358
                        await vscode.window.showInformationMessage(`Device Info: Allowing time for device discovery (${deviceWaitTime} ms)`);
×
359
                    }
360

361
                    await util.delay(deviceWaitTime);
×
362
                }
363

364
                let activeDevices = this.activeDeviceManager.getActiveDevices();
×
365

366
                if (activeDevices && Object.keys(activeDevices).length) {
×
367
                    let items = [];
×
368

369
                    // Create the Quick Picker option items
370
                    for (const key of Object.keys(activeDevices)) {
×
371
                        let device = activeDevices[key];
×
372
                        let itemText = `${device.ip} | ${device.deviceInfo['default-device-name']} - ${device.deviceInfo['model-number']}`;
×
373

374
                        if (this.activeDeviceManager.lastUsedDevice && device.deviceInfo['default-device-name'] === this.activeDeviceManager.lastUsedDevice) {
×
375
                            items.unshift(itemText);
×
376
                        } else {
377
                            items.push(itemText);
×
378
                        }
379
                    }
380

381
                    // Give the user the option to type their own IP incase the device they want has not yet been detected on the network
382
                    let manualIpOption = 'Other';
×
383
                    items.push(manualIpOption);
×
384

385
                    let host = await vscode.window.showQuickPick(items, { placeHolder: `Please Select a Roku or use the "${manualIpOption}" option to enter a IP` });
×
386

387
                    if (host === manualIpOption) {
×
388
                        showInputBox = true;
×
389
                    } else if (host) {
×
390
                        let defaultDeviceName = host.substring(host.toLowerCase().indexOf(' | ') + 3, host.toLowerCase().lastIndexOf(' - '));
×
391
                        let deviceIP = host.substring(0, host.toLowerCase().indexOf(' | '));
×
392
                        if (defaultDeviceName) {
×
393
                            this.activeDeviceManager.lastUsedDevice = defaultDeviceName;
×
394
                        }
395
                        config.host = deviceIP;
×
396
                    } else {
397
                        // User canceled. Give them one more change to enter an ip
398
                        showInputBox = true;
×
399
                    }
400
                } else {
401
                    showInputBox = true;
×
402
                }
403
            } else {
404
                showInputBox = true;
×
405
            }
406
        }
407

408
        if (showInputBox) {
5!
409
            config.host = await this.openInputBox('The IP address of your Roku device');
×
410
        }
411
        // #endregion
412

413
        //check the host and throw error if not provided or update the workspace to set last host
414
        if (!config.host) {
5!
415
            throw new Error('Debug session terminated: host is required.');
×
416
        } else {
417
            await this.context.workspaceState.update('remoteHost', config.host);
5✔
418
        }
419

420
        return config;
5✔
421
    }
422

423
    /**
424
     * Validates the password parameter in the config and opens an input ui if set to ${promptForPassword}
425
     * @param config  current config object
426
     */
427
    private async processPasswordParameter(config: BrightScriptLaunchConfiguration) {
428
        //prompt for password if not hardcoded
429
        if (config.password.trim() === '${promptForPassword}') {
5!
430
            config.password = await this.openInputBox('The developer account password for your Roku device.');
×
431
            if (!config.password) {
×
432
                throw new Error('Debug session terminated: password is required.');
×
433
            }
434
        }
435

436
        return config;
5✔
437
    }
438

439
    /**
440
     * Validates the deepLinkUrl parameter in the config and opens an input ui if set to ${promptForDeepLinkUrl} or if the url contains ${promptForQueryParams
441
     * @param config  current config object
442
     */
443
    private async processDeepLinkUrlParameter(config: BrightScriptLaunchConfiguration) {
444
        if (config.deepLinkUrl) {
5!
445
            config.deepLinkUrl = config.deepLinkUrl.replace('${host}', config.host);
×
446
            config.deepLinkUrl = config.deepLinkUrl.replace('${promptForHost}', config.host);
×
447
            if (config.deepLinkUrl.includes('${promptForQueryParams}')) {
×
448
                let queryParams = await this.openInputBox('Querystring params for deep link');
×
449
                config.deepLinkUrl = config.deepLinkUrl.replace('${promptForQueryParams}', queryParams);
×
450
            }
451
            if (config.deepLinkUrl === '${promptForDeepLinkUrl}') {
×
452
                config.deepLinkUrl = await this.openInputBox('Full deep link url');
×
453
            }
454
        }
455
        return config;
5✔
456
    }
457

458
    /**
459
     * Helper to open a vscode input box ui
460
     * @param placeHolder placeHolder text
461
     * @param value default value
462
     */
463
    private async openInputBox(placeHolder: string, value = '') {
×
464
        return vscode.window.showInputBox({
×
465
            placeHolder: placeHolder,
466
            value: value
467
        });
468
    }
469

470
    /**
471
     * Get the brsConfig file, if available
472
     */
473
    public getBrsConfig(workspaceFolder: vscode.Uri) {
474
        //try to load brsconfig settings
475
        let settings = vscode.workspace.getConfiguration('brightscript', workspaceFolder);
2✔
476
        let configFilePath = settings.get<string>('configFile');
2✔
477
        if (!configFilePath) {
2!
478
            configFilePath = 'bsconfig.json';
2✔
479
        }
480

481
        //if the path is relative, resolve it relative to the workspace folder. If it's absolute, use as is (path.resolve handles this logic for us)
482
        let workspaceFolderPath = bslangUtil.uriToPath(workspaceFolder.toString());
2✔
483
        configFilePath = path.resolve(workspaceFolderPath, configFilePath);
2✔
484
        try {
2✔
485
            let brsconfig = bslangUtil.loadConfigFile(configFilePath);
2✔
486
            return brsconfig;
×
487
        } catch (e) {
488
            console.error(`Could not load brsconfig file at "${configFilePath}`);
2✔
489
            return undefined;
2✔
490
        }
491
    }
492
}
493

494
export interface BrightScriptLaunchConfiguration extends LaunchConfiguration {
495
    /**
496
     * The name of this launch configuration
497
     */
498
    name: string;
499
    /**
500
     * The type of this debug configuration
501
     */
502
    type: string;
503
    /**
504
     * Should the debugger launch or attach. roku-debug only supports launching
505
     */
506
    request: 'launch' | 'attach';
507

508
    /**
509
     * A path to a file where all brightscript console output will be written. If falsey, file logging will be disabled.
510
     */
511
    logfilePath?: string;
512
    /**
513
     *  If true, then the zip archive is NOT deleted after a debug session has been closed.
514
     * @default true
515
     */
516
    retainDeploymentArchive?: boolean;
517

518
    /**
519
     * A path to an environment variables file which will be used to augment the launch config
520
     */
521
    envFile?: string;
522

523
    /**
524
     * If injectRdbOnDeviceComponent is true and this is true the screen saver will be be disabled while the deployed application is running.
525
     */
526
    disableScreenSaver?: boolean;
527

528
    /**
529
     * By default the OnDeviceComponent tries to find an open callback port on the server. This allows you to set one explicitly
530
     */
531
    rdbCallbackPort?: number;
532
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc