Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
715af62
Clarify zip commands
MilapNaik May 20, 2025
3a294ec
Remove duplicate args
MilapNaik May 20, 2025
2e6f2ca
Clarify createSignedPackage and sideload args
MilapNaik May 20, 2025
1dc59c5
Merge branch 'v4' into new-args-for-commands
MilapNaik Jun 23, 2025
2669edb
More args changes
MilapNaik Jul 24, 2025
d27a227
Take rootdir out of RekeyDevice in tests
MilapNaik Jul 24, 2025
26aec57
Use outDir for rekeyDevice() use since we took out rootDir and getOut…
MilapNaik Jul 30, 2025
4f52123
fixes to path to pkg flow: only make CLI changes, leave internal func…
MilapNaik Aug 5, 2025
f3d4e32
Take out outdir
MilapNaik Aug 6, 2025
d4f4fe8
Take out all aliases
MilapNaik Aug 7, 2025
022e0c0
Change bundle to use out instead of outdir and outfile
MilapNaik Aug 7, 2025
c36dd30
Take out duplicate host, change outdir type to avoid confusion
MilapNaik Aug 7, 2025
7aebfaa
Change all remotePort to ecpPort, and add packagePort when necessary
MilapNaik Aug 7, 2025
d57455d
Change readme examples to use variable names that the CLI was changed to
MilapNaik Aug 7, 2025
f8e8bb4
Delete bundle, change sideload for most of what bundle was
MilapNaik Aug 7, 2025
8b15191
Use outzip instead
MilapNaik Aug 7, 2025
f738554
Get rid of aliases
MilapNaik Aug 29, 2025
26844b3
Turn createSignedPackage into package, and remove exec
MilapNaik Aug 29, 2025
f47dbad
Process app title and app version, or manifest path for creating a si…
MilapNaik Aug 29, 2025
9a3b3d7
Close app when sideloading unless noclose is specified
MilapNaik Aug 29, 2025
e1c41f7
Allow for either sideloading zip or rootDir; if it's zip, we retain t…
MilapNaik Aug 29, 2025
65fa031
resolve cwd with manifestPath
MilapNaik Sep 2, 2025
3d9827f
Take out stagingDir form createSignedPackage, use tempDir in tests
MilapNaik Sep 2, 2025
d98c0e8
Take out more stagingDir
MilapNaik Sep 3, 2025
21bfc56
Delete exec command
MilapNaik Sep 3, 2025
6936b82
Delete more exec command
MilapNaik Sep 3, 2025
c07856d
Fix tests, add a few new ones
MilapNaik Sep 4, 2025
cd41167
Fix tests and add a few more for sideload command
MilapNaik Sep 4, 2025
edf2068
Forgot to delete this alias
MilapNaik Sep 4, 2025
a184314
Make changes to readme so it aligns with cli changes
MilapNaik Sep 4, 2025
cfcc535
Make changes to getOptionsFromJson so it stays faithful to Readme
MilapNaik Sep 4, 2025
1330889
Fix test failing because of getOptionsFromJson change
MilapNaik Sep 4, 2025
0b3d408
Some cli doc wording tweaks
TwitchBronBron Nov 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 85 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ sample rokudeploy.json
The new release has a few breaking changes that is worth going over in order to prepare developers for what they will need to change when they choose to upgrade.

### JavaScript functions don't load config files from disk
In v3, files like `roku-deploy.json` and `bsconfig.json` would be loaded anytime a rokuDeploy function was called through the NodeJS api. This functionality has been removed in v4 so that developers have more control over when the config files are loaded. If your script needs to load the config file values, you can simply call `util.getOptionsFromJson` before calling the desired rokuDeploy function. Here's an example:
In v3, files like `roku-deploy.json` and `bsconfig.json` would be loaded anytime a rokuDeploy function was called through the NodeJS api. This functionality has been removed in v4 so that developers have more control over when the config files are loaded. If your script needs to load the config file values, you can simply call `util.getOptionsFromJson` before calling the desired rokuDeploy function. This will default to load from `rokudeploy.json`. Here's an example:

```javascript
const config = {
//get the default options
...rokuDeploy.getOptions(),
//override with any values found in the `rokudeploy.json` file
//override with any values found in the `rokudeploy.json` file. You can specify current working directory here.
...util.getOptionsFromJson({ cwd: process.cwd() })
};
await rokuDeploy.sideload(options);
await rokuDeploy.sideload(config);
```

