From c6d25368f075cc29eea37d7282040fea272a4605 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 2 Jan 2024 13:39:51 -0500 Subject: [PATCH 01/20] Make change to assume dest folder. Delete 2 old tests that looked for old behavior, add 2 more tests that look for new behavior --- src/RokuDeploy.spec.ts | 52 ++++++++++++++++++++++++------------------ src/RokuDeploy.ts | 4 +++- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index c090fd4..9aaff30 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -3062,28 +3062,6 @@ describe('index', () => { }]); }); - it('works for other globs without dest', async () => { - expect(await getFilePaths([{ - src: `components/screen1/*creen1.brs` - }])).to.eql([{ - src: s`${rootDir}/components/screen1/screen1.brs`, - dest: s`screen1.brs` - }]); - }); - - it('skips directory folder names for other globs without dest', async () => { - expect(await getFilePaths([{ - //straight wildcard matches folder names too - src: `components/*` - }])).to.eql([{ - src: s`${rootDir}/components/component1.brs`, - dest: s`component1.brs` - }, { - src: s`${rootDir}/components/component1.xml`, - dest: s`component1.xml` - }]); - }); - it('applies negated patterns', async () => { writeFiles(rootDir, [ 'components/component1.brs', @@ -3281,6 +3259,36 @@ describe('index', () => { await fsExtra.remove(s`${thisRootDir}/../`); } }); + + it('maintains original file path', async () => { + fsExtra.outputFileSync(`${rootDir}/components/CustomButton.brs`, ''); + expect( + await rokuDeploy.getFilePaths([ + 'components/CustomButton.brs' + ], rootDir) + ).to.eql([{ + src: s`${rootDir}/components/CustomButton.brs`, + dest: s`components/CustomButton.brs` + }]); + }); + + it('correctly assumes file path if not given', async () => { + fsExtra.outputFileSync(`${rootDir}/components/CustomButton.brs`, ''); + expect( + await rokuDeploy.getFilePaths([ + { src: 'components/*' } + ], rootDir) + ).to.eql([{ + src: s`${rootDir}/components/CustomButton.brs`, + dest: s`components/CustomButton.brs` + }, { + src: s`${rootDir}/components/component1.brs`, + dest: s`components/component1.brs` + }, { + src: s`${rootDir}/components/component1.xml`, + dest: s`components/component1.xml` + }]); + }); }); describe('computeFileDestPath', () => { diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index 37762ce..f3cd8d3 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -329,7 +329,9 @@ export class RokuDeploy { //`pattern` is some other glob magic } else { const fileNameAndExtension = path.basename(srcPath); - result = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); + const foundFilePath = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); + const assumedFilePath = util.stringReplaceInsensitive(srcPath, rootDir, ''); + result = entry.dest ? foundFilePath : assumedFilePath; } result = util.standardizePath( From 2ba8ecae845401c533bb79006373dc7dc32cf8f0 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 2 Jan 2024 14:30:01 -0500 Subject: [PATCH 02/20] Sort files --- src/RokuDeploy.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/RokuDeploy.spec.ts b/src/RokuDeploy.spec.ts index 9aaff30..76d751a 100644 --- a/src/RokuDeploy.spec.ts +++ b/src/RokuDeploy.spec.ts @@ -3275,18 +3275,18 @@ describe('index', () => { it('correctly assumes file path if not given', async () => { fsExtra.outputFileSync(`${rootDir}/components/CustomButton.brs`, ''); expect( - await rokuDeploy.getFilePaths([ + (await rokuDeploy.getFilePaths([ { src: 'components/*' } - ], rootDir) + ], rootDir)).sort((a, b) => a.src.localeCompare(b.src)) ).to.eql([{ - src: s`${rootDir}/components/CustomButton.brs`, - dest: s`components/CustomButton.brs` - }, { src: s`${rootDir}/components/component1.brs`, dest: s`components/component1.brs` }, { src: s`${rootDir}/components/component1.xml`, dest: s`components/component1.xml` + }, { + src: s`${rootDir}/components/CustomButton.brs`, + dest: s`components/CustomButton.brs` }]); }); }); From cc92e04ca11b2815b4d8b0cb5be8c273b3734e5b Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 16 Jan 2024 10:48:26 -0500 Subject: [PATCH 03/20] Update src/RokuDeploy.ts Co-authored-by: Bronley Plumb --- src/RokuDeploy.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index f3cd8d3..d6d0fc8 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -329,9 +329,11 @@ export class RokuDeploy { //`pattern` is some other glob magic } else { const fileNameAndExtension = path.basename(srcPath); - const foundFilePath = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); - const assumedFilePath = util.stringReplaceInsensitive(srcPath, rootDir, ''); - result = entry.dest ? foundFilePath : assumedFilePath; + if (entry.dest) { + result = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); + } else { + result = util.stringReplaceInsensitive(srcPath, rootDir, ''); + } } result = util.standardizePath( From 9bbd579ffa13777f8f36ecba0d80b7c040a2049f Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Wed, 17 Jan 2024 13:30:18 -0500 Subject: [PATCH 04/20] Removed redundant entry.dest is null situation --- src/RokuDeploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index d6d0fc8..e7a0462 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -330,7 +330,7 @@ export class RokuDeploy { } else { const fileNameAndExtension = path.basename(srcPath); if (entry.dest) { - result = util.standardizePath(`${entry.dest ?? ''}/${fileNameAndExtension}`); + result = util.standardizePath(`${entry.dest}/${fileNameAndExtension}`); } else { result = util.stringReplaceInsensitive(srcPath, rootDir, ''); } From 460cbf673b37f344825a34dc9d70f8f9eae57a9f Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Fri, 19 Jan 2024 15:02:47 -0500 Subject: [PATCH 05/20] Added a few commands, not all are working correctly --- src/RokuDeploy.ts | 2 +- src/cli.ts | 173 ++++++++++++++++++++++++++++++++++++++++++++-- src/index.ts | 2 + tsconfig.json | 2 +- 4 files changed, 172 insertions(+), 7 deletions(-) mode change 100644 => 100755 src/cli.ts diff --git a/src/RokuDeploy.ts b/src/RokuDeploy.ts index f3cd8d3..c7ed29d 100644 --- a/src/RokuDeploy.ts +++ b/src/RokuDeploy.ts @@ -957,7 +957,7 @@ export class RokuDeploy { * Centralizes getting output zip file path based on passed in options * @param options */ - public getOutputZipFilePath(options: GetOutputZipFilePathOptions) { + public getOutputZipFilePath(options?: GetOutputZipFilePathOptions) { options = this.getOptions(options) as any; let zipFileName = options.outFile; diff --git a/src/cli.ts b/src/cli.ts old mode 100644 new mode 100755 index 063f5fd..5675926 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,170 @@ #!/usr/bin/env node -import { deploy } from './index'; -deploy().then((...args) => { - console.log(...args); -}, (...args) => { - console.error(...args); +import * as yargs from 'yargs'; +import { stagingDir } from './testUtils.spec'; +import { prepublishToStaging, zipPackage, createPackage, publish, getOutputZipFilePath, getOutputPkgFilePath, getDeviceInfo, getDevId, zipFolder } from './index'; +const outDir = './out'; + +new Promise((resolve, reject) => { + // TODO: is this necessary?vv + // eslint-disable-next-line + yargs + .command('prepublishToStaging', 'Copies all of the referenced files to the staging folder', (builder) => { + return builder + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: true }) + .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: true }); + }, (args: any) => { + console.log('prepublishToStaging'); + prepublishToStaging({ + files: [ + 'manifest' + ], + stagingDir: args.stagingDir, + rootDir: args.rootDir + }).then(() => { + console.error('SUCCESS'); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + // TODO: Should we have defaults for these^^ + // TODO: This doesn't work + }) + + .command('zipPackage', 'Given an already-populated staging folder, create a zip archive of it and copy it to the output folder', (builder) => { + return builder + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', default: outDir, demandOption: false }); + }, (args: any) => { + console.log('zipPackage'); + zipPackage({ + stagingDir: stagingDir, + outDir: args.outDir + }).then(() => { + console.error('SUCCESS'); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + // TODO: Missing manifest file + }) + + .command('createPackage', 'Create a zip folder containing all of the specified roku project files', (builder) => { + return builder + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', default: outDir, demandOption: false }); + }, (args: any) => { + console.log('createPackage'); + createPackage({ + files: [ + 'manifest' + ], + stagingDir: '.tmp/dist', + outDir: args.outDir, + rootDir: './src' + }).then(() => { + console.error('SUCCESS'); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + // TODO: Missing manifest file + }) + + .command('publish', 'Publish a pre-existing packaged zip file to a remote Roku', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: true }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: true }) + .option('outDir', { type: 'string', description: 'The output directory', default: outDir, demandOption: false }) + .option('outFile', { type: 'string', description: 'The output file', default: 'roku-deploy', demandOption: false }); + }, (args: any) => { + console.log('publish'); + publish({ + host: args.host, + password: args.password, + outDir: args.outDir, + outFile: args.outFile + }).then(() => { + console.error('SUCCESS'); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + // TODO: Times out + }) + + // TODO: + // convertToSquashfs + // rekeyDevice + // signExistingPackage + // retrieveSignedPackage + // deploy + // deleteInstalledChannel + // takeScreenshot + // deployAndSignPackage - TODO: does the same thing as deploy but also signs package...is it necessary? + + .command('getOutputZipFilePath', 'Centralizes getting output zip file path based on passed in options', (builder) => { + // EXAMPLE: npx roku-deploy getOutputZipFilePath + return builder; + }, (args: any) => { + console.log('getOutputZipFilePath'); + console.log(getOutputZipFilePath({})); + }) + + .command('getOutputPkgFilePath', 'Centralizes getting output pkg file path based on passed in options', (builder) => { + // EXAMPLE: npx roku-deploy getOutputPkgFilePath + return builder; + }, (args: any) => { + console.log('getOutputPkgFilePath'); + let result = getOutputPkgFilePath({}); + console.log(result); + }) + + .command('getDeviceInfo', 'Get the `device-info` response from a Roku device', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: true }); + }, (args: any) => { + console.log('getDeviceInfo'); + let result = getDeviceInfo({ + host: args.host + }).then(() => { + console.error('SUCCESS', result); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + // TODO: returns pending promise? + }) + + .command('getDevId', 'Get Dev ID', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: true }); + }, (args: any) => { + console.log('getDevId'); + let result = getDevId({ + host: args.host + }).then(() => { + console.error('SUCCESS', result); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + // TODO: returns pending promise? + }) + + .command('zipFolder', 'Given a path to a folder, zip up that folder and all of its contents', (builder) => { + // EXAMPLE: npx roku-deploy zipFolder --srcFolder ./src --zipFilePath ./output.zip + return builder + .option('srcFolder', { type: 'string', description: 'The folder that should be zipped', demandOption: true }) + .option('zipFilePath', { type: 'string', description: 'The path to the zip that will be created. Must be .zip file name', demandOption: true }); + }, (args: any) => { + console.log('zipFolder'); + zipFolder( + args.srcFolder, + args.zipFilePath + ).then(() => { + console.error('SUCCESS'); + }, (error) => { + console.error('ERROR', error, '\n', args); + }); + }) + + .argv; +}).catch((e) => { + console.error(e); + process.exit(1); }); diff --git a/src/index.ts b/src/index.ts index dff33ac..d68b4d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ let retrieveSignedPackage = RokuDeploy.prototype.retrieveSignedPackage.bind(roku let signExistingPackage = RokuDeploy.prototype.signExistingPackage.bind(rokuDeploy); let stringifyManifest = RokuDeploy.prototype.stringifyManifest.bind(rokuDeploy); let takeScreenshot = RokuDeploy.prototype.takeScreenshot.bind(rokuDeploy); +let getDevId = RokuDeploy.prototype.getDevId.bind(rokuDeploy); let zipFolder = RokuDeploy.prototype.zipFolder.bind(rokuDeploy); let zipPackage = RokuDeploy.prototype.zipPackage.bind(rokuDeploy); @@ -56,6 +57,7 @@ export { signExistingPackage, stringifyManifest, takeScreenshot, + getDevId, zipFolder, zipPackage }; diff --git a/tsconfig.json b/tsconfig.json index 381ba26..5d1cd50 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,7 @@ "include": [ "src/**/*.ts", "device.spec.ts" - ], +, "src/cli.ts" ], "ts-node": { "transpileOnly": true } From 6db962aceaf19ea48d46a7f12d4478bdb170c48a Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Wed, 31 Jan 2024 13:37:39 -0500 Subject: [PATCH 06/20] Move commands to their own file --- src/commands/ConvertToSquashfsCommand.ts | 10 ++++++++++ src/commands/CreatePackageCommand.ts | 11 +++++++++++ src/commands/DeleteInstalledChannelCommand.ts | 10 ++++++++++ src/commands/DeployCommand.ts | 11 +++++++++++ src/commands/PrepublishCommand.ts | 10 ++++++++++ src/commands/PublishCommand.ts | 12 ++++++++++++ src/commands/RekeyDeviceCommand.ts | 14 ++++++++++++++ src/commands/RetrieveSignedPackageCommand.ts | 11 +++++++++++ src/commands/SignExistingPackageCommand.ts | 12 ++++++++++++ src/commands/TakeScreenshotCommand.ts | 10 ++++++++++ src/commands/ZipPackageCommand.ts | 10 ++++++++++ 11 files changed, 121 insertions(+) create mode 100644 src/commands/ConvertToSquashfsCommand.ts create mode 100644 src/commands/CreatePackageCommand.ts create mode 100644 src/commands/DeleteInstalledChannelCommand.ts create mode 100644 src/commands/DeployCommand.ts create mode 100644 src/commands/PrepublishCommand.ts create mode 100644 src/commands/PublishCommand.ts create mode 100644 src/commands/RekeyDeviceCommand.ts create mode 100644 src/commands/RetrieveSignedPackageCommand.ts create mode 100644 src/commands/SignExistingPackageCommand.ts create mode 100644 src/commands/TakeScreenshotCommand.ts create mode 100644 src/commands/ZipPackageCommand.ts diff --git a/src/commands/ConvertToSquashfsCommand.ts b/src/commands/ConvertToSquashfsCommand.ts new file mode 100644 index 0000000..b1e1e7b --- /dev/null +++ b/src/commands/ConvertToSquashfsCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class ConvertToSquashfsCommand { + async run(args) { + await rokuDeploy.convertToSquashfs({ + host: args.host, + password: args.password + }); + } +} diff --git a/src/commands/CreatePackageCommand.ts b/src/commands/CreatePackageCommand.ts new file mode 100644 index 0000000..f4b4718 --- /dev/null +++ b/src/commands/CreatePackageCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy } from '../index'; + +export class CreatePackageCommand { + async run(args) { + await rokuDeploy.createPackage({ + stagingDir: args.stagingDir, + outDir: args.outDir, + rootDir: args.rootDir + }); + } +} diff --git a/src/commands/DeleteInstalledChannelCommand.ts b/src/commands/DeleteInstalledChannelCommand.ts new file mode 100644 index 0000000..1ec9c29 --- /dev/null +++ b/src/commands/DeleteInstalledChannelCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class DeleteInstalledChannelCommand { + async run(args) { + await rokuDeploy.deleteInstalledChannel({ + host: args.host, + password: args.password + }); + } +} diff --git a/src/commands/DeployCommand.ts b/src/commands/DeployCommand.ts new file mode 100644 index 0000000..5947deb --- /dev/null +++ b/src/commands/DeployCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy } from '../index'; + +export class DeployCommand { + async run(args) { + await rokuDeploy.deploy({ + host: args.host, + password: args.password, + rootDir: args.rootDir + }); + } +} diff --git a/src/commands/PrepublishCommand.ts b/src/commands/PrepublishCommand.ts new file mode 100644 index 0000000..2ca65cc --- /dev/null +++ b/src/commands/PrepublishCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class PrepublishCommand { + async run(args) { + await rokuDeploy.prepublishToStaging({ + stagingDir: args.stagingDir, + rootDir: args.rootDir + }); + } +} diff --git a/src/commands/PublishCommand.ts b/src/commands/PublishCommand.ts new file mode 100644 index 0000000..91554fa --- /dev/null +++ b/src/commands/PublishCommand.ts @@ -0,0 +1,12 @@ +import { rokuDeploy } from '../index'; + +export class PublishCommand { + async run(args) { + await rokuDeploy.publish({ + host: args.host, + password: args.password, + outDir: args.outDir, + outFile: args.outFile + }); + } +} \ No newline at end of file diff --git a/src/commands/RekeyDeviceCommand.ts b/src/commands/RekeyDeviceCommand.ts new file mode 100644 index 0000000..7098772 --- /dev/null +++ b/src/commands/RekeyDeviceCommand.ts @@ -0,0 +1,14 @@ +import { rokuDeploy } from '../index'; + +export class RekeyDeviceCommand { + async run(args) { + await rokuDeploy.rekeyDevice({ + host: args.host, + password: args.password, + rekeySignedPackage: args.rekeySignedPackage, + signingPassword: args.signingPassword, + rootDir: args.rootDir, + devId: args.devId + }); + } +} diff --git a/src/commands/RetrieveSignedPackageCommand.ts b/src/commands/RetrieveSignedPackageCommand.ts new file mode 100644 index 0000000..cfecba6 --- /dev/null +++ b/src/commands/RetrieveSignedPackageCommand.ts @@ -0,0 +1,11 @@ +import { rokuDeploy } from '../index'; + +export class RetrieveSignedPackageCommand { + async run(args) { + await rokuDeploy.retrieveSignedPackage('path_to_pkg', { + host: args.host, + password: args.password, + outFile: args.outFile + }); + } +} diff --git a/src/commands/SignExistingPackageCommand.ts b/src/commands/SignExistingPackageCommand.ts new file mode 100644 index 0000000..2d5733f --- /dev/null +++ b/src/commands/SignExistingPackageCommand.ts @@ -0,0 +1,12 @@ +import { rokuDeploy } from '../index'; + +export class SignExistingPackageCommand { + async run(args) { + await rokuDeploy.signExistingPackage({ + host: args.host, + password: args.password, + signingPassword: args.signingPassword, + stagingDir: args.stagingDir + }); + } +} diff --git a/src/commands/TakeScreenshotCommand.ts b/src/commands/TakeScreenshotCommand.ts new file mode 100644 index 0000000..ac5b7bc --- /dev/null +++ b/src/commands/TakeScreenshotCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class TakeScreenshotCommand { + async run(args) { + await rokuDeploy.takeScreenshot({ + host: args.host, + password: args.password + }); + } +} diff --git a/src/commands/ZipPackageCommand.ts b/src/commands/ZipPackageCommand.ts new file mode 100644 index 0000000..7ef1f09 --- /dev/null +++ b/src/commands/ZipPackageCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class ZipPackageCommand { + async run(args) { + await rokuDeploy.zipPackage({ + stagingDir: args.stagingDir, + outDir: args.outDir + }); + } +} From 20eb21f133ea9f5f307a08efa089340f6c93b14b Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Thu, 1 Feb 2024 00:30:59 -0500 Subject: [PATCH 07/20] More command files --- src/commands/GetDevIdCommand.ts | 9 +++++++++ src/commands/GetDeviceInfoCommand.ts | 9 +++++++++ src/commands/GetOutputPkgFilePathCommand.ts | 10 ++++++++++ src/commands/GetOutputZipFilePathCommand.ts | 10 ++++++++++ src/commands/ZipFolderCommand.ts | 10 ++++++++++ 5 files changed, 48 insertions(+) create mode 100644 src/commands/GetDevIdCommand.ts create mode 100644 src/commands/GetDeviceInfoCommand.ts create mode 100644 src/commands/GetOutputPkgFilePathCommand.ts create mode 100644 src/commands/GetOutputZipFilePathCommand.ts create mode 100644 src/commands/ZipFolderCommand.ts diff --git a/src/commands/GetDevIdCommand.ts b/src/commands/GetDevIdCommand.ts new file mode 100644 index 0000000..72ccde8 --- /dev/null +++ b/src/commands/GetDevIdCommand.ts @@ -0,0 +1,9 @@ +import { rokuDeploy } from '../index'; + +export class GetDevIdCommand { + async run(args) { + await rokuDeploy.getDevId({ + host: args.host + }); + } +} diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts new file mode 100644 index 0000000..42e6580 --- /dev/null +++ b/src/commands/GetDeviceInfoCommand.ts @@ -0,0 +1,9 @@ +import { rokuDeploy } from '../index'; + +export class GetDeviceInfoCommand { + async run(args) { + await rokuDeploy.getDeviceInfo({ + host: args.host + }); + } +} diff --git a/src/commands/GetOutputPkgFilePathCommand.ts b/src/commands/GetOutputPkgFilePathCommand.ts new file mode 100644 index 0000000..cfa9450 --- /dev/null +++ b/src/commands/GetOutputPkgFilePathCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class GetOutputPkgFilePathCommand { + run(args) { + rokuDeploy.getOutputPkgFilePath({ + outFile: args.outFile, + outDir: args.outDir + }); + } +} diff --git a/src/commands/GetOutputZipFilePathCommand.ts b/src/commands/GetOutputZipFilePathCommand.ts new file mode 100644 index 0000000..a8f697f --- /dev/null +++ b/src/commands/GetOutputZipFilePathCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class GetOutputZipFilePathCommand { + run(args) { + rokuDeploy.getOutputZipFilePath({ + outFile: args.outFile, + outDir: args.outDir + }); + } +} diff --git a/src/commands/ZipFolderCommand.ts b/src/commands/ZipFolderCommand.ts new file mode 100644 index 0000000..edf9078 --- /dev/null +++ b/src/commands/ZipFolderCommand.ts @@ -0,0 +1,10 @@ +import { rokuDeploy } from '../index'; + +export class ZipFolderCommand { + async run(args) { + await rokuDeploy.zipFolder( + args.srcFolder, + args.zipFilePath + ); + } +} From 4e4b66a9a5ec1977c06e1fe85df7698650b4d8aa Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Thu, 1 Feb 2024 18:59:39 -0500 Subject: [PATCH 08/20] Change commands to help with tests --- src/commands/GetDeviceInfoCommand.ts | 3 ++- src/commands/GetOutputPkgFilePathCommand.ts | 3 ++- src/commands/GetOutputZipFilePathCommand.ts | 3 ++- src/commands/RetrieveSignedPackageCommand.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts index 42e6580..0670849 100644 --- a/src/commands/GetDeviceInfoCommand.ts +++ b/src/commands/GetDeviceInfoCommand.ts @@ -2,8 +2,9 @@ import { rokuDeploy } from '../index'; export class GetDeviceInfoCommand { async run(args) { - await rokuDeploy.getDeviceInfo({ + const outputPath = await rokuDeploy.getDeviceInfo({ host: args.host }); + console.log(JSON.stringify(outputPath)); } } diff --git a/src/commands/GetOutputPkgFilePathCommand.ts b/src/commands/GetOutputPkgFilePathCommand.ts index cfa9450..78fff13 100644 --- a/src/commands/GetOutputPkgFilePathCommand.ts +++ b/src/commands/GetOutputPkgFilePathCommand.ts @@ -2,9 +2,10 @@ import { rokuDeploy } from '../index'; export class GetOutputPkgFilePathCommand { run(args) { - rokuDeploy.getOutputPkgFilePath({ + const outputPath = rokuDeploy.getOutputPkgFilePath({ outFile: args.outFile, outDir: args.outDir }); + console.log(outputPath); } } diff --git a/src/commands/GetOutputZipFilePathCommand.ts b/src/commands/GetOutputZipFilePathCommand.ts index a8f697f..5525057 100644 --- a/src/commands/GetOutputZipFilePathCommand.ts +++ b/src/commands/GetOutputZipFilePathCommand.ts @@ -2,9 +2,10 @@ import { rokuDeploy } from '../index'; export class GetOutputZipFilePathCommand { run(args) { - rokuDeploy.getOutputZipFilePath({ + const outputPath = rokuDeploy.getOutputZipFilePath({ outFile: args.outFile, outDir: args.outDir }); + console.log(outputPath); } } diff --git a/src/commands/RetrieveSignedPackageCommand.ts b/src/commands/RetrieveSignedPackageCommand.ts index cfecba6..411dfd9 100644 --- a/src/commands/RetrieveSignedPackageCommand.ts +++ b/src/commands/RetrieveSignedPackageCommand.ts @@ -2,7 +2,7 @@ import { rokuDeploy } from '../index'; export class RetrieveSignedPackageCommand { async run(args) { - await rokuDeploy.retrieveSignedPackage('path_to_pkg', { + await rokuDeploy.retrieveSignedPackage(args.pathToPkg, { host: args.host, password: args.password, outFile: args.outFile From 2b809e6886ad0c720eb9a2f81de63a1ff67b87d2 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Thu, 1 Feb 2024 19:00:49 -0500 Subject: [PATCH 09/20] Add testing suite for all cli (2 tests are not working) --- src/cli.spec.ts | 324 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/cli.spec.ts diff --git a/src/cli.spec.ts b/src/cli.spec.ts new file mode 100644 index 0000000..7bddc32 --- /dev/null +++ b/src/cli.spec.ts @@ -0,0 +1,324 @@ +import * as childProcess from 'child_process'; +import { cwd, expectPathExists, rootDir, stagingDir, tempDir, outDir } from './testUtils.spec'; +import * as fsExtra from 'fs-extra'; +import { expect } from 'chai'; +import * as path from 'path'; +import { createSandbox } from 'sinon'; +import { rokuDeploy } from './index'; +import { PublishCommand } from './commands/PublishCommand'; +import { ConvertToSquashfsCommand } from './commands/ConvertToSquashfsCommand'; +import { RekeyDeviceCommand } from './commands/RekeyDeviceCommand'; +import { SignExistingPackageCommand } from './commands/SignExistingPackageCommand'; +import { DeployCommand } from './commands/DeployCommand'; +import { DeleteInstalledChannelCommand } from './commands/DeleteInstalledChannelCommand'; +import { TakeScreenshotCommand } from './commands/TakeScreenshotCommand'; +import { GetDeviceInfoCommand } from './commands/GetDeviceInfoCommand'; +import { GetDevIdCommand } from './commands/GetDevIdCommand'; +import { RetrieveSignedPackageCommand } from './commands/RetrieveSignedPackageCommand'; + +const sinon = createSandbox(); + +function execSync(command: string) { + const output = childProcess.execSync(command, { cwd: tempDir }); + process.stdout.write(output); + return output; + // return childProcess.execSync(command, { stdio: 'inherit', cwd: tempDir }); +} +describe('cli', () => { + before(function build() { + this.timeout(20000); + execSync('npm run build'); + }); + beforeEach(() => { + fsExtra.emptyDirSync(tempDir); + //most tests depend on a manifest file existing, so write an empty one + fsExtra.outputFileSync(`${rootDir}/manifest`, ''); + }); + afterEach(() => { + fsExtra.removeSync(tempDir); + }); + + it('Successfully runs prepublishToStaging', () => { + //make the files + // fsExtra.outputFileSync(`${rootDir}/manifest`, ''); + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); + + expect(() => { + execSync(`node ${cwd}/dist/cli.js prepublishToStaging --stagingDir ${stagingDir} --rootDir ${rootDir}`); + }).to.not.throw(); + }); + + it('Successfully copies rootDir folder to staging folder', () => { + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); + + execSync(`node ${cwd}/dist/cli.js prepublishToStaging --rootDir ${rootDir} --stagingDir ${stagingDir}`); + + expectPathExists(`${stagingDir}/source/main.brs`); + }); + + it('Successfully uses zipPackage to create .zip', () => { + fsExtra.outputFileSync(`${stagingDir}/manifest`, ''); + + execSync(`node ${cwd}/dist/cli.js zipPackage --stagingDir ${stagingDir} --outDir ${outDir}`); + expectPathExists(`${outDir}/roku-deploy.zip`); + }); + + it('Successfully uses createPackage to create .pkg', () => { + execSync(`node ${cwd}/dist/cli.js createPackage --stagingDir ${stagingDir} --rootDir ${rootDir} --outDir ${outDir}`); + expectPathExists(`${outDir}/roku-deploy.zip`); + }); + + it('Publish passes proper options', async () => { + const stub = sinon.stub(rokuDeploy, 'publish').callsFake(async () => { + return Promise.resolve({ + message: 'Publish successful', + results: {} + }); + }); + + const command = new PublishCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536', + outDir: outDir, + outFile: 'rokudeploy-outfile' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536', + outDir: outDir, + outFile: 'rokudeploy-outfile' + }); + }); + + it('Converts to squashfs', async () => { + const stub = sinon.stub(rokuDeploy, 'convertToSquashfs').callsFake(async () => { + return Promise.resolve(); + }); + + const command = new ConvertToSquashfsCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Rekeys a device', async () => { + const stub = sinon.stub(rokuDeploy, 'rekeyDevice').callsFake(async () => { + return Promise.resolve(); + }); + + const command = new RekeyDeviceCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536', + rekeySignedPackage: `${tempDir}/testSignedPackage.pkg`, + signingPassword: '12345', + rootDir: rootDir, + devId: 'abcde' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536', + rekeySignedPackage: `${tempDir}/testSignedPackage.pkg`, + signingPassword: '12345', + rootDir: rootDir, + devId: 'abcde' + }); + }); + + it('Signs an existing package', async () => { + const stub = sinon.stub(rokuDeploy, 'signExistingPackage').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new SignExistingPackageCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536', + signingPassword: undefined, + stagingDir: stagingDir + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536', + signingPassword: undefined, + stagingDir: stagingDir + }); + }); + + it('Retrieves a signed package', async () => { + const stub = sinon.stub(rokuDeploy, 'retrieveSignedPackage').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new RetrieveSignedPackageCommand(); + await command.run({ + pathToPkg: 'path_to_pkg', + host: '1.2.3.4', + password: '5536', + outFile: 'roku-deploy-test' + }); + console.log(stub.getCall(0).args[0]); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + pathToPkg: 'path_to_pkg', + host: '1.2.3.4', + password: '5536', + outFile: 'roku-deploy-test' + });//TODO: fix! + }); + + it('Deploys a package', async () => { + const stub = sinon.stub(rokuDeploy, 'deploy').callsFake(async () => { + return Promise.resolve({ + message: 'Convert successful', + results: {} + }); + }); + + const command = new DeployCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536', + rootDir: rootDir + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536', + rootDir: rootDir + }); + }); + + it('Deletes an installed channel', async () => { + const stub = sinon.stub(rokuDeploy, 'deleteInstalledChannel').callsFake(async () => { + return Promise.resolve({ response: {}, body: {} }); + }); + + const command = new DeleteInstalledChannelCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Takes a screenshot', async () => { + const stub = sinon.stub(rokuDeploy, 'takeScreenshot').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new TakeScreenshotCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4', + password: '5536' + }); + }); + + it('Gets output zip file path', () => { + let zipFilePath = execSync(`node ${cwd}/dist/cli.js getOutputZipFilePath --outFile "roku-deploy" --outDir ${outDir}`).toString(); + + expect(zipFilePath.trim()).to.equal(path.join(path.resolve(outDir), 'roku-deploy.zip')); + }); + + it('Gets output pkg file path', () => { + let pkgFilePath = execSync(`node ${cwd}/dist/cli.js getOutputPkgFilePath --outFile "roku-deploy" --outDir ${outDir}`).toString(); + + expect(pkgFilePath.trim()).to.equal(path.join(path.resolve(outDir), 'roku-deploy.pkg')); + }); + + it('Device info arguments are correct', async () => { + const stub = sinon.stub(rokuDeploy, 'getDeviceInfo').callsFake(async () => { + return Promise.resolve({ + response: {}, + body: {} + }); + }); + + const command = new GetDeviceInfoCommand(); + await command.run({ + host: '1.2.3.4' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4' + }); + }); + + it('Prints device info to console', async () => { + let consoleOutput = ''; + sinon.stub(console, 'log').callsFake((...args) => { + consoleOutput += args.join(' ') + '\n'; //TODO: I don't think this is accurately adding a new line + }); + sinon.stub(rokuDeploy, 'getDeviceInfo').returns(Promise.resolve({ + 'device-id': '1234', + 'serial-number': 'abcd' + })); + await new GetDeviceInfoCommand().run({ + host: '1.2.3.4' + }); + expect(consoleOutput.trim()).to.eql( + '{"device-id":"1234","serial-number":"abcd"}' + ); + }); //TODO: This passes when it is it.only, but fails in the larger test suite? + + it('Gets dev id', async () => { + const stub = sinon.stub(rokuDeploy, 'getDevId').callsFake(async () => { + return Promise.resolve(''); + }); + + const command = new GetDevIdCommand(); + await command.run({ + host: '1.2.3.4', + password: '5536' + }); + + expect( + stub.getCall(0).args[0] + ).to.eql({ + host: '1.2.3.4' + }); + }); + + it('Zips a folder', () => { + execSync(`node ${cwd}/dist/cli.js zipFolder --srcFolder ${rootDir} --zipFilePath "roku-deploy.zip"`); + + expectPathExists(`${tempDir}/roku-deploy.zip`); + }); +}); From 13f2ca4f6c011b51569d45158cf4ceb50b43fefb Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Thu, 1 Feb 2024 19:02:02 -0500 Subject: [PATCH 10/20] Updated cli file after adding in commands and tests --- src/cli.ts | 326 ++++++++++++++++++++++++++--------------------------- 1 file changed, 158 insertions(+), 168 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 5675926..002551f 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,170 +1,160 @@ #!/usr/bin/env node import * as yargs from 'yargs'; -import { stagingDir } from './testUtils.spec'; -import { prepublishToStaging, zipPackage, createPackage, publish, getOutputZipFilePath, getOutputPkgFilePath, getDeviceInfo, getDevId, zipFolder } from './index'; -const outDir = './out'; - -new Promise((resolve, reject) => { - // TODO: is this necessary?vv - // eslint-disable-next-line - yargs - .command('prepublishToStaging', 'Copies all of the referenced files to the staging folder', (builder) => { - return builder - .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: true }) - .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: true }); - }, (args: any) => { - console.log('prepublishToStaging'); - prepublishToStaging({ - files: [ - 'manifest' - ], - stagingDir: args.stagingDir, - rootDir: args.rootDir - }).then(() => { - console.error('SUCCESS'); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - // TODO: Should we have defaults for these^^ - // TODO: This doesn't work - }) - - .command('zipPackage', 'Given an already-populated staging folder, create a zip archive of it and copy it to the output folder', (builder) => { - return builder - .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) - .option('outDir', { type: 'string', description: 'The output directory', default: outDir, demandOption: false }); - }, (args: any) => { - console.log('zipPackage'); - zipPackage({ - stagingDir: stagingDir, - outDir: args.outDir - }).then(() => { - console.error('SUCCESS'); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - // TODO: Missing manifest file - }) - - .command('createPackage', 'Create a zip folder containing all of the specified roku project files', (builder) => { - return builder - .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) - .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: false }) - .option('outDir', { type: 'string', description: 'The output directory', default: outDir, demandOption: false }); - }, (args: any) => { - console.log('createPackage'); - createPackage({ - files: [ - 'manifest' - ], - stagingDir: '.tmp/dist', - outDir: args.outDir, - rootDir: './src' - }).then(() => { - console.error('SUCCESS'); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - // TODO: Missing manifest file - }) - - .command('publish', 'Publish a pre-existing packaged zip file to a remote Roku', (builder) => { - return builder - .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: true }) - .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: true }) - .option('outDir', { type: 'string', description: 'The output directory', default: outDir, demandOption: false }) - .option('outFile', { type: 'string', description: 'The output file', default: 'roku-deploy', demandOption: false }); - }, (args: any) => { - console.log('publish'); - publish({ - host: args.host, - password: args.password, - outDir: args.outDir, - outFile: args.outFile - }).then(() => { - console.error('SUCCESS'); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - // TODO: Times out - }) - - // TODO: - // convertToSquashfs - // rekeyDevice - // signExistingPackage - // retrieveSignedPackage - // deploy - // deleteInstalledChannel - // takeScreenshot - // deployAndSignPackage - TODO: does the same thing as deploy but also signs package...is it necessary? - - .command('getOutputZipFilePath', 'Centralizes getting output zip file path based on passed in options', (builder) => { - // EXAMPLE: npx roku-deploy getOutputZipFilePath - return builder; - }, (args: any) => { - console.log('getOutputZipFilePath'); - console.log(getOutputZipFilePath({})); - }) - - .command('getOutputPkgFilePath', 'Centralizes getting output pkg file path based on passed in options', (builder) => { - // EXAMPLE: npx roku-deploy getOutputPkgFilePath - return builder; - }, (args: any) => { - console.log('getOutputPkgFilePath'); - let result = getOutputPkgFilePath({}); - console.log(result); - }) - - .command('getDeviceInfo', 'Get the `device-info` response from a Roku device', (builder) => { - return builder - .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: true }); - }, (args: any) => { - console.log('getDeviceInfo'); - let result = getDeviceInfo({ - host: args.host - }).then(() => { - console.error('SUCCESS', result); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - // TODO: returns pending promise? - }) - - .command('getDevId', 'Get Dev ID', (builder) => { - return builder - .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: true }); - }, (args: any) => { - console.log('getDevId'); - let result = getDevId({ - host: args.host - }).then(() => { - console.error('SUCCESS', result); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - // TODO: returns pending promise? - }) - - .command('zipFolder', 'Given a path to a folder, zip up that folder and all of its contents', (builder) => { - // EXAMPLE: npx roku-deploy zipFolder --srcFolder ./src --zipFilePath ./output.zip - return builder - .option('srcFolder', { type: 'string', description: 'The folder that should be zipped', demandOption: true }) - .option('zipFilePath', { type: 'string', description: 'The path to the zip that will be created. Must be .zip file name', demandOption: true }); - }, (args: any) => { - console.log('zipFolder'); - zipFolder( - args.srcFolder, - args.zipFilePath - ).then(() => { - console.error('SUCCESS'); - }, (error) => { - console.error('ERROR', error, '\n', args); - }); - }) - - .argv; -}).catch((e) => { - console.error(e); - process.exit(1); -}); +import { PrepublishCommand } from './commands/PrepublishCommand'; +import { ZipPackageCommand } from './commands/ZipPackageCommand'; +import { CreatePackageCommand } from './commands/CreatePackageCommand'; +import { PublishCommand } from './commands/PublishCommand'; +import { ConvertToSquashfsCommand } from './commands/ConvertToSquashfsCommand'; +import { RekeyDeviceCommand } from './commands/RekeyDeviceCommand'; +import { SignExistingPackageCommand } from './commands/SignExistingPackageCommand'; +import { RetrieveSignedPackageCommand } from './commands/RetrieveSignedPackageCommand'; +import { DeployCommand } from './commands/DeployCommand'; +import { DeleteInstalledChannelCommand } from './commands/DeleteInstalledChannelCommand'; +import { TakeScreenshotCommand } from './commands/TakeScreenshotCommand'; +import { GetOutputZipFilePathCommand } from './commands/GetOutputZipFilePathCommand'; +import { GetOutputPkgFilePathCommand } from './commands/GetOutputPkgFilePathCommand'; +import { GetDeviceInfoCommand } from './commands/GetDeviceInfoCommand'; +import { GetDevIdCommand } from './commands/GetDevIdCommand'; +import { ZipFolderCommand } from './commands/ZipFolderCommand'; + +void yargs + .command('prepublishToStaging', 'Copies all of the referenced files to the staging folder', (builder) => { + return builder + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: false }); + }, (args: any) => { + return new PrepublishCommand().run(args); + }) + + .command('zipPackage', 'Given an already-populated staging folder, create a zip archive of it and copy it to the output folder', (builder) => { + return builder + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', demandOption: false }); + }, (args: any) => { + return new ZipPackageCommand().run(args); + }) + + .command('createPackage', 'Create a zip folder containing all of the specified roku project files', (builder) => { + return builder + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The selected root folder to be copied', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', demandOption: false }); + }, (args: any) => { + return new CreatePackageCommand().run(args); + }) + + .command('publish', 'Publish a pre-existing packaged zip file to a remote Roku', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', demandOption: false }) + .option('outFile', { type: 'string', description: 'The output file', demandOption: false }); + }, (args: any) => { + return new PublishCommand().run(args); + }) + + .command('convertToSquashfs', 'Convert a pre-existing packaged zip file to a squashfs file', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }); + }, (args: any) => { + return new ConvertToSquashfsCommand().run(args); + }) + + .command('rekeyDevice', 'Rekey a device', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }) + .option('rekeySignedPackage', { type: 'string', description: 'The signed package to be used for rekeying', demandOption: false }) + .option('signingPassword', { type: 'string', description: 'The password of the signing key', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The root directory', demandOption: false }) + .option('devId', { type: 'string', description: 'The dev ID', demandOption: false }); + }, (args: any) => { + return new RekeyDeviceCommand().run(args); + }) + + .command('signExistingPackage', 'Sign a package', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }) + .option('signingPassword', { type: 'string', description: 'The password of the signing key', demandOption: false }) + .option('stagingDir', { type: 'string', description: 'The selected staging folder', demandOption: false }); + }, (args: any) => { + return new SignExistingPackageCommand().run(args); + }) + + .command('retrieveSignedPackage', 'Retrieve a signed package', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }) + .option('outFile', { type: 'string', description: 'The output file', demandOption: false }); + }, (args: any) => { + return new RetrieveSignedPackageCommand().run(args); + }) + + .command('deploy', 'Deploy a pre-existing packaged zip file to a remote Roku', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }) + .option('rootDir', { type: 'string', description: 'The root directory', demandOption: false }); + }, (args: any) => { + return new DeployCommand().run(args); + }) + + .command('deleteInstalledChannel', 'Delete an installed channel', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }); + }, (args: any) => { + return new DeleteInstalledChannelCommand().run(args); + }) + + .command('takeScreenshot', 'Take a screenshot', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }) + .option('password', { type: 'string', description: 'The password of the host Roku', demandOption: false }); + }, (args: any) => { + return new TakeScreenshotCommand().run(args); + }) + + .command('getOutputZipFilePath', 'Centralizes getting output zip file path based on passed in options', (builder) => { + return builder + .option('outFile', { type: 'string', description: 'The output file', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', demandOption: false }); + return builder; + }, (args: any) => { + return new GetOutputZipFilePathCommand().run(args); + }) + + .command('getOutputPkgFilePath', 'Centralizes getting output pkg file path based on passed in options', (builder) => { + return builder + .option('outFile', { type: 'string', description: 'The output file', demandOption: false }) + .option('outDir', { type: 'string', description: 'The output directory', demandOption: false }); + }, (args: any) => { + return new GetOutputPkgFilePathCommand().run(args); + }) + + .command('getDeviceInfo', 'Get the `device-info` response from a Roku device', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }); + }, (args: any) => { + return new GetDeviceInfoCommand().run(args); + }) + + .command('getDevId', 'Get Dev ID', (builder) => { + return builder + .option('host', { type: 'string', description: 'The IP Address of the host Roku', demandOption: false }); + }, (args: any) => { + return new GetDevIdCommand().run(args); + }) + + .command('zipFolder', 'Given a path to a folder, zip up that folder and all of its contents', (builder) => { + return builder + .option('srcFolder', { type: 'string', description: 'The folder that should be zipped', demandOption: false }) + .option('zipFilePath', { type: 'string', description: 'The path to the zip that will be created. Must be .zip file name', demandOption: false }); + }, (args: any) => { + console.log('args', args); + return new ZipFolderCommand().run(args); + }) + + .argv; From 2d134412f4a7a8119a383f8b5990e41c7dae6159 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Fri, 2 Feb 2024 12:05:09 -0500 Subject: [PATCH 11/20] Fixed a few test cases --- src/cli.spec.ts | 31 +++++++++++++++++----------- src/commands/GetDeviceInfoCommand.ts | 4 ++-- src/util.ts | 14 +++++++++++++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/cli.spec.ts b/src/cli.spec.ts index 7bddc32..4cf25f3 100644 --- a/src/cli.spec.ts +++ b/src/cli.spec.ts @@ -22,7 +22,6 @@ function execSync(command: string) { const output = childProcess.execSync(command, { cwd: tempDir }); process.stdout.write(output); return output; - // return childProcess.execSync(command, { stdio: 'inherit', cwd: tempDir }); } describe('cli', () => { before(function build() { @@ -33,14 +32,15 @@ describe('cli', () => { fsExtra.emptyDirSync(tempDir); //most tests depend on a manifest file existing, so write an empty one fsExtra.outputFileSync(`${rootDir}/manifest`, ''); + sinon.restore(); }); afterEach(() => { fsExtra.removeSync(tempDir); + sinon.restore(); }); it('Successfully runs prepublishToStaging', () => { //make the files - // fsExtra.outputFileSync(`${rootDir}/manifest`, ''); fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ''); expect(() => { @@ -175,16 +175,14 @@ describe('cli', () => { password: '5536', outFile: 'roku-deploy-test' }); - console.log(stub.getCall(0).args[0]); expect( - stub.getCall(0).args[0] - ).to.eql({ - pathToPkg: 'path_to_pkg', + stub.getCall(0).args + ).to.eql(['path_to_pkg', { host: '1.2.3.4', password: '5536', outFile: 'roku-deploy-test' - });//TODO: fix! + }]); }); it('Deploys a package', async () => { @@ -284,7 +282,7 @@ describe('cli', () => { it('Prints device info to console', async () => { let consoleOutput = ''; sinon.stub(console, 'log').callsFake((...args) => { - consoleOutput += args.join(' ') + '\n'; //TODO: I don't think this is accurately adding a new line + consoleOutput += args.join(' ') + '\n'; }); sinon.stub(rokuDeploy, 'getDeviceInfo').returns(Promise.resolve({ 'device-id': '1234', @@ -293,10 +291,19 @@ describe('cli', () => { await new GetDeviceInfoCommand().run({ host: '1.2.3.4' }); - expect(consoleOutput.trim()).to.eql( - '{"device-id":"1234","serial-number":"abcd"}' - ); - }); //TODO: This passes when it is it.only, but fails in the larger test suite? + + // const consoleOutputObject: Record = { + // 'device-id': '1234', + // 'serial-number': 'abcd' + // }; + + expect(consoleOutput).to.eql([ + 'Name Value ', + '---------------------------', + 'device-id 1234 ', + 'serial-number abcd \n' + ].join('\n')); + }); it('Gets dev id', async () => { const stub = sinon.stub(rokuDeploy, 'getDevId').callsFake(async () => { diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts index 0670849..33f01f5 100644 --- a/src/commands/GetDeviceInfoCommand.ts +++ b/src/commands/GetDeviceInfoCommand.ts @@ -1,10 +1,10 @@ -import { rokuDeploy } from '../index'; +import { rokuDeploy, toTable } from '../index'; export class GetDeviceInfoCommand { async run(args) { const outputPath = await rokuDeploy.getDeviceInfo({ host: args.host }); - console.log(JSON.stringify(outputPath)); + console.log(toTable(outputPath)); } } diff --git a/src/util.ts b/src/util.ts index cb90917..19eed65 100644 --- a/src/util.ts +++ b/src/util.ts @@ -255,3 +255,17 @@ export function standardizePathPosix(stringParts, ...expressions: any[]) { result.join('') ); } + +export function toTable(deviceInfo: Record) { + const margin = 5; + const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; + const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '')?.toString().length)) + margin; + let table = []; + table.push('Name'.padEnd(keyWidth, ' ') + 'Value'.padEnd(keyWidth, ' ')); + table.push('-'.repeat(keyWidth + valueWidth)); + for (const [key, value] of Object.entries(deviceInfo)) { + table.push(key.padEnd(keyWidth, ' ') + value?.toString().padEnd(keyWidth, ' ')); + } + + return table.join('\n'); +} //TODO: Create a test for this function From 09a275f9fedac4276b61919c259583cf8542d6b6 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Fri, 2 Feb 2024 12:08:24 -0500 Subject: [PATCH 12/20] Change name and input of table helper --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 19eed65..a1e22c0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -256,7 +256,7 @@ export function standardizePathPosix(stringParts, ...expressions: any[]) { ); } -export function toTable(deviceInfo: Record) { +export function printObjectToTable(deviceInfo: Record) { const margin = 5; const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '')?.toString().length)) + margin; From 52b1bc06153f67c059cb07a41ed6aa1a0f53ed8f Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 12:56:47 -0500 Subject: [PATCH 13/20] Changed name of toTable --- src/commands/GetDeviceInfoCommand.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts index 33f01f5..9190cb5 100644 --- a/src/commands/GetDeviceInfoCommand.ts +++ b/src/commands/GetDeviceInfoCommand.ts @@ -1,10 +1,10 @@ -import { rokuDeploy, toTable } from '../index'; +import { rokuDeploy, printObjectToTable } from '../index'; export class GetDeviceInfoCommand { async run(args) { const outputPath = await rokuDeploy.getDeviceInfo({ host: args.host }); - console.log(toTable(outputPath)); + console.log(printObjectToTable(outputPath)); } } From f3a0c56f6cc37408923375f83eae1a07bcf5b83f Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 13:31:48 -0500 Subject: [PATCH 14/20] Make test for objectToTable --- src/commands/GetDeviceInfoCommand.ts | 5 +++-- src/util.spec.ts | 20 ++++++++++++++++++++ src/util.ts | 28 ++++++++++++++-------------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts index 9190cb5..af45e53 100644 --- a/src/commands/GetDeviceInfoCommand.ts +++ b/src/commands/GetDeviceInfoCommand.ts @@ -1,10 +1,11 @@ -import { rokuDeploy, printObjectToTable } from '../index'; +import { rokuDeploy } from '../index'; +import { util } from '../util'; export class GetDeviceInfoCommand { async run(args) { const outputPath = await rokuDeploy.getDeviceInfo({ host: args.host }); - console.log(printObjectToTable(outputPath)); + console.log(util.printObjectToTable(outputPath)); } } diff --git a/src/util.spec.ts b/src/util.spec.ts index b174570..70f26e1 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -296,4 +296,24 @@ describe('util', () => { expect(util.decodeHtmlEntities(''')).to.eql(`'`); }); }); + + describe('printObjectToTable', () => { + it('should print an object to a table', () => { + const deviceInfo = { + 'device-id': '1234', + 'serial-number': 'abcd' + }; + + const result = util.printObjectToTable(deviceInfo); + + const expectedOutput = [ + 'Name Value ', + '---------------------------', + 'device-id 1234 ', + 'serial-number abcd ' + ].join('\n'); + + expect(result).to.eql(expectedOutput); + }); + }); }); diff --git a/src/util.ts b/src/util.ts index a1e22c0..29e6c48 100644 --- a/src/util.ts +++ b/src/util.ts @@ -225,6 +225,20 @@ export class Util { }); } + public printObjectToTable(deviceInfo: Record) { + const margin = 5; + const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; + const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '')?.toString().length)) + margin; + let table = []; + table.push('Name'.padEnd(keyWidth, ' ') + 'Value'.padEnd(keyWidth, ' ')); + table.push('-'.repeat(keyWidth + valueWidth)); + for (const [key, value] of Object.entries(deviceInfo)) { + table.push(key.padEnd(keyWidth, ' ') + value?.toString().padEnd(keyWidth, ' ')); + } + + return table.join('\n'); + } + } export let util = new Util(); @@ -255,17 +269,3 @@ export function standardizePathPosix(stringParts, ...expressions: any[]) { result.join('') ); } - -export function printObjectToTable(deviceInfo: Record) { - const margin = 5; - const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; - const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '')?.toString().length)) + margin; - let table = []; - table.push('Name'.padEnd(keyWidth, ' ') + 'Value'.padEnd(keyWidth, ' ')); - table.push('-'.repeat(keyWidth + valueWidth)); - for (const [key, value] of Object.entries(deviceInfo)) { - table.push(key.padEnd(keyWidth, ' ') + value?.toString().padEnd(keyWidth, ' ')); - } - - return table.join('\n'); -} //TODO: Create a test for this function From b5f41ca07f388be604400913056400dc38d9148a Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 13:36:05 -0500 Subject: [PATCH 15/20] add yargs to pacjage.json --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f4f6ecb..14527d2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "parse-ms": "^2.1.0", "postman-request": "^2.88.1-postman.32", "temp-dir": "^2.0.0", - "xml2js": "^0.5.0" + "xml2js": "^0.5.0", + "yargs": "^17.7.2" }, "devDependencies": { "@types/chai": "^4.2.22", @@ -45,6 +46,7 @@ "@types/request": "^2.47.0", "@types/sinon": "^10.0.4", "@types/xml2js": "^0.4.5", + "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "5.1.0", "@typescript-eslint/parser": "5.1.0", "chai": "^4.3.4", From 4b959438a4d9d26ad8c60dd9a71a005c74e5bcbd Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 13:38:23 -0500 Subject: [PATCH 16/20] add package-lock.json --- package-lock.json | 199 +++++++++++++++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index febb616..3695c96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,8 @@ "parse-ms": "^2.1.0", "postman-request": "^2.88.1-postman.32", "temp-dir": "^2.0.0", - "xml2js": "^0.5.0" + "xml2js": "^0.5.0", + "yargs": "^17.7.2" }, "bin": { "roku-deploy": "dist/cli.js" @@ -40,6 +41,7 @@ "@types/request": "^2.47.0", "@types/sinon": "^10.0.4", "@types/xml2js": "^0.4.5", + "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "5.1.0", "@typescript-eslint/parser": "5.1.0", "chai": "^4.3.4", @@ -804,6 +806,21 @@ "@types/node": "*" } }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.1.0.tgz", @@ -1038,7 +1055,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1399,14 +1415,16 @@ } }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/color-convert": { @@ -1675,8 +1693,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/enquirer": { "version": "2.3.6", @@ -1700,7 +1717,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -2304,7 +2320,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2572,7 +2587,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3212,6 +3226,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3278,6 +3303,24 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -4057,7 +4100,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4363,7 +4405,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4377,7 +4418,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4700,7 +4740,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4717,7 +4756,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4732,7 +4770,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4743,8 +4780,7 @@ "node_modules/wrap-ansi/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrappy": { "version": "1.0.2", @@ -4788,7 +4824,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -4800,21 +4835,20 @@ "dev": true }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -4874,6 +4908,14 @@ "node": ">=8" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -5509,6 +5551,21 @@ "@types/node": "*" } }, + "@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.1.0.tgz", @@ -5647,8 +5704,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "3.2.1", @@ -5918,13 +5974,12 @@ "dev": true }, "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "requires": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, @@ -6143,8 +6198,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "enquirer": { "version": "2.3.6", @@ -6164,8 +6218,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-string-regexp": { "version": "1.0.5", @@ -6605,8 +6658,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-func-name": { "version": "2.0.0", @@ -6799,8 +6851,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.3", @@ -7291,6 +7342,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -7335,6 +7397,21 @@ "requires": { "has-flag": "^4.0.0" } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } } } }, @@ -7932,8 +8009,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", @@ -8167,7 +8243,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8178,7 +8253,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -8410,7 +8484,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8421,7 +8494,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -8430,7 +8502,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -8438,8 +8509,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -8478,8 +8548,7 @@ "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { "version": "4.0.0", @@ -8488,18 +8557,24 @@ "dev": true }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + } } }, "yargs-parser": { From b6c6223017bd260fc788ae0e1277e848c44d6e80 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 13:40:05 -0500 Subject: [PATCH 17/20] add new line at eof --- src/commands/PublishCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/PublishCommand.ts b/src/commands/PublishCommand.ts index 91554fa..39a66cc 100644 --- a/src/commands/PublishCommand.ts +++ b/src/commands/PublishCommand.ts @@ -9,4 +9,4 @@ export class PublishCommand { outFile: args.outFile }); } -} \ No newline at end of file +} From f15f29543051421d6aa51ecc4f8978020bd05c9c Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 13:51:56 -0500 Subject: [PATCH 18/20] Add test for coverage, also remove an unncessary '?' --- src/util.spec.ts | 18 ++++++++++++++++++ src/util.ts | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/util.spec.ts b/src/util.spec.ts index 70f26e1..fd3d353 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -315,5 +315,23 @@ describe('util', () => { expect(result).to.eql(expectedOutput); }); + + it('should still print a table when a value is null', () => { + const deviceInfo = { + 'device-id': '1234', + 'serial-number': null + }; + + const result = util.printObjectToTable(deviceInfo); + + const expectedOutput = [ + 'Name Value ', + '---------------------------', + 'device-id 1234 ', + 'serial-number undefined' + ].join('\n'); + + expect(result).to.eql(expectedOutput); + }); }); }); diff --git a/src/util.ts b/src/util.ts index 29e6c48..c275c88 100644 --- a/src/util.ts +++ b/src/util.ts @@ -228,7 +228,7 @@ export class Util { public printObjectToTable(deviceInfo: Record) { const margin = 5; const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; - const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '')?.toString().length)) + margin; + const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '').toString().length)) + margin; let table = []; table.push('Name'.padEnd(keyWidth, ' ') + 'Value'.padEnd(keyWidth, ' ')); table.push('-'.repeat(keyWidth + valueWidth)); From 096d23aa49c6314aa887c0f54da232ecd4dee375 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 15:34:00 -0500 Subject: [PATCH 19/20] Change function name --- src/util.spec.ts | 6 +++--- src/util.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util.spec.ts b/src/util.spec.ts index fd3d353..f58c18c 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -297,14 +297,14 @@ describe('util', () => { }); }); - describe('printObjectToTable', () => { + describe('objectToTableString', () => { it('should print an object to a table', () => { const deviceInfo = { 'device-id': '1234', 'serial-number': 'abcd' }; - const result = util.printObjectToTable(deviceInfo); + const result = util.objectToTableString(deviceInfo); const expectedOutput = [ 'Name Value ', @@ -322,7 +322,7 @@ describe('util', () => { 'serial-number': null }; - const result = util.printObjectToTable(deviceInfo); + const result = util.objectToTableString(deviceInfo); const expectedOutput = [ 'Name Value ', diff --git a/src/util.ts b/src/util.ts index c275c88..ca7d7d4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -225,7 +225,7 @@ export class Util { }); } - public printObjectToTable(deviceInfo: Record) { + public objectToTableString(deviceInfo: Record) { const margin = 5; const keyWidth = Math.max(...Object.keys(deviceInfo).map(x => x.length)) + margin; const valueWidth = Math.max(...Object.values(deviceInfo).map(x => (x ?? '').toString().length)) + margin; From dfd1e2434438787dbfe721f76db6fea477caf441 Mon Sep 17 00:00:00 2001 From: Milap Naik Date: Tue, 6 Feb 2024 15:43:30 -0500 Subject: [PATCH 20/20] last function name change --- src/commands/GetDeviceInfoCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/GetDeviceInfoCommand.ts b/src/commands/GetDeviceInfoCommand.ts index af45e53..1bf10b3 100644 --- a/src/commands/GetDeviceInfoCommand.ts +++ b/src/commands/GetDeviceInfoCommand.ts @@ -6,6 +6,6 @@ export class GetDeviceInfoCommand { const outputPath = await rokuDeploy.getDeviceInfo({ host: args.host }); - console.log(util.printObjectToTable(outputPath)); + console.log(util.objectToTableString(outputPath)); } }