diff --git a/CHANGELOG.md b/CHANGELOG.md index 702b77e..5fe7ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [4.0.0-alpha.3] - unreleased +### Changed (Breaking) + - Renamed `RekeyDeviceOptions.rekeySignedPackage` → `pkg` to match CLI `--pkg` option + - Renamed `ZipOptions.stagingDir` → `dir` to match CLI `--dir` option + - Renamed `remotePort` → `ecpPort` throughout all options interfaces and `getOptions()` defaults + - CLI `sideload` command: replaced `--noclose` flag with `--close` boolean (use `--no-close` to skip closing the channel) +### Added + - `sideload()` now accepts `zip` (explicit zip path) and `rootDir` (auto-zip a directory) options directly + - `sideload()` now automatically calls `closeChannel()` before sideloading (controlled by new `close` option, defaults to `true`) + - CLI `sideload --no-close` flag to skip closing the channel before sideloading + + + ## [4.0.0-alpha.2](https://github.com/rokucommunity/roku-deploy/compare/4.0.0-alpha.1...v4.0.0-alpha.2) - 2025-06-02 ### Added - Add interactive remote mode ([#169](https://github.com/rokucommunity/roku-deploy/pull/169)) diff --git a/README.md b/README.md index b3a9d68..8e8f88d 100644 --- a/README.md +++ b/README.md @@ -105,13 +105,16 @@ Lastly, the default files array has changed. node modules and static analysis fi ## CLI Usage ### Sideload a project to your Roku device -Sideload a .zip package or directory to a roku device: +Sideload a .zip package or directory to a roku device. By default, the channel is closed before sideloading. Use `--no-close` to skip. ```shell # Sideload a zip file npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --zip './path/to/your/app.zip' # Sideload from a directory (will be zipped first automatically) npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --rootDir './path/to/your/project' + +# Sideload without closing the channel first +npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --zip './path/to/your/app.zip' --no-close ``` ### Create a signed package from an existing dev channel @@ -212,7 +215,7 @@ Use this logic if you'd like to create a zip from your application folder. //create a signed package of your project rokuDeploy.zip({ outDir: 'folder/to/put/zip', - stagingDir: 'path/to/files/to/zip', + dir: 'path/to/files/to/zip', outFile: 'filename-of-your-app.zip' //...other options if necessary }).then(function(){ @@ -232,14 +235,26 @@ rokuDeploy.keyPress({ ``` ### Sideloading a project -If you've already created a zip using some other tool, you can use roku-deploy to sideload the zip. +Sideload a zip file, a directory, or a pre-built zip at the default `outDir`/`outFile` location. The current dev channel is closed before sideloading by default; pass `close: false` to skip. ```typescript -//sideload a package onto a specified Roku +// Sideload a zip file rokuDeploy.sideload({ host: 'ip-of-roku', password: 'password for roku dev admin portal', - outDir: 'folder/where/your/zip/resides/', - outFile: 'filename-of-your-app.zip' + zip: './path/to/your/app.zip' + //...other options if necessary +}).then(function(){ + //the app has been sideloaded +}, function(error) { + //it failed + console.error(error); +}); + +// Sideload from a source directory (will be zipped automatically) +rokuDeploy.sideload({ + host: 'ip-of-roku', + password: 'password for roku dev admin portal', + rootDir: './path/to/your/project' //...other options if necessary }).then(function(){ //the app has been sideloaded @@ -263,8 +278,7 @@ rokuDeploy.convertToSquashfs({ rokuDeploy.createSignedPackage({ host: '1.2.3.4', password: 'password', - signingPassword: 'signing password', - stagingDir: './path/to/staging/directory' + signingPassword: 'signing password' //...other options if necessary }) ``` @@ -294,7 +308,7 @@ rokuDeploy.captureScreenshot({ rokuDeploy.rekeyDevice({ host: 'ip-of-roku', password: 'password', - rekeySignedPackage: './path/to/signed.pkg' + pkg: './path/to/signed.pkg' //...other options if necessary }) ``` @@ -482,7 +496,7 @@ Here are the available options for customizing to your developer-specific workfl - **signingPassword:** string (*required for signing*) The password used for creating signed packages. -- **rekeySignedPackage:** string (*required for rekeying*) +- **pkg:** string (*required for rekeying*) Path to a copy of the signed package you want to use for rekeying. - **devId:** string @@ -555,10 +569,10 @@ Here are the available options for customizing to your developer-specific workfl just in case roku adds support for custom usernames in the future. - **packagePort?:** number = `80` - The port used for package-related requests. This is mainly used for things like emulators, or when your roku is behind a firewall with a port-forward. + The port used for package-related requests. This is mainly used when your roku is behind a firewall with a port-forward. -- **remotePort?:** number = `8060` - The port used for sending remote control commands (like home press or back press). This is mainly used for things like emulators, or when your roku is behind a firewall with a port-forward. +- **ecpPort?:** number = `8060` + The port used for sending ECP/remote control commands (like key presses). This is mainly used when your roku is behind a firewall with a port-forward. - **screenshotDir?:** string = `"./tmp/roku-deploy/screenshots/"` The directory where screenshots should be saved. Will use the OS temp directory by default. diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index 6901d02..8503d49 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -39,7 +39,7 @@ describe('RokuDeploy', () => { stagingDir: stagingDir, signingPassword: '12345', host: 'localhost', - rekeySignedPackage: `${tempDir}/testSignedPackage.pkg` + pkg: `${tempDir}/testSignedPackage.pkg` }); options.rootDir = rootDir; fsExtra.emptyDirSync(tempDir); @@ -416,7 +416,7 @@ describe('RokuDeploy', () => { it('should use given port if provided', async () => { const stub = mockDoGetRequest(body); - await rokuDeploy.getDeviceInfo({ host: '1.1.1.1', remotePort: 9999 }); + await rokuDeploy.getDeviceInfo({ host: '1.1.1.1', ecpPort: 9999 }); expect(stub.getCall(0).args[0].url).to.eql('http://1.1.1.1:9999/query/device-info'); }); @@ -427,7 +427,7 @@ describe('RokuDeploy', () => { 29380007-0800-1025-80a4-d83154332d7e `); - const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', remotePort: 8060, enhance: true }); + const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', ecpPort: 8060, enhance: true }); expect(result.isStick).not.to.exist; }); @@ -443,7 +443,7 @@ describe('RokuDeploy', () => { it('should sanitize additional data when the host+param+format signature is triggered', async () => { mockDoGetRequest(body); - const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', remotePort: 8060, enhance: true }); + const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', ecpPort: 8060, enhance: true }); expect(result).to.include({ // make sure the number fields are turned into numbers softwareBuild: 4170, @@ -482,7 +482,7 @@ describe('RokuDeploy', () => { it('converts keys to camel case when enabled', async () => { mockDoGetRequest(body); - const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', remotePort: 8060, enhance: true }); + const result = await rokuDeploy.getDeviceInfo({ host: '192.168.1.10', ecpPort: 8060, enhance: true }); const props = [ 'udn', 'serialNumber', @@ -742,7 +742,7 @@ describe('RokuDeploy', () => { try { fsExtra.ensureDirSync(options.stagingDir); await rokuDeploy.zip({ - stagingDir: s`${tempDir}/path/to/nowhere`, + dir: s`${tempDir}/path/to/nowhere`, outDir: outDir }); } catch (e) { @@ -755,7 +755,7 @@ describe('RokuDeploy', () => { let err; try { await rokuDeploy.zip({ - stagingDir: s`${tempDir}/path/to/nowhere`, + dir: s`${tempDir}/path/to/nowhere`, outDir: outDir }); } catch (e) { @@ -816,7 +816,7 @@ describe('RokuDeploy', () => { resolve(); }); }); - await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', remotePort: 987, key: 'home' }); + await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', ecpPort: 987, key: 'home' }); await promise; }); @@ -841,7 +841,7 @@ describe('RokuDeploy', () => { resolve(); }); }); - await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', remotePort: 987, key: 'home', timeout: 1000 }); + await rokuDeploy.keyPress({ ...options, host: '1.2.3.4', ecpPort: 987, key: 'home', timeout: 1000 }); await promise; }); }); @@ -1646,7 +1646,7 @@ describe('RokuDeploy', () => { ${options.devId} `; mockDoGetRequest(body); - fsExtra.outputFileSync(path.resolve(rootDir, options.rekeySignedPackage), ''); + fsExtra.outputFileSync(path.resolve(rootDir, options.pkg), ''); }); it('does not crash when archive is undefined', async () => { @@ -1657,7 +1657,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: options.rekeySignedPackage, + pkg: options.pkg, signingPassword: options.signingPassword, devId: options.devId }); @@ -1678,7 +1678,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: s`notReal.pkg`, + pkg: s`notReal.pkg`, signingPassword: options.signingPassword, devId: options.devId }); @@ -1695,7 +1695,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: s`${tempDir}/testSignedPackage.pkg`, + pkg: s`${tempDir}/testSignedPackage.pkg`, signingPassword: options.signingPassword, devId: options.devId }); @@ -1709,7 +1709,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: options.rekeySignedPackage, + pkg: options.pkg, signingPassword: options.signingPassword, devId: options.devId }); @@ -1723,7 +1723,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: options.rekeySignedPackage, + pkg: options.pkg, signingPassword: options.signingPassword, devId: undefined }); @@ -1735,7 +1735,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: options.rekeySignedPackage, + pkg: options.pkg, signingPassword: options.signingPassword, devId: options.devId }); @@ -1755,7 +1755,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: options.rekeySignedPackage, + pkg: options.pkg, signingPassword: options.signingPassword, devId: options.devId }); @@ -1775,7 +1775,7 @@ describe('RokuDeploy', () => { await rokuDeploy.rekeyDevice({ host: '1.2.3.4', password: 'password', - rekeySignedPackage: options.rekeySignedPackage, + pkg: options.pkg, signingPassword: options.signingPassword, devId: '45fdc2019903ac333ff624b0b2cddd2c733c3e74' }); @@ -2969,7 +2969,7 @@ describe('RokuDeploy', () => { }); await rokuDeploy.zip({ - stagingDir: stagingDir, + dir: stagingDir, outDir: outDir }); const data = fsExtra.readFileSync(rokuDeploy['getOutputZipFilePath']({ outDir: outDir })); @@ -3762,13 +3762,13 @@ describe('RokuDeploy', () => { }); - describe('remotePort', () => { + describe('ecpPort', () => { it('defaults to 8060', () => { - expect(rokuDeploy.getOptions({}).remotePort).to.equal(8060); + expect(rokuDeploy.getOptions({}).ecpPort).to.equal(8060); }); it('can be overridden', () => { - expect(rokuDeploy.getOptions({ remotePort: 1234 }).remotePort).to.equal(1234); + expect(rokuDeploy.getOptions({ ecpPort: 1234 }).ecpPort).to.equal(1234); }); }); @@ -3857,10 +3857,10 @@ describe('RokuDeploy', () => { }); it('throws error when rekeyDevice is missing required options', async () => { - const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd', rekeySignedPackage: 'abcd', signingPassword: 'abcd' }; + const requiredOptions: Partial = { host: '1.2.3.4', password: 'abcd', pkg: 'abcd', signingPassword: 'abcd' }; await testRequiredOptions('rekeyDevice', requiredOptions, 'host'); await testRequiredOptions('rekeyDevice', requiredOptions, 'password'); - await testRequiredOptions('rekeyDevice', requiredOptions, 'rekeySignedPackage'); + await testRequiredOptions('rekeyDevice', requiredOptions, 'pkg'); await testRequiredOptions('rekeyDevice', requiredOptions, 'signingPassword'); }); diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index e97f369..6bfc1e0 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -62,18 +62,24 @@ export class RokuDeploy { * @param options */ public async zip(options: ZipOptions) { - logger.info('Beginning to zip staging folder'); + logger.info('Beginning to zip folder'); options = this.getOptions(options) as any; + if (options.dir) { + options.dir = path.resolve((options as any).cwd, options.dir); + } else { + options.dir = (options as any).stagingDir; + } + let zipFilePath = this.getOutputZipFilePath(options as any); - //ensure the manifest file exists in the staging folder - if (!await util.fileExistsCaseInsensitive(`${options.stagingDir}/manifest`)) { - throw new Error(`Cannot zip package: missing manifest file in "${options.stagingDir}"`); + //ensure the manifest file exists in the folder to be zipped + if (!await util.fileExistsCaseInsensitive(`${options.dir}/manifest`)) { + throw new Error(`Cannot zip package: missing manifest file in "${options.dir}"`); } - //create a zip of the staging folder - await this.makeZip(options.stagingDir, zipFilePath); + //create a zip of the folder + await this.makeZip(options.dir, zipFilePath); logger.info('Zip created at:', zipFilePath); } @@ -217,7 +223,7 @@ export class RokuDeploy { let filledOptions = this.getOptions(options); // press the home button to return to the main screen return this.doPostRequest({ - url: `http://${filledOptions.host}:${filledOptions.remotePort}/${filledOptions.action}/${filledOptions.key}`, + url: `http://${filledOptions.host}:${filledOptions.ecpPort}/${filledOptions.action}/${filledOptions.key}`, timeout: filledOptions.timeout }, false); } @@ -236,9 +242,32 @@ export class RokuDeploy { * @param options */ public async sideload(options: SideloadOptions): Promise<{ message: string; results: any }> { - logger.info('Beggining to sideload package'); + logger.info('Beginning to sideload package'); this.checkRequiredOptions(options, ['host', 'password']); + + // Resolve zip/rootDir before getOptions so outDir/outFile are set correctly + if (options.zip) { + options.zip = path.resolve(options.cwd ?? process.cwd(), options.zip); + options.outDir = path.dirname(options.zip); + options.outFile = path.basename(options.zip); + options.retainDeploymentArchive = true; + } else if (options.rootDir) { + options.rootDir = path.resolve(options.cwd ?? process.cwd(), options.rootDir); + } + options = this.getOptions(options) as any; + + // Close the channel before sideloading unless explicitly disabled + if (options.close !== false) { + await this.closeChannel(options as CloseChannelOptions); + } + + // If rootDir was provided (and no zip), zip it first then sideload + if (!options.zip && options.rootDir) { + await this.zip({ dir: options.rootDir, outDir: options.outDir, outFile: options.outFile, cwd: options.cwd }); + options.retainDeploymentArchive = false; + } + //make sure the outDir exists await fsExtra.ensureDir(options.outDir); @@ -411,16 +440,16 @@ export class RokuDeploy { } /** - * resign Roku Device with supplied pkg and + * resign Roku Device with a supplied signed pkg and * @param options */ public async rekeyDevice(options: RekeyDeviceOptions) { - this.checkRequiredOptions(options, ['host', 'password', 'rekeySignedPackage', 'signingPassword']); + this.checkRequiredOptions(options, ['host', 'password', 'pkg', 'signingPassword']); options = this.getOptions(options) as any; - let rekeySignedPackagePath = options.rekeySignedPackage; - if (!path.isAbsolute(options.rekeySignedPackage)) { - rekeySignedPackagePath = path.join(options.rootDir, options.rekeySignedPackage); + let pkgPath = options.pkg; + if (!path.isAbsolute(options.pkg)) { + pkgPath = path.join(options.rootDir, options.pkg); } let requestOptions = this.generateBaseRequestOptions('plugin_inspect', options as any, { mysubmit: 'Rekey', @@ -430,7 +459,7 @@ export class RokuDeploy { let results: HttpResponse; try { - requestOptions.formData.archive = fsExtra.createReadStream(rekeySignedPackagePath); + requestOptions.formData.archive = fsExtra.createReadStream(pkgPath); results = await this.doPostRequest(requestOptions); } finally { //ensure the stream is closed @@ -885,7 +914,7 @@ export class RokuDeploy { failOnCompileError: true, deleteDevChannel: true, packagePort: 80, - remotePort: 8060, + ecpPort: 8060, timeout: 150000, rootDir: './', files: [...DefaultFiles], @@ -978,7 +1007,7 @@ export class RokuDeploy { //try using the host as-is (it'll probably fail...) } - const url = `http://${options.host}:${options.remotePort}/query/device-info`; + const url = `http://${options.host}:${options.ecpPort}/query/device-info`; let response; try { @@ -1021,7 +1050,7 @@ export class RokuDeploy { * Get the External Control Protocol (ECP) setting mode of the device. This determines whether * the device accepts remote control commands via the ECP API. * - * @param options - Configuration options including host, remotePort, timeout, etc. + * @param options - Configuration options including host, ecpPort, timeout, etc. * @returns The ECP setting mode: * - 'enabled': fully enabled and accepting commands * - 'disabled': ECP is disabled (device may still be reachable but ECP commands won't work) @@ -1235,7 +1264,7 @@ export interface GetDeviceInfoOptions { /** * The port to use to send the device-info request (defaults to the standard 8060 ECP port) */ - remotePort?: number; + ecpPort?: number; /** * The number of milliseconds at which point this request should timeout and return a rejected promise */ @@ -1254,7 +1283,7 @@ export interface SendKeyEventOptions { host: string; // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents key: RokuKey | string; - remotePort?: number; + ecpPort?: number; timeout?: number; } @@ -1280,7 +1309,7 @@ export interface SendTextOptions extends SendKeyEventOptions { export interface CloseChannelOptions { host: string; - remotePort?: number; + ecpPort?: number; timeout?: number; } @@ -1292,7 +1321,7 @@ export interface StageOptions { } export interface ZipOptions { - stagingDir?: string; + dir?: string; outDir?: string; outFile?: string; cwd?: string; @@ -1302,6 +1331,19 @@ export interface SideloadOptions { appType?: 'channel' | 'dcl'; host: string; password: string; + /** + * The path to an existing zip file to sideload. Takes precedence over `rootDir`. + */ + zip?: string; + /** + * The root folder to zip and then sideload. Used when `zip` is not provided. + */ + rootDir?: string; + /** + * Close the channel before sideloading. Defaults to true. + * Set to false to skip closing the channel. + */ + close?: boolean; remoteDebug?: boolean; remoteDebugConnectEarly?: boolean; failOnCompileError?: boolean; @@ -1309,6 +1351,8 @@ export interface SideloadOptions { outDir?: string; outFile?: string; deleteDevChannel?: boolean; + ecpPort?: number; + timeout?: number; cwd?: string; packageUploadOverrides?: PackageUploadOverridesOptions; } @@ -1334,7 +1378,7 @@ export interface ConvertToSquashfsOptions { export interface RekeyDeviceOptions { host: string; password: string; - rekeySignedPackage: string; + pkg: string; signingPassword: string; rootDir?: string; devId: string; @@ -1390,7 +1434,7 @@ export interface GetDevIdOptions { /** * The port to use to send the device-info request (defaults to the standard 8060 ECP port) */ - remotePort?: number; + ecpPort?: number; /** * The number of milliseconds at which point this request should timeout and return a rejected promise */ diff --git a/src/RokuDeployOptions.ts b/src/RokuDeployOptions.ts index 8879ad3..99e87cb 100644 --- a/src/RokuDeployOptions.ts +++ b/src/RokuDeployOptions.ts @@ -84,7 +84,7 @@ export interface RokuDeployOptions { * This is mainly useful for things like emulators that use alternate ports, * or when sending commands through some type of port forwarding. */ - remotePort?: number; + ecpPort?: number; /** * The directory where screenshots should be saved. Will use the OS temp directory by default @@ -118,7 +118,7 @@ export interface RokuDeployOptions { /** * Path to a copy of the signed package you want to use for rekeying */ - rekeySignedPackage?: string; + pkg?: string; /** * Dev ID we are expecting the device to have. If supplied we check that the dev ID returned after keying matches what we expected diff --git a/src/cli.ts b/src/cli.ts index e3975ba..d31010c 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,5 @@ #!/usr/bin/env node import * as yargs from 'yargs'; -import * as path from 'path'; import { SendTextCommand } from './commands/SendTextCommand'; import { StageCommand } from './commands/StageCommand'; import { SideloadCommand } from './commands/SideloadCommand'; @@ -28,7 +27,7 @@ void yargs .option('password', { type: 'string', description: 'The password of the target Roku', demandOption: false }) .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands (like pressing the home button)', demandOption: false }) .option('packagePort', { type: 'number', description: 'The port to use for sending a packaging to the device', demandOption: false }) - .option('noclose', { type: 'boolean', description: 'Should the command not close the channel before sideloading', demandOption: false }) + .option('close', { type: 'boolean', description: 'Close the channel before sideloading. Use --no-close to skip.', demandOption: false }) .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }) .option('remoteDebug', { type: 'boolean', description: 'Should the command be run in remote debug mode', demandOption: false }) .option('remoteDebugConnectEarly', { type: 'boolean', description: 'Should the command connect to the debugger early', demandOption: false }) @@ -51,14 +50,6 @@ void yargs .option('devId', { type: 'string', description: 'The dev ID', demandOption: false }) .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); }, (args: any) => { - if (args.out) { - if (!args.out.endsWith('.pkg')) { - throw new Error('Out must end with a .pkg'); - } - args.out = path.resolve(args.cwd, args.out); - args.outDir = path.dirname(args.out); - args.outFile = path.basename(args.out); - } return new CreateSignedPackageCommand().run(args); }) @@ -69,9 +60,6 @@ void yargs .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); }, (args: any) => { - if (args.ecpPort) { - args.remotePort = args.ecpPort; - } return new KeyPressCommand().run(args); }) @@ -82,9 +70,6 @@ void yargs .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); }, (args: any) => { - if (args.ecpPort) { - args.remotePort = args.ecpPort; - } return new KeyUpCommand().run(args); }) @@ -95,9 +80,6 @@ void yargs .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); }, (args: any) => { - if (args.ecpPort) { - args.remotePort = args.ecpPort; - } return new KeyDownCommand().run(args); }) @@ -108,9 +90,6 @@ void yargs .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }) .option('timeout', { type: 'number', description: 'The timeout for this command', demandOption: false }); }, (args: any) => { - if (args.ecpPort) { - args.remotePort = args.ecpPort; - } return new SendTextCommand().run(args); }) @@ -119,9 +98,6 @@ void yargs .option('host', { type: 'string', description: 'The IP Address of the target Roku', demandOption: false }) .option('ecpPort', { type: 'number', description: 'The port to use for ECP commands like remote key presses', demandOption: false }); }, (args: any) => { - if (args.ecpPort) { - args.remotePort = args.ecpPort; - } return new RemoteControlCommand().run(args); }) @@ -152,7 +128,6 @@ void yargs .option('devId', { type: 'string', description: 'The dev ID', demandOption: false }) .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); }, (args: any) => { - args.rekeySignedPackage = path.resolve(args.cwd, args.pkg); return new RekeyDeviceCommand().run(args); }) @@ -171,11 +146,6 @@ void yargs .option('out', { type: 'string', description: 'The location where the screenshot will be saved relative to cwd', demandOption: false, defaultDescription: './out/roku-deploy.jpg' }) .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); }, (args: any) => { - if (args.out) { - args.out = path.resolve(args.cwd, args.out); - args.screenshotDir = path.dirname(args.out); - args.screenshotFile = path.basename(args.out); - } return new CaptureScreenshotCommand().run(args); }) @@ -199,14 +169,6 @@ void yargs .option('out', { type: 'string', description: 'the path to the zip file that will be created, relative to cwd', demandOption: false, alias: 'outZip' }) .option('cwd', { type: 'string', description: 'The current working directory to use for relative paths', demandOption: false }); }, (args: any) => { - if (args.out) { - args.out = path.resolve(args.cwd, args.out); - args.outDir = path.dirname(args.out); - args.outFile = path.basename(args.out); - } - if (args.dir) { - args.stagingDir = path.resolve(args.cwd, args.dir); - } return new ZipCommand().run(args); }) diff --git a/src/commands/CaptureScreenshotCommand.ts b/src/commands/CaptureScreenshotCommand.ts index 6a75ed5..bedf22a 100644 --- a/src/commands/CaptureScreenshotCommand.ts +++ b/src/commands/CaptureScreenshotCommand.ts @@ -1,11 +1,19 @@ import { rokuDeploy, util } from '../index'; +import * as path from 'path'; export class CaptureScreenshotCommand { async run(args) { + args.cwd ??= process.cwd(); + let options = { ...util.getOptionsFromJson(args), ...args }; + if (args.out) { + args.out = path.resolve(args.cwd, args.out); + options.screenshotDir = path.dirname(args.out); + options.screenshotFile = path.basename(args.out); + } await rokuDeploy.captureScreenshot(options); } } diff --git a/src/commands/CreateSignedPackageCommand.ts b/src/commands/CreateSignedPackageCommand.ts index 904a9fe..4571975 100644 --- a/src/commands/CreateSignedPackageCommand.ts +++ b/src/commands/CreateSignedPackageCommand.ts @@ -1,11 +1,22 @@ import { rokuDeploy, util } from '../index'; +import * as path from 'path'; export class CreateSignedPackageCommand { async run(args) { + args.cwd ??= process.cwd(); + let options = { ...util.getOptionsFromJson(args), ...args }; + if (args.out) { + if (!args.out.endsWith('.pkg')) { + throw new Error('Out must end with a .pkg'); + } + args.out = path.resolve(args.cwd, args.out); + options.outDir = path.dirname(args.out); + options.outFile = path.basename(args.out); + } await rokuDeploy.createSignedPackage(options); } } diff --git a/src/commands/RekeyDeviceCommand.ts b/src/commands/RekeyDeviceCommand.ts index ca00fd8..b07ae56 100644 --- a/src/commands/RekeyDeviceCommand.ts +++ b/src/commands/RekeyDeviceCommand.ts @@ -1,11 +1,17 @@ import { rokuDeploy, util } from '../index'; +import * as path from 'path'; export class RekeyDeviceCommand { async run(args) { + args.cwd ??= process.cwd(); + let options = { ...util.getOptionsFromJson(args), ...args }; + if (args.pkg) { + options.pkg = path.resolve(args.cwd, args.pkg); + } await rokuDeploy.rekeyDevice(options); } } diff --git a/src/commands/SideloadCommand.ts b/src/commands/SideloadCommand.ts index c488b32..f35775d 100644 --- a/src/commands/SideloadCommand.ts +++ b/src/commands/SideloadCommand.ts @@ -1,47 +1,24 @@ import { rokuDeploy, util } from '../index'; -import type { CloseChannelOptions } from '../RokuDeploy'; import * as path from 'path'; export class SideloadCommand { async run(args) { + args.cwd ??= process.cwd(); + let options = { ...util.getOptionsFromJson(args), ...args }; - // Process args so that they can be compatible with the RokuDeploy - args.cwd ??= process.cwd(); if (args.zip) { - args.zip = path.resolve(args.cwd, args.zip); - options.outDir = path.dirname(args.zip); - options.outFile = path.basename(args.zip); + options.zip = path.resolve(args.cwd, args.zip); } if (args.rootDir) { options.rootDir = path.resolve(args.cwd, args.rootDir); } - if (args.outZip) { options.outZip = path.resolve(args.cwd, args.outZip); } - - if (args.ecpPort) { - options.remotePort = args.ecpPort; - } - - if (args.noclose !== true) { - await rokuDeploy.closeChannel(options as CloseChannelOptions); - } - - - if (args.zip) { - options.retainDeploymentArchive = true; - await rokuDeploy.sideload(options); - } else if (args.rootDir) { - await rokuDeploy.zip(options); - options.retainDeploymentArchive = false; - await rokuDeploy.sideload(options); - } else { - throw new Error('Either zip or rootDir must be provided for sideload command'); - } + await rokuDeploy.sideload(options); } } diff --git a/src/commands/StageCommand.ts b/src/commands/StageCommand.ts index 8cdf460..5f45796 100644 --- a/src/commands/StageCommand.ts +++ b/src/commands/StageCommand.ts @@ -6,6 +6,9 @@ export class StageCommand { ...util.getOptionsFromJson(args), ...args }; + if (options.out) { + options.stagingDir = options.out; + } await rokuDeploy.stage(options); } } diff --git a/src/commands/ZipCommand.ts b/src/commands/ZipCommand.ts index b03e80d..183c7aa 100644 --- a/src/commands/ZipCommand.ts +++ b/src/commands/ZipCommand.ts @@ -1,11 +1,22 @@ import { rokuDeploy, util } from '../index'; +import * as path from 'path'; export class ZipCommand { async run(args) { + args.cwd ??= process.cwd(); + let options = { ...util.getOptionsFromJson(args), ...args }; + if (args.out) { + args.out = path.resolve(args.cwd, args.out); + options.outDir = path.dirname(args.out); + options.outFile = path.basename(args.out); + } + if (args.dir) { + options.dir = path.resolve(args.cwd, args.dir); + } await rokuDeploy.zip(options); } }