### Removed support for bsconfig.json
Expand All @@ -72,11 +72,11 @@ We've removed support for loading `bsconfig.json` files. This was introduced in
const config = {
//get the default options
...rokuDeploy.getOptions(),
//override with any values found in
//override with any values found in config file
...util.getOptionsFromJson({ configPath: './bsconfig.json' })
};
//call some rokuDeploy function
await rokuDeploy.sideload(options);
await rokuDeploy.sideload(config);
```

### Changed, added, or moved some functions in the main Node API
Expand Down Expand Up @@ -104,55 +104,82 @@ Lastly, the default files array has changed. node modules and static analysis fi

## CLI Usage

### Deploy a zip package
Deploy a .zip package of your project to a roku device
### Sideload a project to your Roku device
Sideload a .zip package or directory to a roku device:
```shell
npx roku-deploy deploy --host 'ip.of.roku' --password 'password of device' --rootDir '.' --outDir './out'
```

# Sideload a zip file
npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --zip './path/to/your/app.zip'

### Create a signed package of your project
```shell
npx roku-deploy deploy package --host 'ip.of.roku' --password 'password' --signingPassword 'signing password'
# Sideload from a directory (will be zipped first automatically)
npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --rootDir './path/to/your/project'
```

### Stage the root directory
### Create a signed package from an existing dev channel
```shell
npx roku-deploy stage --stagingDir './path/to/staging/dir' --rootDir './path/to/root/dir'
npx roku-deploy package --host 'ip.of.roku' --password 'password' --signingPassword 'signing password' --out './out/my-app.pkg'
```

### Zip the contents of a given directory
### Stage files to a directory
Copy your project files to a staging directory:
```shell
npx roku-deploy zip --stagingDir './path/to/root/dir' --outDir './path/to/out/dir'
npx roku-deploy stage --rootDir './path/to/root/dir' --out './path/to/staging/dir'
```

### Press the Home key
### Zip a directory
Create a zip file from a directory:
```shell
npx roku-deploy keyPress --key 'Home' --host 'ip.of.roku' --remotePort 1234 --timeout 5000
npx roku-deploy zip --dir './path/to/directory' --out './path/to/output.zip'
```

### Sideload a build
### Remote control commands
Send key presses to your Roku:
```shell
npx roku-deploy sideload --host 'ip.of.roku' --password 'password' --outDir './path/to/out/dir'
# Press a key
npx roku-deploy keyPress --key 'Home' --host 'ip.of.roku'

# Hold a key down
npx roku-deploy keyDown --key 'Up' --host 'ip.of.roku'

# Release a key
npx roku-deploy keyUp --key 'Up' --host 'ip.of.roku'

# Send text to the device
npx roku-deploy sendText --text 'Hello World' --host 'ip.of.roku'

# Interactive remote control mode
npx roku-deploy remote-control --host 'ip.of.roku'
```

### Convert to SquashFS
Convert your dev channel to SquashFS format:
```shell
npx roku-deploy squash --host 'ip.of.roku' --password 'password'
```

### Create a signed package
### Device management
```shell
npx roku-deploy sign --host 'ip.of.roku' --password 'password'
# Take a screenshot
npx roku-deploy screenshot --host 'ip.of.roku' --password 'password' --out './screenshot.jpg'

# Rekey a device with a signed package
npx roku-deploy rekey --host 'ip.of.roku' --password 'password' --pkg './path/to/signed.pkg' --signingPassword 'signing password'

# Delete the dev channel
npx roku-deploy deleteDevChannel --host 'ip.of.roku' --password 'password'

# Get device information
npx roku-deploy getDeviceInfo --host 'ip.of.roku'

# Get device ID
npx roku-deploy getDevId --host 'ip.of.roku'
```

You can view the full list of commands by running:
You can view the full list of commands and their options by running:

```shell
npx roku-deploy --help
```


## JavaScript Usage

### Copying the files to staging
Expand Down Expand Up @@ -242,6 +269,36 @@ rokuDeploy.createSignedPackage({
})
```

### Send text to device
```typescript
rokuDeploy.sendText({
text: 'Hello World',
host: 'ip-of-roku'
//...other options if necessary
})
```

### Take a screenshot
```typescript
rokuDeploy.captureScreenshot({
host: 'ip-of-roku',
password: 'password',
screenshotDir: './screenshots/',
screenshotFile: 'screenshot.jpg'
//...other options if necessary
})
```

### Rekey a device
```typescript
rokuDeploy.rekeyDevice({
host: 'ip-of-roku',
password: 'password',
rekeySignedPackage: './path/to/signed.pkg'
//...other options if necessary
})
```

Can't find what you need? We offer a variety of functions available in the [RokuDeploy.ts file](https://github.com/rokucommunity/roku-deploy/blob/v4/src/RokuDeploy.ts). Here are all of the public functions:
- `stage()`
- `zip()`
Expand All @@ -256,10 +313,11 @@ Can't find what you need? We offer a variety of functions available in the [Roku
- `createSignedPackage()`
- `deleteDevChannel()`
- `captureScreenshot()`
- `getOptions()`
- `checkRequiredOptions()`
- `convertToSquashfs()`
- `getDeviceInfo()`
- `getDevId()`
- `getOptions()`
- `checkRequiredOptions()`


