Skip to content

Commit

Permalink
cloud flash command supports optional --product flag to flash a produ…
Browse files Browse the repository at this point in the history
…ct device
  • Loading branch information
busticated committed Apr 9, 2020
1 parent ee28ddd commit fe21048
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 16 deletions.
7 changes: 6 additions & 1 deletion src/cli/cloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ module.exports = ({ commandProcessor, root }) => {

commandProcessor.createCommand(cloud, 'flash', 'Pass a binary, source file, or source directory to a device!', {
params: '<device> [files...]',
options: Object.assign({}, compileOptions),
options: Object.assign({}, compileOptions, {
'product': {
description: 'Target a device within the given Product ID or Slug'
}
}),
handler: (args) => {
const CloudCommands = require('../cmd/cloud');
return new CloudCommands().flashDevice(args);
Expand All @@ -71,6 +75,7 @@ module.exports = ({ commandProcessor, root }) => {
'$0 $command green tinker': 'Flash the default Tinker app to device green',
'$0 $command red blink.ino': 'Compile blink.ino in the cloud and flash to device red',
'$0 $command orange firmware.bin': 'Flash the pre-compiled binary to device orange',
'$0 $command blue --product 12345': 'Compile the source code in the current directory in the cloud and flash to device blue within product 12345'
}
});

Expand Down
14 changes: 9 additions & 5 deletions src/cli/cloud.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,13 @@ describe('Cloud Command-Line Interface', () => {
});

it('Parses options', () => {
const argv = commandProcessor.parse(root, ['cloud', 'flash', 'my-device', '--followSymlinks', '--target', '2.0.0']);
const args = ['cloud', 'flash', 'my-device', '--followSymlinks', '--target', '2.0.0', '--product', '12345'];
const argv = commandProcessor.parse(root, args);
expect(argv.clierror).to.equal(undefined);
expect(argv.params).to.eql({ device: 'my-device', files: [] });
expect(argv.followSymlinks).to.equal(true);
expect(argv.target).to.equal('2.0.0');
expect(argv.product).to.equal('12345');
});

it('Includes help', () => {
Expand All @@ -236,12 +238,14 @@ describe('Cloud Command-Line Interface', () => {
'Options:',
' --target The firmware version to compile against. Defaults to latest version, or version on device for cellular. [string]',
' --followSymlinks Follow symlinks when collecting files [boolean]',
' --product Target a device within the given Product ID or Slug [string]',
'',
'Examples:',
' particle cloud flash blue Compile the source code in the current directory in the cloud and flash to device blue',
' particle cloud flash green tinker Flash the default Tinker app to device green',
' particle cloud flash red blink.ino Compile blink.ino in the cloud and flash to device red',
' particle cloud flash orange firmware.bin Flash the pre-compiled binary to device orange',
' particle cloud flash blue Compile the source code in the current directory in the cloud and flash to device blue',
' particle cloud flash green tinker Flash the default Tinker app to device green',
' particle cloud flash red blink.ino Compile blink.ino in the cloud and flash to device red',
' particle cloud flash orange firmware.bin Flash the pre-compiled binary to device orange',
' particle cloud flash blue --product 12345 Compile the source code in the current directory in the cloud and flash to device blue within product 12345',
''
].join(os.EOL));
});
Expand Down
14 changes: 13 additions & 1 deletion src/cmd/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ module.exports = class ParticleApi {
);
}

markAsDevelopmentDevice(deviceId, development, product){
return this._wrap(
this.api.markAsDevelopmentDevice({
development,
deviceId,
product,
auth: this.accessToken
})
);
}

claimDevice(deviceId, requestTransfer){
return this._wrap(
this.api.claimDevice({
Expand Down Expand Up @@ -110,10 +121,11 @@ module.exports = class ParticleApi {
);
}

flashDevice(deviceId, files, targetVersion){
flashDevice(deviceId, files, targetVersion, product){
return this._wrap(
this.api.flashDevice({
deviceId,
product,
// TODO (mirande): callers should provide an object like: { [filename]: filepath }
files: files.map || files,
targetVersion,
Expand Down
35 changes: 26 additions & 9 deletions src/cmd/cloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ParticleAPI = require('./api');
const UI = require('../lib/ui');
const prompts = require('../lib/prompts');

const fs = require('fs');
const fs = require('fs-extra');
const path = require('path');
const extend = require('xtend');
const chalk = require('chalk');
Expand Down Expand Up @@ -151,7 +151,7 @@ module.exports = class CloudCommand {
});
}

flashDevice({ target, followSymlinks, params: { device, files } }){
flashDevice({ target, followSymlinks, product, params: { device, files } }){
return Promise.resolve()
.then(() => {
if (files.length === 0){
Expand All @@ -160,7 +160,7 @@ module.exports = class CloudCommand {
}

if (!fs.existsSync(files[0])){
return this._flashKnownApp({ deviceId: device, filePath: files[0] });
return this._flashKnownApp({ product, deviceId: device, filePath: files[0] });
}

const targetVersion = target === 'latest' ? null : target;
Expand Down Expand Up @@ -193,7 +193,7 @@ module.exports = class CloudCommand {
this.ui.stdout.write(os.EOL);
}

return this._doFlash({ deviceId: device, fileMapping, targetVersion });
return this._doFlash({ product, deviceId: device, fileMapping, targetVersion });
});
})
.catch((error) => {
Expand All @@ -202,11 +202,18 @@ module.exports = class CloudCommand {
});
}

_doFlash({ deviceId, fileMapping, targetVersion }){
_doFlash({ product, deviceId, fileMapping, targetVersion }){
return Promise.resolve()
.then(() => {
if (!product){
return;
}
this.ui.stdout.write(`marking device ${deviceId} as a development device${os.EOL}`);
return createAPI().markAsDevelopmentDevice(deviceId, true, product);
})
.then(() => {
this.ui.stdout.write(`attempting to flash firmware to your device ${deviceId}${os.EOL}`);
return createAPI().flashDevice(deviceId, fileMapping, targetVersion);
return createAPI().flashDevice(deviceId, fileMapping, targetVersion, product);
})
.then((resp) => {
if (resp.status || resp.message){
Expand All @@ -218,12 +225,22 @@ module.exports = class CloudCommand {
throw normalizedApiError(resp);
}
})
.then(() => {
if (!product){
return;
}
[
`device ${deviceId} is now marked as a developement device and will NOT receive automatic product firmware updates.`,
'to resume normal updates, please visit:',
`https://console.particle.io/${product}/devices/unmark-development/${deviceId}`
].forEach(line => this.ui.stdout.write(`${line}${os.EOL}`));
})
.catch(err => {
throw normalizedApiError(err);
});
}

_flashKnownApp({ deviceId, filePath }){
_flashKnownApp({ product, deviceId, filePath }){
if (!settings.knownApps[filePath]){
throw new VError(`I couldn't find that file: ${filePath}`);
}
Expand Down Expand Up @@ -265,7 +282,7 @@ module.exports = class CloudCommand {
});
})
.then((fileMapping) => {
return this._doFlash({ deviceId, fileMapping });
return this._doFlash({ product, deviceId, fileMapping });
});
}

Expand Down Expand Up @@ -375,7 +392,7 @@ module.exports = class CloudCommand {
return createAPI().downloadFirmwareBinary(resp.binary_id)
.then(data => {
this.ui.stdout.write(`saving to: ${filename}${os.EOL}`);
return fs.promises.writeFile(filename, data);
return fs.writeFile(filename, data);
})
.then(() => {
return resp.sizeInfo;
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/cloud.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
PRODUCT_01_DEVICE_01_ID,
PRODUCT_01_DEVICE_01_NAME,
PATH_TMP_DIR,
PATH_PROJ_BLANK_INO,
PATH_PROJ_STROBY_INO,
PATH_FIXTURES_PROJECTS_DIR
} = require('../lib/env');
Expand Down Expand Up @@ -170,6 +171,27 @@ describe('Cloud Commands [@device]', () => {
await delay(40 * 1000); // TODO (mirande): replace w/ `cli.waitForDeviceToGetOnline()` helper
});

it('Flashes a product device', async () => {
const args = ['cloud', 'flash', PRODUCT_01_DEVICE_01_ID, PATH_PROJ_BLANK_INO, '--product', PRODUCT_01_ID];
const { stdout, stderr, exitCode } = await cli.run(args);
const log = [
'Including:',
` ${PATH_PROJ_BLANK_INO}`,
`marking device ${PRODUCT_01_DEVICE_01_ID} as a development device`,
`attempting to flash firmware to your device ${PRODUCT_01_DEVICE_01_ID}`,
'Flash device OK: Update started',
`device ${PRODUCT_01_DEVICE_01_ID} is now marked as a developement device and will NOT receive automatic product firmware updates.`,
'to resume normal updates, please visit:',
`https://console.particle.io/${PRODUCT_01_ID}/devices/unmark-development/${PRODUCT_01_DEVICE_01_ID}`
];

expect(stdout.split('\n')).to.include.members(log);
expect(stderr).to.equal('');
expect(exitCode).to.equal(0);

await delay(40 * 1000); // TODO (mirande): replace w/ `cli.waitForProductVariable()` helper
});

it('Removes device', async () => {
const args = ['cloud', 'remove', DEVICE_NAME, '--yes'];
const { stdout, stderr, exitCode } = await cli.run(args);
Expand Down

0 comments on commit fe21048

Please sign in to comment.