### Running roku-deploy as an npm script
Expand Down
80 changes: 58 additions & 22 deletions src/RokuDeploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,6 @@ describe('index', () => {
password: 'password',
rekeySignedPackage: options.rekeySignedPackage,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: options.devId
});
} catch (e) {
Expand All @@ -1408,17 +1407,16 @@ describe('index', () => {
</div>`;
mockDoPostRequest(body);
try {
fsExtra.writeFileSync(s`${tempDir}/notReal.pkg`, '');
fsExtra.writeFileSync(s`notReal.pkg`, '');
await rokuDeploy.rekeyDevice({
host: '1.2.3.4',
password: 'password',
rekeySignedPackage: s`../notReal.pkg`,
rekeySignedPackage: s`notReal.pkg`,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: options.devId
});
} finally {
fsExtra.removeSync(s`${tempDir}/notReal.pkg`);
fsExtra.removeSync(s`notReal.pkg`);
}
});

Expand All @@ -1432,7 +1430,6 @@ describe('index', () => {
password: 'password',
rekeySignedPackage: s`${tempDir}/testSignedPackage.pkg`,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: options.devId
});
});
Expand All @@ -1447,7 +1444,6 @@ describe('index', () => {
password: 'password',
rekeySignedPackage: options.rekeySignedPackage,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: options.devId
});
});
Expand All @@ -1462,7 +1458,6 @@ describe('index', () => {
password: 'password',
rekeySignedPackage: options.rekeySignedPackage,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: undefined
});
});
Expand All @@ -1475,7 +1470,6 @@ describe('index', () => {
password: 'password',
rekeySignedPackage: options.rekeySignedPackage,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: options.devId
});
} catch (e) {
Expand Down Expand Up @@ -1516,7 +1510,6 @@ describe('index', () => {
password: 'password',
rekeySignedPackage: options.rekeySignedPackage,
signingPassword: options.signingPassword,
rootDir: options.rootDir,
devId: '45fdc2019903ac333ff624b0b2cddd2c733c3e74'
});
} catch (e) {
Expand All @@ -1530,7 +1523,10 @@ describe('index', () => {
describe('createSignedPackage', () => {
let onHandler: any;
beforeEach(() => {
fsExtra.outputFileSync(`${stagingDir}/manifest`, ``);
fsExtra.outputFileSync(`${tempDir}/manifest`, `
title=RokuDeployTestChannel
major_version=1
minor_version=0`);
sinon.stub(fsExtra, 'ensureDir').callsFake(((pth: string, callback: (err: Error) => void) => {
//do nothing, assume the dir gets created
}) as any);
Expand Down Expand Up @@ -1567,7 +1563,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
});
} catch (e) {
expect(e).to.equal(error);
Expand All @@ -1583,7 +1579,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
});
} catch (e) {
expect(e).to.be.instanceof(errors.UnparsableDeviceResponseError);
Expand All @@ -1604,7 +1600,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
}),
'Invalid Password.'
);
Expand All @@ -1622,8 +1618,8 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir,
outDir: outDir
outDir: outDir,
manifestPath: s`${tempDir}/manifest`
});
expect(pkgPath).to.equal(s`${outDir}/roku-deploy.pkg`);
expect(stub.getCall(0).args[0].url).to.equal('http://1.2.3.4:80/pkgs//P6953175d5df120c0069c53de12515b9a.pkg');
Expand All @@ -1636,7 +1632,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
});
expect(pkgPath).to.equal('pkgs/sdcard0/Pae6cec1eab06a45ca1a7f5b69edd3a20.pkg');
});
Expand All @@ -1648,7 +1644,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
}),
'Unknown error signing package'
);
Expand All @@ -1665,13 +1661,53 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir,
devId: '123'
devId: '123',
manifestPath: s`${tempDir}/manifest`
}),
`Package signing cancelled: provided devId '123' does not match on-device devId '789'`
);
});

it('should return error if neither manifestPath nor appTitle and appVersion are provided', async () => {
await expectThrowsAsync(
rokuDeploy.createSignedPackage({
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
devId: '123'
}),
`Either appTitle and appVersion or manifestPath must be provided`
);
});

it('should return error if major or minor version is missing from manifest', async () => {
fsExtra.outputFileSync(`${tempDir}/manifest`, `title=AwesomeApp`);
await expectThrowsAsync(
rokuDeploy.createSignedPackage({
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
devId: '123',
manifestPath: s`${tempDir}/manifest`
}),
`Either major or minor version is missing from the manifest`
);
});

it('should return error if value for appTitle is missing from manifest', async () => {
fsExtra.outputFileSync(`${tempDir}/manifest`, `major_version=1\nminor_version=0`);
await expectThrowsAsync(
rokuDeploy.createSignedPackage({
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
devId: '123',
manifestPath: s`${tempDir}/manifest`
}),
`Value for appTitle is missing from the manifest`
);
});

it('returns a pkg file path on success', async () => {
//the write stream should return null, which causes a specific branch to be executed
createWriteStreamStub.callsFake(() => {
Expand All @@ -1698,7 +1734,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'password',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
});
} catch (e) {
error = e as any;
Expand All @@ -1723,7 +1759,7 @@ describe('index', () => {
host: '1.2.3.4',
password: 'aaaa',
signingPassword: options.signingPassword,
stagingDir: stagingDir
manifestPath: s`${tempDir}/manifest`
}),
'Some error'
);
Expand Down
Loading