diff --git a/.resinci.yml b/.resinci.yml index 769f34b3..577d7e28 100644 --- a/.resinci.yml +++ b/.resinci.yml @@ -5,31 +5,26 @@ npm: os: alpine architecture: x86_64 node_versions: - - "8" - "10" - name: linux os: alpine architecture: x86 node_versions: - - "8" - "10" - name: darwin os: macos architecture: x86_64 node_versions: - - "8" - "10" - name: windows os: windows architecture: x86_64 node_versions: - - "8" - "10" - name: windows os: windows architecture: x86 node_versions: - - "8" - "10" docker: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 561c7e61..8f1a2eb4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,8 @@ The balena CLI is an open source project and your contribution is welcome! In order to ease development: * `npm run build:fast` skips some of the build steps for interactive testing, or -* `./bin/balena-dev` uses `ts-node/register` and `coffeescript/register` to transpile on the fly. +* `npm run test:source` skips testing the standalone zip packages (which is rather slow) +* `./bin/balena-dev` uses `ts-node/register` to transpile on the fly. Before opening a PR, test your changes with `npm test`. Keep compatibility in mind, as the CLI is meant to run on Linux, macOS and Windows. balena CI will run test code on all three platforms, but @@ -121,22 +122,15 @@ $ npm install # "cleanly" update the npm-shrinkwrap.json file $ git add npm-shrinkwrap.json # add it for committing (solve merge errors) ``` -## TypeScript vs CoffeeScript, and Capitano vs oclif +## TypeScript and oclif -The CLI was originally written in [CoffeeScript](https://coffeescript.org), but we decided to -migrate to [TypeScript](https://www.typescriptlang.org/) in order to take advantage of static -typing and formal programming interfaces. The migration is taking place gradually, as part of -maintenance work or the implementation of new features. The recommended way of making the -conversion is to first generate plain Javascript, for example using the command: - -``` -npx decaffeinate --use-js-modules file.coffee -``` - -Then manually convert plain Javascript to Typescript. There is also a ["Coffeescript Preview" -Visual Studio Code -extension](https://marketplace.visualstudio.com/items?itemName=drewbarrett.vscode-coffeescript-preview) -that you may find handy. +The CLI currently contains a mix of plain JavaScript and +[TypeScript](https://www.typescriptlang.org/) code. The goal is to have all code written in +Typescript, in order to take advantage of static typing and formal programming interfaces. +The migration towards Typescript is taking place gradually, as part of maintenance work or +the implementation of new features. Historically, the CLI was originally written in +[CoffeeScript](https://coffeescript.org), but all CoffeeScript code was migrated to either +Javascript or Typescript. Similarly, [Capitano](https://github.com/balena-io/capitano) was originally adopted as the CLI's framework, but later we decided to take advantage of [oclif](https://oclif.io/)'s features such diff --git a/automation/build-bin.ts b/automation/build-bin.ts index 48a36567..056d98bc 100644 --- a/automation/build-bin.ts +++ b/automation/build-bin.ts @@ -118,7 +118,7 @@ async function buildPkg() { console.log(`\nCopying to build-bin:\n${nativeExtensionPaths.join('\n')}`); - await Bluebird.map(nativeExtensionPaths, extPath => + await Bluebird.map(nativeExtensionPaths, (extPath) => fs.copy( extPath, extPath.replace( @@ -268,7 +268,7 @@ export async function buildOclifInstaller() { } for (const dir of dirs) { console.log(`rimraf(${dir})`); - await Bluebird.fromCallback(cb => rimraf(dir, cb)); + await Bluebird.fromCallback((cb) => rimraf(dir, cb)); } console.log('======================================================='); console.log(`oclif-dev "${packCmd}" "${packOpts.join('" "')}"`); diff --git a/automation/capitanodoc/markdown.ts b/automation/capitanodoc/markdown.ts index d7752a89..9d910bb0 100644 --- a/automation/capitanodoc/markdown.ts +++ b/automation/capitanodoc/markdown.ts @@ -55,7 +55,7 @@ function renderOclifCommand(command: OclifCommand): string[] { result.push(description); if (!_.isEmpty(command.examples)) { - result.push('Examples:', command.examples!.map(v => `\t${v}`).join('\n')); + result.push('Examples:', command.examples!.map((v) => `\t${v}`).join('\n')); } if (!_.isEmpty(command.args)) { @@ -106,7 +106,7 @@ function renderToc(categories: Category[]): string[] { result.push(`- ${category.title}`); result.push( category.commands - .map(command => { + .map((command) => { const signature = typeof command === 'object' ? command.signature // Capitano @@ -139,10 +139,7 @@ function sortCommands(doc: Document): void { (cmd: CapitanoCommand | OclifCommand, x: string) => typeof cmd === 'object' // Capitano vs oclif command ? cmd.signature.replace(/\W+/g, ' ').includes(x) - : (cmd.usage || '') - .toString() - .replace(/\W+/g, ' ') - .includes(x), + : (cmd.usage || '').toString().replace(/\W+/g, ' ').includes(x), ), ); } diff --git a/automation/capitanodoc/utils.ts b/automation/capitanodoc/utils.ts index c0139c07..2a29e162 100644 --- a/automation/capitanodoc/utils.ts +++ b/automation/capitanodoc/utils.ts @@ -93,7 +93,7 @@ export class MarkdownFileParser { crlfDelay: Infinity, }); - rl.on('line', line => { + rl.on('line', (line) => { // try to match a line like "## Getting Started", where the number // of '#' characters is the sectionLevel ('##' -> 2), and the // sectionTitle is "Getting Started" diff --git a/automation/check-doc.js b/automation/check-doc.js index 8f1ac65a..26b0bc39 100644 --- a/automation/check-doc.js +++ b/automation/check-doc.js @@ -40,13 +40,13 @@ async function checkBuildTimestamps() { const stagedFiles = _.uniq([ ...gitStatus.created, ...gitStatus.staged, - ...gitStatus.renamed.map(o => o.to), + ...gitStatus.renamed.map((o) => o.to), ]) // select only staged files that start with lib/ or typings/ - .filter(f => f.match(/^(lib|typings)[/\\]/)) - .map(f => path.join(ROOT, f)); + .filter((f) => f.match(/^(lib|typings)[/\\]/)) + .map((f) => path.join(ROOT, f)); - const fStats = await Promise.all(stagedFiles.map(f => fs.stat(f))); + const fStats = await Promise.all(stagedFiles.map((f) => fs.stat(f))); fStats.forEach((fStat, index) => { if (fStat.mtimeMs > docStat.mtimeMs) { const fPath = stagedFiles[index]; diff --git a/automation/check-npm-version.js b/automation/check-npm-version.js index d4bb5d12..39ed083e 100644 --- a/automation/check-npm-version.js +++ b/automation/check-npm-version.js @@ -31,9 +31,7 @@ function semverGte(v1, v2) { function checkNpmVersion() { const execSync = require('child_process').execSync; - const npmVersion = execSync('npm --version') - .toString() - .trim(); + const npmVersion = execSync('npm --version').toString().trim(); const requiredVersion = '6.9.0'; if (!semverGte(npmVersion, requiredVersion)) { // In case you take issue with the error message below: diff --git a/automation/deploy-bin.ts b/automation/deploy-bin.ts index a98f7cc2..626a77f1 100644 --- a/automation/deploy-bin.ts +++ b/automation/deploy-bin.ts @@ -61,7 +61,7 @@ export async function release() { } /** Return a cached Octokit instance, creating a new one as needed. */ -const getOctokit = _.once(function() { +const getOctokit = _.once(function () { const Octokit = (require('@octokit/rest') as typeof import('@octokit/rest')).Octokit.plugin( require('@octokit/plugin-throttling'), ); diff --git a/automation/update-module.ts b/automation/update-module.ts index 25a13d1e..7e2b271c 100644 --- a/automation/update-module.ts +++ b/automation/update-module.ts @@ -87,7 +87,7 @@ async function main() { const upstreamName = process.argv[2]; - const upstream = upstreams.find(v => v.repo === upstreamName); + const upstream = upstreams.find((v) => v.repo === upstreamName); if (!upstream) { console.error( diff --git a/automation/utils.ts b/automation/utils.ts index cfdd0302..8ace95cb 100644 --- a/automation/utils.ts +++ b/automation/utils.ts @@ -50,7 +50,7 @@ export async function runUnderMsys(argv?: string[]) { await new Promise((resolve, reject) => { const args = ['-lc', shellEscape(newArgv)]; const child = spawn(MSYS2_BASH, args, { stdio: 'inherit' }); - child.on('close', code => { + child.on('close', (code) => { if (code) { console.log(`runUnderMsys: child process exited with code ${code}`); reject(code); @@ -93,7 +93,7 @@ export async function getSubprocessStdout( // every line provided to the stderr stream const lines = _.filter( stderr.trim().split(/\r?\n/), - line => !line.startsWith('[debug]'), + (line) => !line.startsWith('[debug]'), ); if (lines.length > 0) { reject( diff --git a/doc/cli.markdown b/doc/cli.markdown index 1d75acca..73a88175 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -306,7 +306,7 @@ Examples: #### -v, --verbose -add extra columns in the tabular output (SLUG) +No-op since release v12.0.0 ## app <name> @@ -729,24 +729,22 @@ List the environment or configuration variables of an application, device or service, as selected by the respective command-line options. (A service is an application container in a "microservices" application.) +The results include application-wide (fleet), device-wide (multiple services on +a device) and service-specific variables that apply to the selected application, +device or service. It can be thought of as including "inherited" variables; +for example, a service inherits device-wide variables, and a device inherits +application-wide variables. + +The printed output may include DEVICE and/or SERVICE columns to distinguish +between application-wide, device-specific and service-specific variables. +An asterisk in these columns indicates that the variable applies to +"all devices" or "all services". + The --config option is used to list "configuration variables" that control balena platform features, as opposed to custom environment variables defined by the user. The --config and the --service options are mutually exclusive because configuration variables cannot be set for specific services. -The --all option is used to include application-wide (fleet), device-wide -(multiple services on a device) and service-specific variables that apply to -the selected application, device or service. It can be thought of as including -"inherited" variables: for example, a service inherits device-wide variables, -and a device inherits application-wide variables. Variables are still filtered -out by type with the --config option, such that configuration and non- -configuration variables are never listed together. - -When the --all option is used, the printed output may include DEVICE and/or -SERVICE columns to distinguish between application-wide, device-specific and -service-specific variables. An asterisk in these columns indicates that the -variable applies to "all devices" or "all services". - The --json option is recommended when scripting the output of this command, because the JSON format is less likely to change and it better represents data types like lists and empty strings. The 'jq' utility may be helpful in shell @@ -761,21 +759,20 @@ by its owner). Examples: $ balena envs --application MyApp - $ balena envs --application MyApp --all --json + $ balena envs --application MyApp --json + $ balena envs --application MyApp --service MyService $ balena envs --application MyApp --service MyService - $ balena envs --application MyApp --all --service MyService $ balena envs --application MyApp --config $ balena envs --device 7cf02a6 - $ balena envs --device 7cf02a6 --all --json - $ balena envs --device 7cf02a6 --config --all --json - $ balena envs --device 7cf02a6 --all --service MyService + $ balena envs --device 7cf02a6 --json + $ balena envs --device 7cf02a6 --config --json + $ balena envs --device 7cf02a6 --service MyService ### Options #### --all -include app-wide, device-wide variables that apply to the selected device or service. -Variables are still filtered out by type with the --config option. +No-op since balena CLI v12.0.0. #### -a, --application APPLICATION @@ -1868,25 +1865,16 @@ secrets.json file exists in the balena directory (usually $HOME/.balena), this file will be used instead. DOCKERIGNORE AND GITIGNORE FILES -By default, both '.dockerignore' and '.gitignore' files are taken into account -in order to prevent files from being sent to the balenaCloud builder or Docker -or balenaEngine (balenaOS device). +The balena CLI will use a '.dockerignore' file (if any) at the source directory +in order to decide which source files to exclude from the "build context" sent +to balenaCloud, Docker or balenaEngine. Previous balena CLI releases (before +v12.0.0) also took '.gitignore' files into account, but this is no longer the +case. This allows files to be used for an image build even if they are listed +in '.gitignore'. -However, this behavior has been DEPRECATED and will change in an upcoming major -version release. The --nogitignore (-G) option should be used to enable the new -behavior already now. This option will cause the CLI to: - -* Disregard all '.gitignore' files at the source directory and subdirectories, - and consider only the '.dockerignore' file (if any) at the source directory. -* Consequently, allow files to be sent to balenaCloud / Docker / balenaEngine - even if they are listed in '.gitignore' files (a longstanding feature request). -* Use a new '.dockerignore' parser and filter library that improves compatibility - with "docker build" and fixes several issues (mainly on Windows). -* Prevent a warning message from being printed. - -When --nogitignore (-G) is provided, a few "hardcoded" dockerignore patterns are -also used and "merged" (in memory) with the patterns found in the '.dockerignore' -file (if any), in the following order: +A few "hardcoded" dockerignore patterns are also used and "merged" (in memory) +with the patterns found in the '.dockerignore' file (if any), in the following +order: **/.git < user's patterns from the '.dockerignore' file, if any > @@ -1982,14 +1970,15 @@ left hand side of the = character will be treated as the variable name. #### --convert-eol, -l -On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). -Source files are not modified. +No-op and deprecated since balena CLI v12.0.0 + +#### --noconvert-eol + +Don't convert line endings from CRLF (Windows format) to LF (Unix format). #### --nogitignore, -G -Disregard all .gitignore files, and consider only the .dockerignore file (if any) -at the source directory. This will be the default behavior in an upcoming major -version release. For more information, see 'balena help push'. +No-op and deprecated since balena CLI v12.0.0. See "balena help push". # Settings @@ -2076,25 +2065,16 @@ secrets.json file exists in the balena directory (usually $HOME/.balena), this file will be used instead. DOCKERIGNORE AND GITIGNORE FILES -By default, both '.dockerignore' and '.gitignore' files are taken into account -in order to prevent files from being sent to the balenaCloud builder or Docker -or balenaEngine (balenaOS device). +The balena CLI will use a '.dockerignore' file (if any) at the source directory +in order to decide which source files to exclude from the "build context" sent +to balenaCloud, Docker or balenaEngine. Previous balena CLI releases (before +v12.0.0) also took '.gitignore' files into account, but this is no longer the +case. This allows files to be used for an image build even if they are listed +in '.gitignore'. -However, this behavior has been DEPRECATED and will change in an upcoming major -version release. The --nogitignore (-G) option should be used to enable the new -behavior already now. This option will cause the CLI to: - -* Disregard all '.gitignore' files at the source directory and subdirectories, - and consider only the '.dockerignore' file (if any) at the source directory. -* Consequently, allow files to be sent to balenaCloud / Docker / balenaEngine - even if they are listed in '.gitignore' files (a longstanding feature request). -* Use a new '.dockerignore' parser and filter library that improves compatibility - with "docker build" and fixes several issues (mainly on Windows). -* Prevent a warning message from being printed. - -When --nogitignore (-G) is provided, a few "hardcoded" dockerignore patterns are -also used and "merged" (in memory) with the patterns found in the '.dockerignore' -file (if any), in the following order: +A few "hardcoded" dockerignore patterns are also used and "merged" (in memory) +with the patterns found in the '.dockerignore' file (if any), in the following +order: **/.git < user's patterns from the '.dockerignore' file, if any > @@ -2148,13 +2128,15 @@ Alternative Dockerfile name/path, relative to the source folder #### --logs -Display full log output +No-op and deprecated since balena CLI v12.0.0. Build logs are now shown by default. + +#### --nologs + +Hide the image build log output (produce less verbose output) #### --nogitignore, -G -Disregard all .gitignore files, and consider only the .dockerignore file (if any) -at the source directory. This will be the default behavior in an upcoming major -version release. For more information, see 'balena help undefined'. +No-op and deprecated since balena CLI v12.0.0. See "balena help undefined". #### --noparent-check @@ -2166,7 +2148,11 @@ Path to a YAML or JSON file with passwords for a private Docker registry #### --convert-eol, -l -On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified. +No-op and deprecated since balena CLI v12.0.0 + +#### --noconvert-eol + +Don't convert line endings from CRLF (Windows format) to LF (Unix format). #### --docker, -P <docker> @@ -2259,25 +2245,16 @@ secrets.json file exists in the balena directory (usually $HOME/.balena), this file will be used instead. DOCKERIGNORE AND GITIGNORE FILES -By default, both '.dockerignore' and '.gitignore' files are taken into account -in order to prevent files from being sent to the balenaCloud builder or Docker -or balenaEngine (balenaOS device). +The balena CLI will use a '.dockerignore' file (if any) at the source directory +in order to decide which source files to exclude from the "build context" sent +to balenaCloud, Docker or balenaEngine. Previous balena CLI releases (before +v12.0.0) also took '.gitignore' files into account, but this is no longer the +case. This allows files to be used for an image build even if they are listed +in '.gitignore'. -However, this behavior has been DEPRECATED and will change in an upcoming major -version release. The --nogitignore (-G) option should be used to enable the new -behavior already now. This option will cause the CLI to: - -* Disregard all '.gitignore' files at the source directory and subdirectories, - and consider only the '.dockerignore' file (if any) at the source directory. -* Consequently, allow files to be sent to balenaCloud / Docker / balenaEngine - even if they are listed in '.gitignore' files (a longstanding feature request). -* Use a new '.dockerignore' parser and filter library that improves compatibility - with "docker build" and fixes several issues (mainly on Windows). -* Prevent a warning message from being printed. - -When --nogitignore (-G) is provided, a few "hardcoded" dockerignore patterns are -also used and "merged" (in memory) with the patterns found in the '.dockerignore' -file (if any), in the following order: +A few "hardcoded" dockerignore patterns are also used and "merged" (in memory) +with the patterns found in the '.dockerignore' file (if any), in the following +order: **/.git < user's patterns from the '.dockerignore' file, if any > @@ -2327,13 +2304,15 @@ Alternative Dockerfile name/path, relative to the source folder #### --logs -Display full log output +No-op and deprecated since balena CLI v12.0.0. Build logs are now shown by default. + +#### --nologs + +Hide the image build log output (produce less verbose output) #### --nogitignore, -G -Disregard all .gitignore files, and consider only the .dockerignore file (if any) -at the source directory. This will be the default behavior in an upcoming major -version release. For more information, see 'balena help undefined'. +No-op and deprecated since balena CLI v12.0.0. See "balena help undefined". #### --noparent-check @@ -2345,7 +2324,11 @@ Path to a YAML or JSON file with passwords for a private Docker registry #### --convert-eol, -l -On Windows only, convert line endings from CRLF (Windows format) to LF (Unix format). Source files are not modified. +No-op and deprecated since balena CLI v12.0.0 + +#### --noconvert-eol + +Don't convert line endings from CRLF (Windows format) to LF (Unix format). #### --docker, -P <docker> diff --git a/lib/actions-oclif/apps.ts b/lib/actions-oclif/apps.ts index db58f48a..2a703aae 100644 --- a/lib/actions-oclif/apps.ts +++ b/lib/actions-oclif/apps.ts @@ -74,9 +74,9 @@ export default class AppsCmd extends Command { ); // Add extended properties - applications.forEach(application => { + applications.forEach((application) => { application.device_count = _.size(application.owns__device); - application.online_devices = _.sumBy(application.owns__device, d => + application.online_devices = _.sumBy(application.owns__device, (d) => d.is_online === true ? 1 : 0, ); }); diff --git a/lib/actions-oclif/device/public-url.ts b/lib/actions-oclif/device/public-url.ts index 0e913a73..bcffd0ec 100644 --- a/lib/actions-oclif/device/public-url.ts +++ b/lib/actions-oclif/device/public-url.ts @@ -60,13 +60,13 @@ export default class DevicePublicUrlCmd extends Command { { name: 'uuid', description: 'the uuid of the device to manage', - parse: dev => tryAsInteger(dev), + parse: (dev) => tryAsInteger(dev), required: true, }, { // Optional hidden arg to support old command format name: 'legacyUuid', - parse: dev => tryAsInteger(dev), + parse: (dev) => tryAsInteger(dev), hidden: true, }, ]; diff --git a/lib/actions-oclif/devices/supported.ts b/lib/actions-oclif/devices/supported.ts index b907c3a8..1bf0584b 100644 --- a/lib/actions-oclif/devices/supported.ts +++ b/lib/actions-oclif/devices/supported.ts @@ -80,7 +80,7 @@ export default class DevicesSupportedCmd extends Command { const { flags: options } = this.parse(DevicesSupportedCmd); let deviceTypes: Array> = await getBalenaSdk() .models.config.getDeviceTypes() - .map(d => { + .map((d) => { if (d.aliases && d.aliases.length) { // remove aliases that are equal to the slug d.aliases = d.aliases.filter((alias: string) => alias !== d.slug); @@ -95,7 +95,7 @@ export default class DevicesSupportedCmd extends Command { return d; }); if (!options.discontinued) { - deviceTypes = deviceTypes.filter(dt => dt.state !== 'DISCONTINUED'); + deviceTypes = deviceTypes.filter((dt) => dt.state !== 'DISCONTINUED'); } const fields = options.verbose ? ['slug', 'aliases', 'arch', 'state', 'name'] @@ -103,7 +103,7 @@ export default class DevicesSupportedCmd extends Command { ? ['slug', 'aliases', 'arch', 'name'] : ['slug', 'name']; deviceTypes = _.sortBy( - deviceTypes.map(d => _.pick(d, fields) as Partial), + deviceTypes.map((d) => _.pick(d, fields) as Partial), fields, ); if (options.json) { diff --git a/lib/actions-oclif/env/add.ts b/lib/actions-oclif/env/add.ts index d0f33084..0c7b659b 100644 --- a/lib/actions-oclif/env/add.ts +++ b/lib/actions-oclif/env/add.ts @@ -130,7 +130,7 @@ export default class EnvAddCmd extends Command { const balena = getBalenaSdk(); const reservedPrefixes = await getReservedPrefixes(balena); - const isConfigVar = _.some(reservedPrefixes, prefix => + const isConfigVar = _.some(reservedPrefixes, (prefix) => _.startsWith(params.name, prefix), ); diff --git a/lib/actions-oclif/env/rename.ts b/lib/actions-oclif/env/rename.ts index 506aaa0b..f0c9869b 100644 --- a/lib/actions-oclif/env/rename.ts +++ b/lib/actions-oclif/env/rename.ts @@ -61,7 +61,7 @@ export default class EnvRenameCmd extends Command { name: 'id', required: true, description: "variable's numeric database ID", - parse: input => parseAsInteger(input, 'id'), + parse: (input) => parseAsInteger(input, 'id'), }, { name: 'value', diff --git a/lib/actions-oclif/env/rm.ts b/lib/actions-oclif/env/rm.ts index 4a2bc17e..62d9f675 100644 --- a/lib/actions-oclif/env/rm.ts +++ b/lib/actions-oclif/env/rm.ts @@ -64,7 +64,7 @@ export default class EnvRmCmd extends Command { name: 'id', required: true, description: "variable's numeric database ID", - parse: input => parseAsInteger(input, 'id'), + parse: (input) => parseAsInteger(input, 'id'), }, ]; diff --git a/lib/actions-oclif/key/index.ts b/lib/actions-oclif/key/index.ts index 90bd744f..1fb3ebc3 100644 --- a/lib/actions-oclif/key/index.ts +++ b/lib/actions-oclif/key/index.ts @@ -45,7 +45,7 @@ export default class KeyCmd extends Command { { name: 'id', description: 'balenaCloud ID for the SSH key', - parse: x => parseAsInteger(x, 'id'), + parse: (x) => parseAsInteger(x, 'id'), required: true, }, ]; diff --git a/lib/actions-oclif/key/rm.ts b/lib/actions-oclif/key/rm.ts index e514b4a5..b2f13514 100644 --- a/lib/actions-oclif/key/rm.ts +++ b/lib/actions-oclif/key/rm.ts @@ -48,7 +48,7 @@ export default class KeyRmCmd extends Command { { name: 'id', description: 'balenaCloud ID for the SSH key', - parse: x => parseAsInteger(x, 'id'), + parse: (x) => parseAsInteger(x, 'id'), required: true, }, ]; diff --git a/lib/actions-oclif/keys.ts b/lib/actions-oclif/keys.ts index cf35ade8..e7f3d7f4 100644 --- a/lib/actions-oclif/keys.ts +++ b/lib/actions-oclif/keys.ts @@ -47,7 +47,7 @@ export default class KeysCmd extends Command { const keys = await getBalenaSdk().models.key.getAll(); // Use 'name' instead of 'title' to match dashboard. - const displayKeys: Array<{ id: number; name: string }> = keys.map(k => { + const displayKeys: Array<{ id: number; name: string }> = keys.map((k) => { return { id: k.id, name: k.title }; }); diff --git a/lib/actions-oclif/os/configure.ts b/lib/actions-oclif/os/configure.ts index 241f0fef..6699222b 100644 --- a/lib/actions-oclif/os/configure.ts +++ b/lib/actions-oclif/os/configure.ts @@ -261,7 +261,7 @@ export default class OsConfigureCmd extends Command { if (options['system-connection']) { const files = await Bluebird.map( options['system-connection'], - async filePath => { + async (filePath) => { const content = await fs.readFile(filePath, 'utf8'); const name = path.basename(filePath); @@ -485,7 +485,7 @@ function camelifyConfigOptions(options: FlagsDef): { [key: string]: any } { if (key.startsWith('config-')) { return key .substring('config-'.length) - .replace(/-[a-z]/g, match => match.substring(1).toUpperCase()); + .replace(/-[a-z]/g, (match) => match.substring(1).toUpperCase()); } return key; }); diff --git a/lib/actions/build.js b/lib/actions/build.js index a9b96f57..0954e031 100644 --- a/lib/actions/build.js +++ b/lib/actions/build.js @@ -33,10 +33,10 @@ Opts must be an object with the following keys: buildEmulated buildOpts: arguments to forward to docker build command */ -const buildProject = function(docker, logger, composeOpts, opts) { +const buildProject = function (docker, logger, composeOpts, opts) { const { loadProject } = require('../utils/compose_ts'); return Promise.resolve(loadProject(logger, composeOpts)) - .then(function(project) { + .then(function (project) { const appType = opts.app?.application_type?.[0]; if ( appType != null && @@ -65,7 +65,7 @@ const buildProject = function(docker, logger, composeOpts, opts) { composeOpts.nogitignore, ); }) - .then(function() { + .then(function () { logger.outputDeferredMessages(); logger.logSuccess('Build succeeded!'); }) @@ -154,7 +154,7 @@ Examples: const { application, arch, deviceType } = options; - return Promise.try(function() { + return Promise.try(function () { if ( (application == null && (arch == null || deviceType == null)) || (application != null && (arch != null || deviceType != null)) @@ -175,7 +175,7 @@ Examples: registrySecretsPath: options['registry-secrets'], }), ) - .then(function({ dockerfilePath, registrySecrets }) { + .then(function ({ dockerfilePath, registrySecrets }) { options.dockerfile = dockerfilePath; options['registry-secrets'] = registrySecrets; @@ -184,11 +184,11 @@ Examples: } else { return helpers .getAppWithArch(application) - .then(app => [app, app.arch, app.device_type]); + .then((app) => [app, app.arch, app.device_type]); } }) - .then(function([app, resolvedArch, resolvedDeviceType]) { + .then(function ([app, resolvedArch, resolvedDeviceType]) { return Promise.join( dockerUtils.getDocker(options), dockerUtils.generateBuildOpts(options), diff --git a/lib/actions/config.js b/lib/actions/config.js index 97faef9a..91070509 100644 --- a/lib/actions/config.js +++ b/lib/actions/config.js @@ -58,8 +58,8 @@ Examples: () => options.drive || getVisuals().drive('Select the device drive'), ) .tap(umountAsync) - .then(drive => config.read(drive, options.type)) - .tap(configJSON => { + .then((drive) => config.read(drive, options.type)) + .tap((configJSON) => { console.info(prettyjson.render(configJSON)); }); }, @@ -105,16 +105,16 @@ Examples: () => options.drive || getVisuals().drive('Select the device drive'), ) .tap(umountAsync) - .then(drive => + .then((drive) => config .read(drive, options.type) - .then(function(configJSON) { + .then(function (configJSON) { console.info(`Setting ${params.key} to ${params.value}`); _.set(configJSON, params.key, params.value); return configJSON; }) .tap(() => umountAsync(drive)) - .then(configJSON => config.write(drive, options.type, configJSON)), + .then((configJSON) => config.write(drive, options.type, configJSON)), ) .tap(() => { console.info('Done'); @@ -163,10 +163,10 @@ Examples: () => options.drive || getVisuals().drive('Select the device drive'), ) .tap(umountAsync) - .then(drive => + .then((drive) => readFileAsync(params.file, 'utf8') .then(JSON.parse) - .then(configJSON => config.write(drive, options.type, configJSON)), + .then((configJSON) => config.write(drive, options.type, configJSON)), ) .tap(() => { console.info('Done'); @@ -220,12 +220,12 @@ Examples: () => options.drive || getVisuals().drive('Select the device drive'), ) .tap(umountAsync) - .then(drive => + .then((drive) => config .read(drive, options.type) .get('uuid') .tap(() => umountAsync(drive)) - .then(function(uuid) { + .then(function (uuid) { let configureCommand = `os configure ${drive} --device ${uuid}`; if (options.advanced) { configureCommand += ' --advanced'; @@ -349,14 +349,14 @@ See the help page for examples: } return Promise.try( - /** @returns {Promise} */ function() { + /** @returns {Promise} */ function () { if (options.device != null) { return balena.models.device.get(options.device); } return balena.models.application.get(options.application); }, ) - .then(function(resource) { + .then(function (resource) { const deviceType = options.deviceType || resource.device_type; let manifestPromise = balena.models.device.getManifestBySlug( deviceType, @@ -367,8 +367,8 @@ See the help page for examples: const appManifestPromise = balena.models.device.getManifestBySlug( app.device_type, ); - manifestPromise = manifestPromise.tap(paramDeviceType => - appManifestPromise.then(function(appDeviceType) { + manifestPromise = manifestPromise.tap((paramDeviceType) => + appManifestPromise.then(function (appDeviceType) { if ( !helpers.areDeviceTypesCompatible( appDeviceType, @@ -391,7 +391,7 @@ See the help page for examples: // required option, that value is used (and the corresponding question is not asked) form.run(formOptions, { override: options }), ) - .then(function(answers) { + .then(function (answers) { answers.version = options.version; if (resource.uuid != null) { @@ -406,7 +406,7 @@ See the help page for examples: } }); }) - .then(function(config) { + .then(function (config) { if (options.output != null) { return writeFileAsync(options.output, JSON.stringify(config)); } diff --git a/lib/actions/deploy.js b/lib/actions/deploy.js index 52e6e111..7cada2dc 100644 --- a/lib/actions/deploy.js +++ b/lib/actions/deploy.js @@ -36,7 +36,7 @@ Opts must be an object with the following keys: buildEmulated buildOpts: arguments to forward to docker build command */ -const deployProject = function(docker, logger, composeOpts, opts) { +const deployProject = function (docker, logger, composeOpts, opts) { const _ = require('lodash'); const doodles = require('resin-doodles'); const sdk = getBalenaSdk(); @@ -46,7 +46,7 @@ const deployProject = function(docker, logger, composeOpts, opts) { } = require('../utils/compose_ts'); return Promise.resolve(loadProject(logger, composeOpts, opts.image)) - .then(function(project) { + .then(function (project) { if ( project.descriptors.length > 1 && !opts.app.application_type?.[0]?.supports_multicontainer @@ -58,7 +58,7 @@ const deployProject = function(docker, logger, composeOpts, opts) { // find which services use images that already exist locally return ( - Promise.map(project.descriptors, function(d) { + Promise.map(project.descriptors, function (d) { // unconditionally build (or pull) if explicitly requested if (opts.shouldPerformBuild) { return d; @@ -69,8 +69,8 @@ const deployProject = function(docker, logger, composeOpts, opts) { .return(d.serviceName) .catchReturn(); }) - .filter(d => !!d) - .then(function(servicesToSkip) { + .filter((d) => !!d) + .then(function (servicesToSkip) { // multibuild takes in a composition and always attempts to // build or pull all services. we workaround that here by // passing a modified composition. @@ -101,11 +101,11 @@ const deployProject = function(docker, logger, composeOpts, opts) { composeOpts.dockerfilePath, composeOpts.nogitignore, ) - .then(builtImages => _.keyBy(builtImages, 'serviceName')); + .then((builtImages) => _.keyBy(builtImages, 'serviceName')); }) - .then(builtImages => + .then((builtImages) => project.descriptors.map( - d => + (d) => builtImages[d.serviceName] ?? { serviceName: d.serviceName, name: typeof d.image === 'string' ? d.image : d.image.tag, @@ -115,7 +115,7 @@ const deployProject = function(docker, logger, composeOpts, opts) { ), ) // @ts-ignore slightly different return types of partial vs non-partial release - .then(function(images) { + .then(function (images) { if (opts.app.application_type?.[0]?.is_legacy) { const { deployLegacy } = require('../utils/deploy-legacy'); @@ -138,7 +138,7 @@ const deployProject = function(docker, logger, composeOpts, opts) { shouldUploadLogs: opts.shouldUploadLogs, }, deployLegacy, - ).then(releaseId => + ).then((releaseId) => // @ts-ignore releaseId should be inferred as a number because that's what deployLegacy is // typed as returning but the .js type-checking doesn't manage to infer it correctly due to // Promise.join typings @@ -165,7 +165,7 @@ const deployProject = function(docker, logger, composeOpts, opts) { }) ); }) - .then(function(release) { + .then(function (release) { logger.outputDeferredMessages(); logger.logSuccess('Deploy succeeded!'); logger.logSuccess(`Release: ${release.commit}`); @@ -263,7 +263,7 @@ Examples: appName = appName_raw || appName || options.application; delete options.application; - return Promise.try(function() { + return Promise.try(function () { if (appName == null) { throw new ExpectedError( 'Please specify the name of the application to deploy', @@ -276,10 +276,10 @@ Examples: ); } }) - .then(function() { + .then(function () { if (image) { return getRegistrySecrets(sdk, options['registry-secrets']).then( - registrySecrets => { + (registrySecrets) => { options['registry-secrets'] = registrySecrets; }, ); @@ -289,14 +289,14 @@ Examples: noParentCheck: options['noparent-check'] || false, projectPath: options.source || '.', registrySecretsPath: options['registry-secrets'], - }).then(function({ dockerfilePath, registrySecrets }) { + }).then(function ({ dockerfilePath, registrySecrets }) { options.dockerfile = dockerfilePath; options['registry-secrets'] = registrySecrets; }); } }) .then(() => helpers.getAppWithArch(appName)) - .then(function(app) { + .then(function (app) { return Promise.join( dockerUtils.getDocker(options), dockerUtils.generateBuildOpts(options), diff --git a/lib/actions/device.js b/lib/actions/device.js index 1f8d3995..e4413b2a 100644 --- a/lib/actions/device.js +++ b/lib/actions/device.js @@ -47,7 +47,7 @@ Examples: const Promise = require('bluebird'); const balena = getBalenaSdk(); - return Promise.try(function() { + return Promise.try(function () { if (options.application != null) { return balena.models.device.getAllByApplication( options.application, @@ -55,8 +55,8 @@ Examples: ); } return balena.models.device.getAll(expandForAppName); - }).tap(function(devices) { - devices = _.map(devices, function(device) { + }).tap(function (devices) { + devices = _.map(devices, function (device) { // @ts-ignore extending the device object with extra props device.dashboard_url = balena.models.device.getDashboardUrl( device.uuid, @@ -105,10 +105,10 @@ Examples: return balena.models.device .get(params.uuid, expandForAppName) - .then(device => + .then((device) => // @ts-ignore `device.getStatus` requires a device with service info, but // this device isn't typed with them, possibly needs fixing? - balena.models.device.getStatus(device).then(function(status) { + balena.models.device.getStatus(params.uuid).then(function (status) { device.status = status; // @ts-ignore extending the device object with extra props device.dashboard_url = balena.models.device.getDashboardUrl( @@ -173,7 +173,7 @@ Examples: return Promise.join( balena.models.application.get(params.application), options.uuid ?? balena.models.device.generateUniqueKey(), - function(application, uuid) { + function (application, uuid) { console.info(`Registering to ${application.app_name}: ${uuid}`); return balena.models.device.register(application.id, uuid); }, @@ -286,7 +286,7 @@ Examples: const balena = getBalenaSdk(); const form = require('resin-cli-form'); - return Promise.try(function() { + return Promise.try(function () { if (!_.isEmpty(params.newName)) { return params.newName; } @@ -321,7 +321,7 @@ Examples: return balena.models.device .get(params.uuid, expandForAppName) - .then(function(device) { + .then(function (device) { // @ts-ignore extending the device object with extra props device.application_name = device.belongs_to__application?.[0] ? device.belongs_to__application[0].app_name @@ -333,9 +333,9 @@ Examples: return Promise.all([ balena.models.device.getManifestBySlug(device.device_type), balena.models.config.getDeviceTypes(), - ]).then(function([deviceDeviceType, deviceTypes]) { + ]).then(function ([deviceDeviceType, deviceTypes]) { const compatibleDeviceTypes = deviceTypes.filter( - dt => + (dt) => balena.models.os.isArchitectureCompatibleWith( deviceDeviceType.arch, dt.arch, @@ -344,11 +344,11 @@ Examples: dt.state !== 'DISCONTINUED', ); - return patterns.selectApplication(application => + return patterns.selectApplication((application) => _.every([ _.some( compatibleDeviceTypes, - dt => dt.slug === application.device_type, + (dt) => dt.slug === application.device_type, ), // @ts-ignore using the extended device object prop device.application_name !== application.app_name, @@ -356,8 +356,8 @@ Examples: ); }); }) - .tap(application => balena.models.device.move(params.uuid, application)) - .then(application => { + .tap((application) => balena.models.device.move(params.uuid, application)) + .then((application) => { console.info(`${params.uuid} was moved to ${application}`); }); }, @@ -404,28 +404,28 @@ Examples: const patterns = require('../utils/patterns'); const { runCommand } = require('../utils/helpers'); - return Promise.try(function() { + return Promise.try(function () { if (options.application != null) { return options.application; } return patterns.selectApplication(); }) .then(balena.models.application.get) - .then(function(application) { + .then(function (application) { const download = () => tmpNameAsync() - .then(function(tempPath) { + .then(function (tempPath) { const osVersion = options['os-version'] || 'default'; return runCommand( `os download ${application.device_type} --output '${tempPath}' --version ${osVersion}`, ); }) - .disposer(tempPath => rimraf(tempPath)); + .disposer((tempPath) => rimraf(tempPath)); - return Promise.using(download(), tempPath => + return Promise.using(download(), (tempPath) => runCommand(`device register ${application.app_name}`) .then(balena.models.device.get) - .tap(function(device) { + .tap(function (device) { let configureCommand = `os configure '${tempPath}' --device ${device.uuid}`; if (options.config) { configureCommand += ` --config '${options.config}'`; @@ -433,7 +433,7 @@ Examples: configureCommand += ' --advanced'; } return runCommand(configureCommand) - .then(function() { + .then(function () { let osInitCommand = `os initialize '${tempPath}' --type ${application.device_type}`; if (options.yes) { osInitCommand += ' --yes'; @@ -443,13 +443,13 @@ Examples: } return runCommand(osInitCommand); }) - .catch(error => - balena.models.device.remove(device.uuid).finally(function() { + .catch((error) => + balena.models.device.remove(device.uuid).finally(function () { throw error; }), ); }), - ).then(function(device) { + ).then(function (device) { console.log('Done'); return device.uuid; }); diff --git a/lib/actions/device_ts.ts b/lib/actions/device_ts.ts index da30ee8a..fd94b1f5 100644 --- a/lib/actions/device_ts.ts +++ b/lib/actions/device_ts.ts @@ -100,7 +100,7 @@ export const osUpdate: CommandDefinition = { targetOsVersion = await form.ask({ message: 'Target OS version', type: 'list', - choices: hupVersionInfo.versions.map(version => ({ + choices: hupVersionInfo.versions.map((version) => ({ name: hupVersionInfo.recommended === version ? `${version} (recommended)` diff --git a/lib/actions/help.js b/lib/actions/help.js index 9b18bace..2d7c502e 100644 --- a/lib/actions/help.js +++ b/lib/actions/help.js @@ -23,8 +23,8 @@ import { getManualSortCompareFunction } from '../utils/helpers'; import { exitWithExpectedError } from '../errors'; import { getOclifHelpLinePairs } from './help_ts'; -const parse = object => - _.map(object, function(item) { +const parse = (object) => + _.map(object, function (item) { // Hacky way to determine if an object is // a function or a command let signature; @@ -37,12 +37,12 @@ const parse = object => return [signature, item.description]; }); -const indent = function(text) { - text = _.map(text.split('\n'), line => ' ' + line); +const indent = function (text) { + text = _.map(text.split('\n'), (line) => ' ' + line); return text.join('\n'); }; -const print = usageDescriptionPairs => +const print = (usageDescriptionPairs) => console.log( indent( columnify(_.fromPairs(usageDescriptionPairs), { @@ -71,7 +71,7 @@ const manuallySortedPrimaryCommands = [ 'local scan', ]; -const general = function(_params, options, done) { +const general = function (_params, options, done) { console.log('Usage: balena [COMMAND] [OPTIONS]\n'); console.log('Primary commands:\n'); @@ -79,10 +79,10 @@ const general = function(_params, options, done) { // We do not want the wildcard command // to be printed in the help screen. const commands = capitano.state.commands.filter( - command => !command.hidden && !command.isWildcard(), + (command) => !command.hidden && !command.isWildcard(), ); - const capitanoCommands = _.groupBy(commands, function(command) { + const capitanoCommands = _.groupBy(commands, function (command) { if (command.primary) { return 'primary'; } @@ -90,11 +90,11 @@ const general = function(_params, options, done) { }); return getOclifHelpLinePairs() - .then(function(oclifHelpLinePairs) { + .then(function (oclifHelpLinePairs) { const primaryHelpLinePairs = parse(capitanoCommands.primary) .concat(oclifHelpLinePairs.primary) .sort( - getManualSortCompareFunction(manuallySortedPrimaryCommands, function( + getManualSortCompareFunction(manuallySortedPrimaryCommands, function ( [signature], manualItem, ) { @@ -133,7 +133,7 @@ const general = function(_params, options, done) { }; const commandHelp = (params, _options, done) => - capitano.state.getMatchCommand(params.command, function(error, command) { + capitano.state.getMatchCommand(params.command, function (error, command) { if (error != null) { return done(error); } diff --git a/lib/actions/local/common.js b/lib/actions/local/common.js index 44476141..11d1d644 100644 --- a/lib/actions/local/common.js +++ b/lib/actions/local/common.js @@ -7,7 +7,7 @@ import { getChalk } from '../../utils/lazy'; export const dockerPort = 2375; export const dockerTimeout = 2000; -export const filterOutSupervisorContainer = function(container) { +export const filterOutSupervisorContainer = function (container) { for (const name of container.Names) { if ( name.includes('resin_supervisor') || @@ -19,7 +19,7 @@ export const filterOutSupervisorContainer = function(container) { return true; }; -export const selectContainerFromDevice = Promise.method(function( +export const selectContainerFromDevice = Promise.method(function ( deviceIp, filterSupervisor, ) { @@ -34,8 +34,8 @@ export const selectContainerFromDevice = Promise.method(function( }); // List all containers, including those not running - return docker.listContainers({ all: true }).then(function(containers) { - containers = containers.filter(function(container) { + return docker.listContainers({ all: true }).then(function (containers) { + containers = containers.filter(function (container) { if (!filterSupervisor) { return true; } @@ -48,7 +48,7 @@ export const selectContainerFromDevice = Promise.method(function( return form.ask({ message: 'Select a container', type: 'list', - choices: _.map(containers, function(container) { + choices: _.map(containers, function (container) { const containerName = container.Names?.[0] || 'Untitled'; const shortContainerId = ('' + container.Id).substr(0, 11); @@ -61,7 +61,7 @@ export const selectContainerFromDevice = Promise.method(function( }); }); -export const pipeContainerStream = Promise.method(function({ +export const pipeContainerStream = Promise.method(function ({ deviceIp, name, outStream, @@ -75,8 +75,8 @@ export const pipeContainerStream = Promise.method(function({ const container = docker.getContainer(name); return container .inspect() - .then(containerInfo => containerInfo?.State?.Running) - .then(isRunning => + .then((containerInfo) => containerInfo?.State?.Running) + .then((isRunning) => container.attach({ logs: !follow || !isRunning, stream: follow && isRunning, @@ -84,8 +84,8 @@ export const pipeContainerStream = Promise.method(function({ stderr: true, }), ) - .then(containerStream => containerStream.pipe(outStream)) - .catch(function(err) { + .then((containerStream) => containerStream.pipe(outStream)) + .catch(function (err) { err = '' + err.statusCode; if (err === '404') { return console.log( diff --git a/lib/actions/local/configure.js b/lib/actions/local/configure.js index 8a68508d..4a565e0e 100644 --- a/lib/actions/local/configure.js +++ b/lib/actions/local/configure.js @@ -17,7 +17,7 @@ limitations under the License. const BOOT_PARTITION = 1; const CONNECTIONS_FOLDER = '/system-connections'; -const getConfigurationSchema = function(connnectionFileName) { +const getConfigurationSchema = function (connnectionFileName) { if (connnectionFileName == null) { connnectionFileName = 'resin-wifi'; } @@ -72,7 +72,7 @@ const getConfigurationSchema = function(connnectionFileName) { }; }; -const inquirerOptions = data => [ +const inquirerOptions = (data) => [ { message: 'Network SSID', type: 'input', @@ -111,7 +111,7 @@ const inquirerOptions = data => [ }, ]; -const getConfiguration = function(data) { +const getConfiguration = function (data) { const _ = require('lodash'); const inquirer = require('inquirer'); @@ -121,7 +121,7 @@ const getConfiguration = function(data) { return inquirer .prompt(inquirerOptions(data)) - .then(answers => _.merge(data, answers)); + .then((answers) => _.merge(data, answers)); }; // Taken from https://goo.gl/kr1kCt @@ -154,7 +154,7 @@ method=auto\ * if the `resin-sample` exists it's reconfigured (legacy mode, will be removed eventually) * otherwise, the new file is created */ -const prepareConnectionFile = function(target) { +const prepareConnectionFile = function (target) { const _ = require('lodash'); const imagefs = require('resin-image-fs'); @@ -164,7 +164,7 @@ const prepareConnectionFile = function(target) { partition: BOOT_PARTITION, path: CONNECTIONS_FOLDER, }) - .then(function(files) { + .then(function (files) { // The required file already exists if (_.includes(files, 'resin-wifi')) { return null; @@ -211,12 +211,12 @@ const prepareConnectionFile = function(target) { ) .thenReturn(null); }) - .then(connectionFileName => getConfigurationSchema(connectionFileName)); + .then((connectionFileName) => getConfigurationSchema(connectionFileName)); }; -const removeHostname = function(schema) { +const removeHostname = function (schema) { const _ = require('lodash'); - schema.mapper = _.reject(schema.mapper, mapper => + schema.mapper = _.reject(schema.mapper, (mapper) => _.isEqual(Object.keys(mapper.template), ['hostname']), ); }; @@ -244,14 +244,14 @@ Examples: return prepareConnectionFile(params.target) .tap(() => - isMountedAsync(params.target).then(function(isMounted) { + isMountedAsync(params.target).then(function (isMounted) { if (!isMounted) { return; } return umountAsync(params.target); }), ) - .then(function(configurationSchema) { + .then(function (configurationSchema) { const dmOpts = {}; if (process.pkg) { // when running in a standalone pkg install, the 'denymount' @@ -261,11 +261,11 @@ Examples: 'denymount', ); } - const dmHandler = cb => + const dmHandler = (cb) => reconfix .readConfiguration(configurationSchema, params.target) .then(getConfiguration) - .then(function(answers) { + .then(function (answers) { if (!answers.hostname) { removeHostname(configurationSchema); } diff --git a/lib/actions/logs.ts b/lib/actions/logs.ts index 913a9d23..2267fcec 100644 --- a/lib/actions/logs.ts +++ b/lib/actions/logs.ts @@ -83,16 +83,16 @@ export const logs: CommandDefinition< }, ], primary: true, - async action(params, options, done) { + async action(params, options) { normalizeUuidProp(params); const balena = getBalenaSdk(); + const { ExpectedError } = await import('../errors'); const { serviceIdToName } = await import('../utils/cloud'); const { displayDeviceLogs, displayLogObject } = await import( '../utils/device/logs' ); const { validateIPAddress } = await import('../utils/validation'); const { checkLoggedIn } = await import('../utils/patterns'); - const { exitWithExpectedError } = await import('../errors'); const Logger = await import('../utils/logger'); const logger = Logger.getLogger(); @@ -136,15 +136,12 @@ export const logs: CommandDefinition< try { await deviceApi.ping(); } catch (e) { - exitWithExpectedError( - new Error( - `Cannot access local mode device at address ${params.uuidOrDevice}`, - ), + throw new ExpectedError( + `Cannot access local mode device at address ${params.uuidOrDevice}`, ); } - const logStream = await deviceApi.getLogStream(); - displayDeviceLogs( + await displayDeviceLogs( logStream, logger, options.system || false, @@ -153,18 +150,19 @@ export const logs: CommandDefinition< } else { await checkLoggedIn(); if (options.tail) { - return balena.logs - .subscribe(params.uuidOrDevice, { count: 100 }) - .then(function(logStream) { - logStream.on('line', displayCloudLog); - logStream.on('error', done); - }) - .catch(done); + const logStream = await balena.logs.subscribe(params.uuidOrDevice, { + count: 100, + }); + // Never resolve (quit with CTRL-C), but reject on a broken connection + await new Promise((_resolve, reject) => { + logStream.on('line', displayCloudLog); + logStream.on('error', reject); + }); } else { - return balena.logs - .history(params.uuidOrDevice) - .each(displayCloudLog) - .catch(done); + const logMessages = await balena.logs.history(params.uuidOrDevice); + for (const logMessage of logMessages) { + await displayCloudLog(logMessage); + } } } }, diff --git a/lib/actions/os.js b/lib/actions/os.js index eac6a499..c9d7a250 100644 --- a/lib/actions/os.js +++ b/lib/actions/os.js @@ -19,7 +19,7 @@ import * as commandOptions from './command-options'; import * as _ from 'lodash'; import { getBalenaSdk, getVisuals } from '../utils/lazy'; -const formatVersion = function(v, isRecommended) { +const formatVersion = function (v, isRecommended) { let result = `v${v}`; if (isRecommended) { result += ' (recommended)'; @@ -27,7 +27,7 @@ const formatVersion = function(v, isRecommended) { return result; }; -const resolveVersion = function(deviceType, version) { +const resolveVersion = function (deviceType, version) { if (version !== 'menu') { if (version[0] === 'v') { version = version.slice(1); @@ -40,8 +40,8 @@ const resolveVersion = function(deviceType, version) { return balena.models.os .getSupportedVersions(deviceType) - .then(function({ versions: vs, recommended }) { - const choices = vs.map(v => ({ + .then(function ({ versions: vs, recommended }) { + const choices = vs.map((v) => ({ value: v, name: formatVersion(v, v === recommended), })); @@ -72,7 +72,7 @@ Example: return balena.models.os .getSupportedVersions(params.type) .then(({ versions: vs, recommended }) => { - vs.forEach(v => { + vs.forEach((v) => { console.log(formatVersion(v, v === recommended)); }); }); @@ -123,7 +123,7 @@ Examples: console.info(`Getting device operating system for ${params.type}`); let displayVersion = ''; - return Promise.try(function() { + return Promise.try(function () { if (!options.version) { console.warn(`OS version is not specified, using the default version: \ the newest stable (non-pre-release) version if available, \ @@ -133,13 +133,13 @@ versions for the given device type are pre-release).`); } return resolveVersion(params.type, options.version); }) - .then(function(version) { + .then(function (version) { if (version !== 'default') { displayVersion = ` ${version}`; } return manager.get(params.type, version); }) - .then(function(stream) { + .then(function (stream) { const visuals = getVisuals(); const bar = new visuals.Progress( `Downloading Device OS${displayVersion}`, @@ -148,7 +148,7 @@ versions for the given device type are pre-release).`); `Downloading Device OS${displayVersion} (size unknown)`, ); - stream.on('progress', function(state) { + stream.on('progress', function (state) { if (state != null) { return bar.update(state); } else { @@ -178,7 +178,7 @@ versions for the given device type are pre-release).`); }, }; -const buildConfigForDeviceType = function(deviceType, advanced) { +const buildConfigForDeviceType = function (deviceType, advanced) { if (advanced == null) { advanced = false; } @@ -201,7 +201,7 @@ const buildConfigForDeviceType = function(deviceType, advanced) { return form.run(questions, { override }); }; -const $buildConfig = function(image, deviceTypeSlug, advanced) { +const $buildConfig = function (image, deviceTypeSlug, advanced) { if (advanced == null) { advanced = false; } @@ -210,7 +210,7 @@ const $buildConfig = function(image, deviceTypeSlug, advanced) { return Promise.resolve( helpers.getManifest(image, deviceTypeSlug), - ).then(deviceTypeManifest => + ).then((deviceTypeManifest) => buildConfigForDeviceType(deviceTypeManifest, advanced), ); }; @@ -246,7 +246,7 @@ Example: params.image, params['device-type'], options.advanced, - ).then(answers => + ).then((answers) => writeFileAsync(options.output, JSON.stringify(answers, null, 4)), ); }, @@ -295,14 +295,14 @@ Initializing device ${INIT_WARNING_MESSAGE}\ `); return Promise.resolve(helpers.getManifest(params.image, options.type)) - .then(manifest => + .then((manifest) => form.run(manifest.initialization?.options, { override: { drive: options.drive, }, }), ) - .tap(function(answers) { + .tap(function (answers) { if (answers.drive == null) { return; } @@ -316,7 +316,7 @@ ${INIT_WARNING_MESSAGE}\ .return(answers.drive) .then(umountAsync); }) - .tap(answers => + .tap((answers) => helpers.sudo([ 'internal', 'osinit', @@ -325,7 +325,7 @@ ${INIT_WARNING_MESSAGE}\ JSON.stringify(answers), ]), ) - .then(function(answers) { + .then(function (answers) { if (answers.drive == null) { return; } diff --git a/lib/actions/preload.js b/lib/actions/preload.js index 6b1beb52..4abd2da3 100644 --- a/lib/actions/preload.js +++ b/lib/actions/preload.js @@ -18,10 +18,10 @@ import * as _ from 'lodash'; import { getBalenaSdk, getVisuals } from '../utils/lazy'; import * as dockerUtils from '../utils/docker'; -const isCurrent = commit => commit === 'latest' || commit === 'current'; +const isCurrent = (commit) => commit === 'latest' || commit === 'current'; let allDeviceTypes; -const getDeviceTypes = function() { +const getDeviceTypes = function () { const Bluebird = require('bluebird'); if (allDeviceTypes !== undefined) { return Bluebird.resolve(allDeviceTypes); @@ -29,27 +29,24 @@ const getDeviceTypes = function() { const balena = getBalenaSdk(); return balena.models.config .getDeviceTypes() - .then(deviceTypes => _.sortBy(deviceTypes, 'name')) - .tap(dt => { + .then((deviceTypes) => _.sortBy(deviceTypes, 'name')) + .tap((dt) => { allDeviceTypes = dt; }); }; -const getDeviceTypesWithSameArch = function(deviceTypeSlug) { - return getDeviceTypes().then(function(deviceTypes) { +const getDeviceTypesWithSameArch = function (deviceTypeSlug) { + return getDeviceTypes().then(function (deviceTypes) { const deviceType = _.find(deviceTypes, { slug: deviceTypeSlug }); - return _(deviceTypes) - .filter({ arch: deviceType.arch }) - .map('slug') - .value(); + return _(deviceTypes).filter({ arch: deviceType.arch }).map('slug').value(); }); }; -const getApplicationsWithSuccessfulBuilds = function(deviceType) { +const getApplicationsWithSuccessfulBuilds = function (deviceType) { const balenaPreload = require('balena-preload'); const balena = getBalenaSdk(); - return getDeviceTypesWithSameArch(deviceType).then(deviceTypes => { + return getDeviceTypesWithSameArch(deviceType).then((deviceTypes) => { /** @type {import('balena-sdk').PineOptionsFor} */ const options = { $filter: { @@ -84,7 +81,7 @@ const getApplicationsWithSuccessfulBuilds = function(deviceType) { }); }; -const selectApplication = function(deviceType) { +const selectApplication = function (deviceType) { const visuals = getVisuals(); const form = require('resin-cli-form'); const { exitWithExpectedError } = require('../errors'); @@ -94,7 +91,7 @@ const selectApplication = function(deviceType) { ); applicationInfoSpinner.start(); - return getApplicationsWithSuccessfulBuilds(deviceType).then(function( + return getApplicationsWithSuccessfulBuilds(deviceType).then(function ( applications, ) { applicationInfoSpinner.stop(); @@ -106,7 +103,7 @@ const selectApplication = function(deviceType) { return form.ask({ message: 'Select an application', type: 'list', - choices: applications.map(app => ({ + choices: applications.map((app) => ({ name: app.app_name, value: app, })), @@ -114,7 +111,7 @@ const selectApplication = function(deviceType) { }); }; -const selectApplicationCommit = function(releases) { +const selectApplicationCommit = function (releases) { const form = require('resin-cli-form'); const { exitWithExpectedError } = require('../errors'); @@ -123,7 +120,7 @@ const selectApplicationCommit = function(releases) { } const DEFAULT_CHOICE = { name: 'current', value: 'current' }; const choices = [DEFAULT_CHOICE].concat( - releases.map(release => ({ + releases.map((release) => ({ name: `${release.end_timestamp} - ${release.commit}`, value: release.commit, })), @@ -136,7 +133,7 @@ const selectApplicationCommit = function(releases) { }); }; -const offerToDisableAutomaticUpdates = function( +const offerToDisableAutomaticUpdates = function ( application, commit, pinDevice, @@ -170,7 +167,7 @@ Alternatively you can pass the --pin-device-to-release flag to pin only this dev message, type: 'confirm', }) - .then(function(update) { + .then(function (update) { if (!update) { return; } @@ -268,7 +265,7 @@ Examples: const progressBars = {}; - const progressHandler = function(event) { + const progressHandler = function (event) { let progressBar = progressBars[event.name]; if (!progressBar) { progressBar = progressBars[event.name] = new visuals.Progress( @@ -280,7 +277,7 @@ Examples: const spinners = {}; - const spinnerHandler = function(event) { + const spinnerHandler = function (event) { let spinner = spinners[event.name]; if (!spinner) { spinner = spinners[event.name] = new visuals.Spinner(event.name); @@ -326,7 +323,7 @@ Examples: } // Get a configured dockerode instance - return dockerUtils.getDocker(options).then(function(docker) { + return dockerUtils.getDocker(options).then(function (docker) { const preloader = new balenaPreload.Preloader( balena, docker, @@ -342,7 +339,7 @@ Examples: let gotSignal = false; - nodeCleanup(function(_exitCode, signal) { + nodeCleanup(function (_exitCode, signal) { if (signal) { gotSignal = true; nodeCleanup.uninstall(); // don't call cleanup handler again @@ -361,7 +358,7 @@ Examples: preloader.on('progress', progressHandler); preloader.on('spinner', spinnerHandler); - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { preloader.on('error', reject); return preloader @@ -371,7 +368,7 @@ Examples: if (!preloader.appId) { return selectApplication( preloader.config.deviceType, - ).then(application => preloader.setApplication(application)); + ).then((application) => preloader.setApplication(application)); } }) .then(() => { @@ -381,7 +378,7 @@ Examples: // handle `--commit current` (and its `--commit latest` synonym) return 'latest'; } - const release = _.find(preloader.application.owns__release, r => + const release = _.find(preloader.application.owns__release, (r) => r.commit.startsWith(options.commit), ); if (!release) { @@ -393,7 +390,7 @@ Examples: } return selectApplicationCommit(preloader.application.owns__release); }) - .then(function(commit) { + .then(function (commit) { if (isCurrent(commit)) { preloader.commit = preloader.application.commit; } else { @@ -416,7 +413,7 @@ Examples: .catch(reject); }) .then(done) - .finally(function() { + .finally(function () { if (!gotSignal) { return preloader.cleanup(); } diff --git a/lib/actions/push.ts b/lib/actions/push.ts index 809f458f..296624f4 100644 --- a/lib/actions/push.ts +++ b/lib/actions/push.ts @@ -80,7 +80,7 @@ async function getAppOwner(sdk: BalenaSDK, appName: string) { // user has access to a collab app with the same name as a personal app. We // present a list to the user which shows the fully qualified application // name (user/appname) and allows them to select - const entries = _.map(applications, app => { + const entries = _.map(applications, (app) => { const username = _.get(app, 'user[0].username'); return { name: `${username}/${appName}`, @@ -406,7 +406,7 @@ export const push: CommandDefinition< : options.env || [], convertEol, }), - ).catch(BuildError, e => { + ).catch(BuildError, (e) => { throw new ExpectedError(e.toString()); }); break; diff --git a/lib/actions/ssh.ts b/lib/actions/ssh.ts index 452afd33..c987bbf8 100644 --- a/lib/actions/ssh.ts +++ b/lib/actions/ssh.ts @@ -101,7 +101,7 @@ async function getContainerId( }); const containers = await new Promise((resolve, reject) => { const output: string[] = []; - subprocess.stdout.on('data', chunk => output.push(chunk.toString())); + subprocess.stdout.on('data', (chunk) => output.push(chunk.toString())); subprocess.on('close', (code: number) => { if (code !== 0) { reject( diff --git a/lib/actions/tunnel.ts b/lib/actions/tunnel.ts index bab3ec83..6946e8b2 100644 --- a/lib/actions/tunnel.ts +++ b/lib/actions/tunnel.ts @@ -130,7 +130,7 @@ export const tunnel: CommandDefinition = { logger.logInfo(`Opening a tunnel to ${device.uuid}...`); const localListeners = _.chain(ports) - .map(mapping => { + .map((mapping) => { const regexResult = /^([0-9]+)(?:$|\:(?:([\w\:\.]+)\:|)([0-9]+))$/.exec( mapping, ); @@ -168,7 +168,7 @@ export const tunnel: CommandDefinition = { }) .map(({ localPort, localAddress, remotePort }) => { return tunnelConnectionToDevice(device.uuid, remotePort, sdk) - .then(handler => + .then((handler) => createServer((client: Socket) => { return handler(client) .then(() => { @@ -181,7 +181,7 @@ export const tunnel: CommandDefinition = { remotePort, ); }) - .catch(err => + .catch((err) => logConnection( client.remoteAddress || '', client.remotePort || 0, @@ -195,7 +195,7 @@ export const tunnel: CommandDefinition = { }), ) .then( - server => + (server) => new Bluebird.Promise((resolve, reject) => { server.on('error', reject); server.listen(localPort, localAddress, () => { diff --git a/lib/app-capitano.js b/lib/app-capitano.js index 3156a8f1..5d87d6f2 100644 --- a/lib/app-capitano.js +++ b/lib/app-capitano.js @@ -20,10 +20,8 @@ import * as capitano from 'capitano'; import * as actions from './actions'; import * as events from './events'; -capitano.permission('user', done => - require('./utils/patterns') - .checkLoggedIn() - .then(done, done), +capitano.permission('user', (done) => + require('./utils/patterns').checkLoggedIn().then(done, done), ); capitano.command({ @@ -108,7 +106,7 @@ capitano.command(actions.push.push); export function run(argv) { const cli = capitano.parse(argv.slice(2)); - const runCommand = function() { + const runCommand = function () { const capitanoExecuteAsync = Promise.promisify(capitano.execute); if (cli.global?.help) { return capitanoExecuteAsync({ @@ -119,11 +117,11 @@ export function run(argv) { } }; - const trackCommand = function() { + const trackCommand = function () { const getMatchCommandAsync = Promise.promisify( capitano.state.getMatchCommand, ); - return getMatchCommandAsync(cli.command).then(function(command) { + return getMatchCommandAsync(cli.command).then(function (command) { // cmdSignature is literally a string like, for example: // "push " // ("applicationOrDevice" is NOT replaced with its actual value) diff --git a/lib/app-common.ts b/lib/app-common.ts index 614b1c9f..86e06fc5 100644 --- a/lib/app-common.ts +++ b/lib/app-common.ts @@ -54,7 +54,7 @@ export const setupSentry = onceAsync(async () => { dsn: config.sentryDsn, release: packageJSON.version, }); - Sentry.configureScope(scope => { + Sentry.configureScope((scope) => { scope.setExtras({ is_pkg: !!(process as any).pkg, node_version: process.version, @@ -162,12 +162,21 @@ async function setupGlobalAgentProxy( privateNoProxy.push(`172.${i}.*`); } + // BALENARC_DO_PROXY is a list of entries to exclude from BALENARC_NO_PROXY + // (so "do proxy" takes precedence over "no proxy"). It is an undocumented + // feature/hack added to facilitate testing of the CLI's standalone executable + // through a local proxy server, by setting BALENARC_DO_PROXY="localhost,127.0.0.1" + // See also runCommandInSubprocess() function in `tests/helpers.ts`. + const doProxy = (process.env.BALENARC_DO_PROXY || '').split(','); + const env = process.env; env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE = ''; env.NO_PROXY = [ ...requiredNoProxy, - ...(noProxy ? noProxy.split(',').filter(v => v) : privateNoProxy), - ].join(','); + ...(noProxy ? noProxy.split(',').filter((v) => v) : privateNoProxy), + ] + .filter((i) => !doProxy.includes(i)) + .join(','); if (proxy) { const proxyUrl: string = diff --git a/lib/app-oclif.ts b/lib/app-oclif.ts index 8156ce8f..88dc24f7 100644 --- a/lib/app-oclif.ts +++ b/lib/app-oclif.ts @@ -42,7 +42,7 @@ export async function run(command: string[], options: AppOptions) { return require('@oclif/command/flush'); } }, - error => { + (error) => { // oclif sometimes exits with ExitError code 0 (not an error) // (Avoid `error instanceof ExitError` here for the reasons explained // in the CONTRIBUTING.md file regarding the `instanceof` operator.) diff --git a/lib/auth/server.ts b/lib/auth/server.ts index 5e2b5d50..2d37c1b7 100644 --- a/lib/auth/server.ts +++ b/lib/auth/server.ts @@ -36,7 +36,7 @@ const createServer = ({ port }: { port: number }) => { app.set('views', path.join(__dirname, 'pages')); const server = app.listen(port); - server.on('connection', socket => serverSockets.push(socket)); + server.on('connection', (socket) => serverSockets.push(socket)); return { app, server }; }; @@ -55,7 +55,7 @@ const createServer = ({ port }: { port: number }) => { * https://github.com/nodejs/node-v0.x-archive/issues/9066 */ export function shutdownServer() { - serverSockets.forEach(s => s.unref()); + serverSockets.forEach((s) => s.unref()); serverSockets.splice(0); } diff --git a/lib/auth/utils.ts b/lib/auth/utils.ts index 674eaf31..82bcb6be 100644 --- a/lib/auth/utils.ts +++ b/lib/auth/utils.ts @@ -39,7 +39,7 @@ export const getDashboardLoginURL = (callbackUrl: string) => { return getBalenaSdk() .settings.get('dashboardUrl') - .then(dashboardUrl => + .then((dashboardUrl) => url.resolve(dashboardUrl, `/login/cli/${callbackUrl}`), ); }; @@ -73,12 +73,12 @@ export const loginIfTokenValid = (token: string) => { return balena.auth .getToken() .catchReturn(undefined) - .then(currentToken => + .then((currentToken) => balena.auth .loginWithToken(token) .return(token) .then(balena.auth.isLoggedIn) - .tap(isLoggedIn => { + .tap((isLoggedIn) => { if (isLoggedIn) { return; } diff --git a/lib/errors.ts b/lib/errors.ts index 3f39136b..21624b22 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -94,9 +94,9 @@ function interpret(error: Error): string { const messages: { [key: string]: (error: Error & { path?: string }) => string; } = { - EISDIR: error => `File is a directory: ${error.path}`, + EISDIR: (error) => `File is a directory: ${error.path}`, - ENOENT: error => `No such file or directory: ${error.path}`, + ENOENT: (error) => `No such file or directory: ${error.path}`, ENOGIT: () => stripIndent` Git is not installed on this system. @@ -112,7 +112,7 @@ const messages: { If this is not the case, and you're trying to burn an SDCard, check that the write lock is not set.`, - EACCES: e => messages.EPERM(e), + EACCES: (e) => messages.EPERM(e), ETIMEDOUT: () => 'Oops something went wrong, please check your connection and try again.', @@ -130,9 +130,8 @@ const messages: { `, BalenaExpiredToken: () => stripIndent` - Looks like your session token has expired. - Please try logging in again with: - $ balena login`, + Looks like the session token has expired. + Try logging in again with the "balena login" command.`, }; const EXPECTED_ERROR_REGEXES = [ @@ -167,8 +166,8 @@ export async function handleError(error: Error) { // Expected? const isExpectedError = error instanceof ExpectedError || - EXPECTED_ERROR_REGEXES.some(re => re.test(message[0])) || - EXPECTED_ERROR_REGEXES.some(re => re.test((error as BalenaError).code)); + EXPECTED_ERROR_REGEXES.some((re) => re.test(message[0])) || + EXPECTED_ERROR_REGEXES.some((re) => re.test((error as BalenaError).code)); // Output/report error if (isExpectedError) { @@ -199,7 +198,7 @@ export function printErrorMessage(message: string) { const messageLines = message.split('\n'); console.error(chalk.red(messageLines.shift())); - messageLines.forEach(line => { + messageLines.forEach((line) => { console.error(line); }); diff --git a/lib/events.ts b/lib/events.ts index 3b890e6e..5087ca56 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -45,7 +45,7 @@ const getMixpanel = _.once((balenaUrl: string) => { */ export async function trackCommand(commandSignature: string) { const Sentry = await import('@sentry/node'); - Sentry.configureScope(scope => { + Sentry.configureScope((scope) => { scope.setExtra('command', commandSignature); }); const balena = getBalenaSdk(); @@ -56,7 +56,7 @@ export async function trackCommand(commandSignature: string) { mixpanel: balenaUrlPromise.then(getMixpanel), }) .then(({ username, balenaUrl, mixpanel }) => { - Sentry.configureScope(scope => { + Sentry.configureScope((scope) => { scope.setUser({ id: username, username, diff --git a/lib/hooks/prerun/track.ts b/lib/hooks/prerun/track.ts index d5863ea4..c1c0f26a 100644 --- a/lib/hooks/prerun/track.ts +++ b/lib/hooks/prerun/track.ts @@ -19,7 +19,7 @@ import { Hook } from '@oclif/config'; let trackResolve: (result: Promise) => void; // note: trackPromise is subject to a Bluebird.timeout, defined in events.ts -export const trackPromise = new Promise(resolve => { +export const trackPromise = new Promise((resolve) => { trackResolve = resolve; }); @@ -34,7 +34,7 @@ export const trackPromise = new Promise(resolve => { * A command signature is something like "env add NAME [VALUE]". That's * literally so: 'NAME' and 'VALUE' are NOT replaced with actual values. */ -const hook: Hook<'prerun'> = async function(options) { +const hook: Hook<'prerun'> = async function (options) { const events = await import('../../events'); const usage: string | string[] | undefined = options.Command.usage; const cmdSignature = diff --git a/lib/preparser.ts b/lib/preparser.ts index cfb17633..e2217c97 100644 --- a/lib/preparser.ts +++ b/lib/preparser.ts @@ -75,11 +75,9 @@ export async function routeCliFramework(argv: string[], options: AppOptions) { } if (process.env.DEBUG) { console.log( - `[debug] new argv=[${[ - argv[0], - argv[1], - ...oclifArgs, - ]}] length=${oclifArgs.length + 2}`, + `[debug] new argv=[${[argv[0], argv[1], ...oclifArgs]}] length=${ + oclifArgs.length + 2 + }`, ); } await (await import('./app-oclif')).run(oclifArgs, options); diff --git a/lib/utils/compose.js b/lib/utils/compose.js index 5ba03b89..6d90bbf0 100644 --- a/lib/utils/compose.js +++ b/lib/utils/compose.js @@ -21,7 +21,7 @@ import * as path from 'path'; import { getChalk } from './lazy'; -export const appendProjectOptions = opts => +export const appendProjectOptions = (opts) => opts.concat([ { signature: 'projectName', @@ -119,7 +119,7 @@ Source files are not modified.`, export function generateOpts(options) { const fs = require('mz/fs'); const { isV12 } = require('./version'); - return fs.realpath(options.source || '.').then(projectPath => ({ + return fs.realpath(options.source || '.').then((projectPath) => ({ projectName: options.projectName, projectPath, inlineLogs: !options.nologs && (!!options.logs || isV12()), @@ -152,7 +152,7 @@ export function createProject(composePath, composeStr, projectName = null) { if (projectName == null) { projectName = path.basename(composePath); } - const descriptors = compose.parse(composition).map(function(descr) { + const descriptors = compose.parse(composition).map(function (descr) { // generate an image name based on the project and service names // if one is not given and the service requires a build if ( @@ -216,7 +216,7 @@ function originalTarDirectory(dir, param) { let readFile; if (process.platform === 'win32') { const { readFileWithEolConversion } = require('./eol-conversion'); - readFile = file => readFileWithEolConversion(file, convertEol); + readFile = (file) => readFileWithEolConversion(file, convertEol); } else { ({ readFile } = fs); } @@ -224,14 +224,14 @@ function originalTarDirectory(dir, param) { const getFiles = () => // @ts-ignore `klaw` returns a `Walker` which is close enough to a stream to work but ts complains Promise.resolve(streamToPromise(klaw(dir))) - .filter(item => !item.stats.isDirectory()) - .map(item => item.path); + .filter((item) => !item.stats.isDirectory()) + .map((item) => item.path); const ignore = new FileIgnorer(dir); const pack = tar.pack(); const ignoreFiles = {}; return getFiles() - .each(function(file) { + .each(function (file) { const type = ignore.getIgnoreFileType(path.relative(dir, file)); if (type != null) { ignoreFiles[type] = ignoreFiles[type] || []; @@ -248,7 +248,7 @@ function originalTarDirectory(dir, param) { } }) .filter(ignore.filter) - .map(function(file) { + .map(function (file) { const relPath = path.relative(path.resolve(dir), file); return Promise.join( relPath, @@ -267,7 +267,7 @@ function originalTarDirectory(dir, param) { ); }) .then(() => preFinalizeCallback?.(pack)) - .then(function() { + .then(function () { pack.finalize(); return pack; }); @@ -278,7 +278,7 @@ function originalTarDirectory(dir, param) { * @param {number} len * @returns {string} */ -const truncateString = function(str, len) { +const truncateString = function (str, len) { if (str.length < len) { return str; } @@ -341,13 +341,13 @@ export function buildProject( return Promise.resolve(checkBuildSecretsRequirements(docker, projectPath)) .then(() => qemu.installQemuIfNeeded(emulated, logger, arch, docker)) - .tap(function(needsQemu) { + .tap(function (needsQemu) { if (!needsQemu) { return; } logger.logInfo('Emulation is enabled'); // Copy qemu into all build contexts - return Promise.map(imageDescriptors, function(d) { + return Promise.map(imageDescriptors, function (d) { if (typeof d.image === 'string' || d.image.context == null) { return; } @@ -359,7 +359,7 @@ export function buildProject( needsQemu, // Tar up the directory, ready for the build stream ) => tarDirectory(projectPath, { convertEol, nogitignore }) - .then(tarStream => + .then((tarStream) => makeBuildTasks( composition, tarStream, @@ -368,7 +368,7 @@ export function buildProject( projectName, ), ) - .map(function(/** @type {any} */ task) { + .map(function (/** @type {any} */ task) { const d = imageDescriptorsByServiceName[task.serviceName]; // multibuild parses the composition internally so any tags we've @@ -428,7 +428,7 @@ export function buildProject( .return([task, binPath]); }), ) - .map(function([task, qemuPath]) { + .map(function ([task, qemuPath]) { const captureStream = buildLogCapture(task.external, task.logBuffer); if (task.external) { @@ -437,7 +437,7 @@ export function buildProject( captureStream.pipe(task.logStream); task.progressHook = pullProgressAdapter(captureStream); } else { - task.streamHook = function(stream) { + task.streamHook = function (stream) { let rawStream; stream = createLogStream(stream); if (qemuPath != null) { @@ -461,11 +461,11 @@ export function buildProject( } return task; }) - .then(function(tasks) { + .then(function (tasks) { logger.logDebug('Prepared tasks; building...'); return Promise.map( builder.performBuilds(tasks, docker, BALENA_ENGINE_TMP_PATH), - function(builtImage) { + function (builtImage) { if (!builtImage.successful) { /** @type {Error & {serviceName?: string}} */ const error = builtImage.error ?? new Error(); @@ -499,12 +499,12 @@ export function buildProject( .getImage(image.name) .inspect() .get('Size') - .then(size => { + .then((size) => { image.props.size = size; }) .return(image); }, - ).tap(function(images) { + ).tap(function (images) { const summary = _(images) .map(({ serviceName, props }) => [ serviceName, @@ -526,7 +526,7 @@ export function buildProject( * @param {import('resin-compose-parse').Composition} composition * @returns {Promise} */ -export const createRelease = function( +export const createRelease = function ( apiEndpoint, auth, userId, @@ -546,12 +546,9 @@ export const createRelease = function( application: appId, composition, source: 'local', - commit: crypto - .pseudoRandomBytes(16) - .toString('hex') - .toLowerCase(), + commit: crypto.pseudoRandomBytes(16).toString('hex').toLowerCase(), }) - .then(function({ release, serviceImages }) { + .then(function ({ release, serviceImages }) { return { client, release: _.omit(release, [ @@ -560,7 +557,7 @@ export const createRelease = function( 'is_created_by__user', '__metadata', ]), - serviceImages: _.mapValues(serviceImages, serviceImage => + serviceImages: _.mapValues(serviceImages, (serviceImage) => _.omit(serviceImage, [ 'created_at', 'is_a_build_of__service', @@ -579,7 +576,7 @@ export const createRelease = function( * @returns {Promise>} */ export const tagServiceImages = (docker, images, serviceImages) => - Promise.map(images, function(d) { + Promise.map(images, function (d) { const serviceImage = serviceImages[d.serviceName]; const imageName = serviceImage.is_stored_at__image_location; const match = /(.*?)\/(.*?)(?::([^/]*))?$/.exec(imageName); @@ -592,7 +589,7 @@ export const tagServiceImages = (docker, images, serviceImages) => .getImage(d.name) .tag({ repo: name, tag, force: true }) .then(() => docker.getImage(`${name}:${tag}`)) - .then(localImage => ({ + .then((localImage) => ({ serviceName: d.serviceName, serviceImage, localImage, @@ -629,13 +626,13 @@ export const getPreviousRepos = (sdk, docker, logger, appID) => $top: 1, }, }) - .then(function(release) { + .then(function (release) { // grab all images from the latest release, return all image locations in the registry if (release.length > 0) { const images = release[0].contains__image; - return Promise.map(images, function(d) { + return Promise.map(images, function (d) { const imageName = d.image[0].is_stored_at__image_location; - return docker.getRegistryAndName(imageName).then(function(registry) { + return docker.getRegistryAndName(imageName).then(function (registry) { logger.logDebug( `Requesting access to previously pushed image repo (${registry.imageName})`, ); @@ -646,7 +643,7 @@ export const getPreviousRepos = (sdk, docker, logger, appID) => return []; } }) - .catch(e => { + .catch((e) => { logger.logDebug(`Failed to access previously pushed image repo: ${e}`); return []; }); @@ -659,7 +656,7 @@ export const getPreviousRepos = (sdk, docker, logger, appID) => * @param {string[]} previousRepos * @returns {Promise} */ -export const authorizePush = function( +export const authorizePush = function ( sdk, tokenAuthEndpoint, registry, @@ -677,7 +674,7 @@ export const authorizePush = function( url: '/auth/v1/token', qs: { service: registry, - scope: images.map(repo => `repository:${repo}:pull,push`), + scope: images.map((repo) => `repository:${repo}:pull,push`), }, }) .get('body') @@ -691,7 +688,7 @@ export const authorizePush = function( * @param {Array} images * @param {(serviceImage: import('balena-release/build/models').ImageModel, props: object) => void} afterEach */ -export const pushAndUpdateServiceImages = function( +export const pushAndUpdateServiceImages = function ( docker, token, images, @@ -725,7 +722,7 @@ export const pushAndUpdateServiceImages = function( 1.4, // `backoffScaler` - wait multiplier for each retry ).finally(renderer.end), /** @type {(size: number, digest: string) => void} */ - function(size, digest) { + function (size, digest) { serviceImage.image_size = size; serviceImage.content_hash = digest; serviceImage.build_log = logs; @@ -741,7 +738,7 @@ export const pushAndUpdateServiceImages = function( serviceImage.status = 'success'; }, ) - .tapCatch(function(e) { + .tapCatch(function (e) { serviceImage.error_message = '' + e; serviceImage.status = 'failed'; }) @@ -752,7 +749,7 @@ export const pushAndUpdateServiceImages = function( // utilities -const renderProgressBar = function(percentage, stepCount) { +const renderProgressBar = function (percentage, stepCount) { const _ = require('lodash'); percentage = _.clamp(percentage, 0, 100); const barCount = Math.floor((stepCount * percentage) / 100); @@ -761,8 +758,8 @@ const renderProgressBar = function(percentage, stepCount) { return `${bar} ${_.padStart(percentage, 3)}%`; }; -var pushProgressRenderer = function(tty, prefix) { - const fn = function(e) { +var pushProgressRenderer = function (tty, prefix) { + const fn = function (e) { const { error, percentage } = e; if (error != null) { throw new Error(error); @@ -776,15 +773,15 @@ var pushProgressRenderer = function(tty, prefix) { return fn; }; -var createLogStream = function(input) { +var createLogStream = function (input) { const split = require('split'); const stripAnsi = require('strip-ansi-stream'); return input.pipe(stripAnsi()).pipe(split()); }; -var dropEmptyLinesStream = function() { +var dropEmptyLinesStream = function () { const through = require('through2'); - return through(function(data, _enc, cb) { + return through(function (data, _enc, cb) { const str = data.toString('utf-8'); if (str.trim()) { this.push(str); @@ -793,10 +790,10 @@ var dropEmptyLinesStream = function() { }); }; -var buildLogCapture = function(objectMode, buffer) { +var buildLogCapture = function (objectMode, buffer) { const through = require('through2'); - return through({ objectMode }, function(data, _enc, cb) { + return through({ objectMode }, function (data, _enc, cb) { // data from pull stream if (data.error) { buffer.push(`${data.error}`); @@ -814,7 +811,7 @@ var buildLogCapture = function(objectMode, buffer) { }); }; -var buildProgressAdapter = function(inline) { +var buildProgressAdapter = function (inline) { const through = require('through2'); const stepRegex = /^\s*Step\s+(\d+)\/(\d+)\s*: (.+)$/; @@ -823,7 +820,7 @@ var buildProgressAdapter = function(inline) { let numSteps = null; let progress; - return through({ objectMode: true }, function(str, _enc, cb) { + return through({ objectMode: true }, function (str, _enc, cb) { if (str == null) { return cb(null, str); } @@ -855,8 +852,8 @@ var buildProgressAdapter = function(inline) { }); }; -var pullProgressAdapter = outStream => - function({ status, id, percentage, error, errorDetail }) { +var pullProgressAdapter = (outStream) => + function ({ status, id, percentage, error, errorDetail }) { if (status != null) { status = status.replace(/^Status: /, ''); } @@ -887,8 +884,8 @@ class BuildProgressUI { const services = _.map(descriptors, 'serviceName'); const streams = _(services) - .map(function(service) { - const stream = through.obj(function(event, _enc, cb) { + .map(function (service) { + const stream = through.obj(function (event, _enc, cb) { eventHandler(service, event); return cb(); }); @@ -937,7 +934,7 @@ class BuildProgressUI { start() { process.on('SIGINT', this._handleInterrupt); this._tty.hideCursor(); - this._services.forEach(service => { + this._services.forEach((service) => { this.streams[service].write({ status: 'Preparing...' }); }); this._runloop = require('./compose_ts').createRunLoop(this._display); @@ -978,7 +975,7 @@ class BuildProgressUI { const serviceToDataMap = this._serviceToDataMap; return _(services) - .map(function(service) { + .map(function (service) { const { status, progress, error } = serviceToDataMap[service] ?? {}; if (error) { return `${error}`; @@ -1060,8 +1057,8 @@ class BuildProgressInline { const services = _.map(descriptors, 'serviceName'); const eventHandler = this._renderEvent; const streams = _(services) - .map(function(service) { - const stream = through.obj(function(event, _enc, cb) { + .map(function (service) { + const stream = through.obj(function (event, _enc, cb) { eventHandler(service, event); return cb(); }); @@ -1083,7 +1080,7 @@ class BuildProgressInline { start() { this._outStream.write('Building services...\n'); - this._services.forEach(service => { + this._services.forEach((service) => { this.streams[service].write({ status: 'Preparing...' }); }); this._startTime = Date.now(); @@ -1099,7 +1096,7 @@ class BuildProgressInline { this._ended = true; if (summary != null) { - this._services.forEach(service => { + this._services.forEach((service) => { this._renderEvent(service, summary[service]); }); } @@ -1122,7 +1119,7 @@ class BuildProgressInline { _renderEvent(service, event) { const _ = require('lodash'); - const str = (function() { + const str = (function () { const { status, error } = event; if (error) { return `${error}`; diff --git a/lib/utils/compose_ts.ts b/lib/utils/compose_ts.ts index d75fc8ca..fc2e4e26 100644 --- a/lib/utils/compose_ts.ts +++ b/lib/utils/compose_ts.ts @@ -188,7 +188,7 @@ export async function tarDirectory( let readFile: (file: string) => Promise; if (process.platform === 'win32') { const { readFileWithEolConversion } = require('./eol-conversion'); - readFile = file => readFileWithEolConversion(file, convertEol); + readFile = (file) => readFileWithEolConversion(file, convertEol); } else { readFile = fs.readFile; } @@ -222,7 +222,7 @@ export function printGitignoreWarn( dockerignoreFile: string, gitignoreFiles: string[], ) { - const ignoreFiles = [dockerignoreFile, ...gitignoreFiles].filter(e => e); + const ignoreFiles = [dockerignoreFile, ...gitignoreFiles].filter((e) => e); if (ignoreFiles.length === 0) { return; } @@ -346,7 +346,7 @@ export async function makeBuildTasks( const buildTasks = await MultiBuild.splitBuildStream(composition, tarStream); logger.logDebug('Found build tasks:'); - _.each(buildTasks, task => { + _.each(buildTasks, (task) => { let infoStr: string; if (task.external) { infoStr = `image pull [${task.imageName}]`; @@ -369,7 +369,7 @@ export async function makeBuildTasks( ); logger.logDebug('Found project types:'); - _.each(buildTasks, task => { + _.each(buildTasks, (task) => { if (task.external) { logger.logDebug(` ${task.serviceName}: External image`); } else { @@ -403,7 +403,7 @@ async function performResolution( ); // Do one task at a time (Bluebird.each instead of Bluebird.all) // in order to reduce peak memory usage. Resolves to buildTasks. - Bluebird.each(buildTasks, buildTask => { + Bluebird.each(buildTasks, (buildTask) => { // buildStream is falsy for "external" tasks (image pull) if (!buildTask.buildStream) { return buildTask; @@ -551,7 +551,7 @@ export async function validateProjectDirectory( const checkCompose = async (folder: string) => { return _.some( await Promise.all( - compositionFileNames.map(filename => + compositionFileNames.map((filename) => fs.exists(path.join(folder, filename)), ), ), @@ -615,7 +615,7 @@ async function pushServiceImages( const { pushAndUpdateServiceImages } = await import('./compose'); const releaseMod = await import('balena-release'); logger.logInfo('Pushing images to registry...'); - await pushAndUpdateServiceImages(docker, token, taggedImages, async function( + await pushAndUpdateServiceImages(docker, token, taggedImages, async function ( serviceImage, ) { logger.logDebug( @@ -713,12 +713,12 @@ function runSpinner( spinner: () => string, msg: string, ) { - const runloop = createRunLoop(function() { + const runloop = createRunLoop(function () { tty.clearLine(); tty.writeLine(`${msg} ${spinner()}`); return tty.cursorUp(); }); - runloop.onEnd = function() { + runloop.onEnd = function () { tty.clearLine(); return tty.writeLine(msg); }; diff --git a/lib/utils/config.ts b/lib/utils/config.ts index 042f813b..53dd0d43 100644 --- a/lib/utils/config.ts +++ b/lib/utils/config.ts @@ -72,7 +72,7 @@ export function generateBaseConfig( application.app_name, options, ) as Promise; - return promise.tap(config => { + return promise.tap((config) => { // os.getConfig always returns a config for an app delete config.apiKey; @@ -91,7 +91,7 @@ export function generateApplicationConfig( application: BalenaSdk.Application, options: { version: string; deviceType?: string }, ) { - return generateBaseConfig(application, options).tap(config => { + return generateBaseConfig(application, options).tap((config) => { if (semver.satisfies(options.version, '<2.7.8')) { return addApplicationKey(config, application.id); } @@ -108,12 +108,12 @@ export function generateDeviceConfig( ) { return getBalenaSdk() .models.application.get(device.belongs_to__application.__id) - .then(application => { + .then((application) => { const baseConfigOpts = { ...options, deviceType: device.device_type, }; - return generateBaseConfig(application, baseConfigOpts).tap(config => { + return generateBaseConfig(application, baseConfigOpts).tap((config) => { if ( deviceApiKey == null && semver.satisfies(options.version, '<2.0.3') @@ -123,7 +123,7 @@ export function generateDeviceConfig( return addDeviceKey(config, device.uuid, deviceApiKey || true); }); }) - .then(config => { + .then((config) => { // Associate a device, to prevent the supervisor // from creating another one on its own. config.registered_at = Math.floor(Date.now() / 1000); @@ -137,7 +137,7 @@ export function generateDeviceConfig( function addApplicationKey(config: any, applicationNameOrId: string | number) { return getBalenaSdk() .models.application.generateApiKey(applicationNameOrId) - .tap(apiKey => { + .tap((apiKey) => { config.apiKey = apiKey; }); } @@ -145,7 +145,7 @@ function addApplicationKey(config: any, applicationNameOrId: string | number) { function addProvisioningKey(config: any, applicationNameOrId: string | number) { return getBalenaSdk() .models.application.generateProvisioningKey(applicationNameOrId) - .tap(apiKey => { + .tap((apiKey) => { config.apiKey = apiKey; }); } @@ -161,7 +161,7 @@ function addDeviceKey( } else { return customDeviceApiKey; } - }).tap(deviceApiKey => { + }).tap((deviceApiKey) => { config.deviceApiKey = deviceApiKey; }); } diff --git a/lib/utils/deploy-legacy.js b/lib/utils/deploy-legacy.js index f7cf5758..ef3e4d58 100644 --- a/lib/utils/deploy-legacy.js +++ b/lib/utils/deploy-legacy.js @@ -18,19 +18,19 @@ import * as Promise from 'bluebird'; import { getVisuals } from './lazy'; -const getBuilderPushEndpoint = function(baseUrl, owner, app) { +const getBuilderPushEndpoint = function (baseUrl, owner, app) { const querystring = require('querystring'); const args = querystring.stringify({ owner, app }); return `https://builder.${baseUrl}/v1/push?${args}`; }; -const getBuilderLogPushEndpoint = function(baseUrl, buildId, owner, app) { +const getBuilderLogPushEndpoint = function (baseUrl, buildId, owner, app) { const querystring = require('querystring'); const args = querystring.stringify({ owner, app, buildId }); return `https://builder.${baseUrl}/v1/pushLogs?${args}`; }; -const bufferImage = function(docker, imageId, bufferFile) { +const bufferImage = function (docker, imageId, bufferFile) { const streamUtils = require('./streams'); const image = docker.getImage(imageId); @@ -40,14 +40,14 @@ const bufferImage = function(docker, imageId, bufferFile) { image.get(), imageMetadata.get('Size'), (imageStream, imageSize) => - streamUtils.buffer(imageStream, bufferFile).tap(bufferedStream => { + streamUtils.buffer(imageStream, bufferFile).tap((bufferedStream) => { // @ts-ignore adding an extra property bufferedStream.length = imageSize; }), ); }; -const showPushProgress = function(message) { +const showPushProgress = function (message) { const visuals = getVisuals(); const progressBar = new visuals.Progress(message); progressBar.update({ percentage: 0 }); @@ -55,8 +55,8 @@ const showPushProgress = function(message) { }; const uploadToPromise = (uploadRequest, logger) => - new Promise(function(resolve, reject) { - const handleMessage = function(data) { + new Promise(function (resolve, reject) { + const handleMessage = function (data) { let obj; data = data.toString(); logger.logDebug(`Received data: ${data}`); @@ -90,7 +90,7 @@ const uploadToPromise = (uploadRequest, logger) => /** * @returns {Promise<{ buildId: number }>} */ -const uploadImage = function( +const uploadImage = function ( imageStream, token, username, @@ -139,7 +139,7 @@ const uploadImage = function( return uploadToPromise(uploadRequest, logger); }; -const uploadLogs = function(logs, token, url, buildId, username, appName) { +const uploadLogs = function (logs, token, url, buildId, username, appName) { const request = require('request'); return request.post({ json: true, @@ -159,7 +159,7 @@ opts must be a hash with the following keys: - buildLogs: a string with build output - shouldUploadLogs */ -export const deployLegacy = function( +export const deployLegacy = function ( docker, logger, token, @@ -177,10 +177,10 @@ export const deployLegacy = function( const logs = buildLogs; return tmpNameAsync() - .then(function(bufferFile) { + .then(function (bufferFile) { logger.logInfo('Initializing deploy...'); return bufferImage(docker, imageName, bufferFile) - .then(stream => + .then((stream) => uploadImage(stream, token, username, url, appName, logger), ) .finally(() => @@ -192,7 +192,7 @@ export const deployLegacy = function( ), ); }) - .tap(function({ buildId }) { + .tap(function ({ buildId }) { if (!shouldUploadLogs) { return; } diff --git a/lib/utils/device/api.ts b/lib/utils/device/api.ts index 392a36ea..79c0efdb 100644 --- a/lib/utils/device/api.ts +++ b/lib/utils/device/api.ts @@ -105,7 +105,7 @@ export class DeviceAPI { json: true, }, this.logger, - ).then(body => { + ).then((body) => { return body.state; }); } @@ -120,7 +120,7 @@ export class DeviceAPI { json: true, }, this.logger, - ).then(body => { + ).then((body) => { return body.info; }); } @@ -166,7 +166,7 @@ export class DeviceAPI { return DeviceAPI.promisifiedRequest(request.get, { url, json: true, - }).then(body => { + }).then((body) => { if (body.status !== 'success') { throw new ApiErrors.DeviceAPIError( 'Non-successful response from supervisor version endpoint', @@ -183,7 +183,7 @@ export class DeviceAPI { return DeviceAPI.promisifiedRequest(request.get, { url, json: true, - }).then(body => { + }).then((body) => { if (body.status !== 'success') { throw new ApiErrors.DeviceAPIError( 'Non-successful response from supervisor status endpoint', @@ -201,13 +201,14 @@ export class DeviceAPI { return new Bluebird((resolve, reject) => { const req = request.get(url); - req.on('error', reject).on('response', async res => { + req.on('error', reject).on('response', async (res) => { if (res.statusCode !== 200) { reject( new ApiErrors.DeviceAPIError( 'Non-200 response from log streaming endpoint', ), ); + return; } res.socket.setKeepAlive(true, 1000); if (os.platform() !== 'win32') { @@ -260,7 +261,7 @@ export class DeviceAPI { } return Bluebird.fromCallback<[request.Response, { message: string }]>( - cb => { + (cb) => { return requestMethod(opts, cb); }, { multiArgs: true }, diff --git a/lib/utils/device/deploy.ts b/lib/utils/device/deploy.ts index 648e866c..8ea7a745 100644 --- a/lib/utils/device/deploy.ts +++ b/lib/utils/device/deploy.ts @@ -315,7 +315,7 @@ export async function performBuilds( logger, LOCAL_APPNAME, LOCAL_RELEASEHASH, - content => { + (content) => { if (!opts.nolive) { return LivepushManager.preprocessDockerfile(content); } else { @@ -356,7 +356,7 @@ export async function performBuilds( // Now tag any external images with the correct name that they should be, // as this won't be done by resin-multibuild - await Bluebird.map(localImages, async localImage => { + await Bluebird.map(localImages, async (localImage) => { if (localImage.external) { // We can be sure that localImage.name is set here, because of the failure code above const image = docker.getImage(localImage.name!); @@ -368,7 +368,7 @@ export async function performBuilds( } }); - await Bluebird.map(_.uniq(imagesToRemove), image => + await Bluebird.map(_.uniq(imagesToRemove), (image) => docker.getImage(image).remove({ force: true }), ); @@ -419,7 +419,7 @@ export async function rebuildSingleTask( logger, LOCAL_APPNAME, LOCAL_RELEASEHASH, - content => { + (content) => { if (!opts.nolive) { return LivepushManager.preprocessDockerfile(content); } else { @@ -460,16 +460,16 @@ function assignOutputHandlers( logger: Logger, logCb?: (serviceName: string, line: string) => void, ) { - _.each(buildTasks, task => { + _.each(buildTasks, (task) => { if (task.external) { - task.progressHook = progressObj => { + task.progressHook = (progressObj) => { displayBuildLog( { serviceName: task.serviceName, message: progressObj.progress }, logger, ); }; } else { - task.streamHook = stream => { + task.streamHook = (stream) => { stream.on('data', (buf: Buffer) => { const str = _.trimEnd(buf.toString()); if (str !== '') { @@ -601,7 +601,7 @@ async function inspectBuildResults(images: LocalImage[]): Promise { const failures: LocalPushErrors.BuildFailure[] = []; - _.each(images, image => { + _.each(images, (image) => { if (!image.successful) { failures.push({ error: image.error!, diff --git a/lib/utils/device/errors.ts b/lib/utils/device/errors.ts index bb470462..082eed91 100644 --- a/lib/utils/device/errors.ts +++ b/lib/utils/device/errors.ts @@ -17,14 +17,14 @@ export class BuildError extends TypedError { public toString(): string { let str = 'Some services failed to build:\n'; - _.each(this.failures, failure => { + _.each(this.failures, (failure) => { str += `\t${failure.serviceName}: ${failure.error.message}\n`; }); return str; } public getServiceError(serviceName: string): string { - const failure = _.find(this.failures, f => f.serviceName === serviceName); + const failure = _.find(this.failures, (f) => f.serviceName === serviceName); if (failure == null) { return 'Unknown build failure'; } diff --git a/lib/utils/device/live.ts b/lib/utils/device/live.ts index bb73d884..a8ad7d3c 100644 --- a/lib/utils/device/live.ts +++ b/lib/utils/device/live.ts @@ -199,7 +199,7 @@ export class LivepushManager { process.on('SIGINT', async () => { this.logger.logLivepush('Cleaning up device...'); await Promise.all( - _.map(this.containers, container => { + _.map(this.containers, (container) => { container.livepush.cleanupIntermediateContainers(); }), ); @@ -263,8 +263,8 @@ export class LivepushManager { // First we detect if the file changed is the Dockerfile // used to build the service if ( - _.some(this.dockerfilePaths[serviceName], name => - _.some(updated, changed => name === changed), + _.some(this.dockerfilePaths[serviceName], (name) => + _.some(updated, (changed) => name === changed), ) ) { this.logger.logLivepush( @@ -330,7 +330,7 @@ export class LivepushManager { this.composition, this.buildContext, this.deployOpts, - id => { + (id) => { this.rebuildRunningIds[serviceName] = id; }, ); @@ -430,10 +430,10 @@ export class LivepushManager { const error = (msg: string) => this.logger.logError(msgString(msg)); const debugLog = (msg: string) => this.logger.logDebug(msgString(msg)); - livepush.on('commandExecute', command => + livepush.on('commandExecute', (command) => log(`Executing command: \`${command.command}\``), ); - livepush.on('commandOutput', output => + livepush.on('commandOutput', (output) => log(` ${output.output.data.toString()}`), ); livepush.on('commandReturn', ({ returnCode, command }) => { diff --git a/lib/utils/device/logs.ts b/lib/utils/device/logs.ts index 7397ea24..6feb6320 100644 --- a/lib/utils/device/logs.ts +++ b/lib/utils/device/logs.ts @@ -40,7 +40,7 @@ export function displayDeviceLogs( filterServices?: string[], ): Bluebird { return new Bluebird((resolve, reject) => { - logs.on('data', log => { + logs.on('data', (log) => { displayLogLine(log, logger, system, filterServices); }); diff --git a/lib/utils/device/ssh.ts b/lib/utils/device/ssh.ts index a45c0b17..dbfa6b28 100644 --- a/lib/utils/device/ssh.ts +++ b/lib/utils/device/ssh.ts @@ -63,7 +63,7 @@ export async function performLocalDeviceSSH( const serviceNames: string[] = []; const containers = allContainers - .map(container => { + .map((container) => { for (const name of container.Names) { if (regex.test(name)) { return { id: container.Id, name }; @@ -75,7 +75,7 @@ export async function performLocalDeviceSSH( } return; }) - .filter(c => c != null); + .filter((c) => c != null); if (containers.length === 0) { throw new ExpectedError( diff --git a/lib/utils/docker-js.js b/lib/utils/docker-js.js index 22915d30..287e3abc 100644 --- a/lib/utils/docker-js.js +++ b/lib/utils/docker-js.js @@ -26,7 +26,7 @@ import * as _ from 'lodash'; // // NOTE: Care MUST be taken when using the function, so as to // not redefine/override options already provided. -export const appendConnectionOptions = opts => +export const appendConnectionOptions = (opts) => opts.concat([ { signature: 'docker', @@ -106,10 +106,10 @@ Implements the same feature as the "docker build --cache-from" option.`, ]); } -const generateConnectOpts = function(opts) { +const generateConnectOpts = function (opts) { const fs = require('mz/fs'); - return Promise.try(function() { + return Promise.try(function () { const connectOpts = {}; // Firsly need to decide between a local docker socket // and a host available over a host:port combo @@ -152,7 +152,7 @@ const generateConnectOpts = function(opts) { cert: fs.readFile(opts.cert, 'utf-8'), key: fs.readFile(opts.key, 'utf-8'), }; - return Promise.props(certBodies).then(toMerge => + return Promise.props(certBodies).then((toMerge) => _.merge(connectOpts, toMerge), ); } @@ -161,12 +161,12 @@ const generateConnectOpts = function(opts) { }); }; -const parseBuildArgs = function(args) { +const parseBuildArgs = function (args) { if (!Array.isArray(args)) { args = [args]; } const buildArgs = {}; - args.forEach(function(arg) { + args.forEach(function (arg) { // note: [^] matches any character, including line breaks const pair = /^([^\s]+?)=([^]*)$/.exec(arg); if (pair != null) { @@ -187,7 +187,7 @@ export function generateBuildOpts(options) { opts.nocache = true; } if (options['cache-from']?.trim()) { - opts.cachefrom = options['cache-from'].split(',').filter(i => !!i.trim()); + opts.cachefrom = options['cache-from'].split(',').filter((i) => !!i.trim()); } if (options.squash != null) { opts.squash = true; @@ -220,7 +220,7 @@ export function getDocker(options) { .tap(ensureDockerSeemsAccessible); } -const getDockerToolbelt = _.once(function() { +const getDockerToolbelt = _.once(function () { const Docker = require('docker-toolbelt'); Promise.promisifyAll(Docker.prototype, { filter(name) { @@ -252,7 +252,7 @@ const getDockerToolbelt = _.once(function() { * }} opts * @returns {import('docker-toolbelt')} */ -export const createClient = function(opts) { +export const createClient = function (opts) { const Docker = getDockerToolbelt(); const docker = new Docker(opts); const { modem } = docker; @@ -269,7 +269,7 @@ export const createClient = function(opts) { return docker; }; -var ensureDockerSeemsAccessible = function(docker) { +var ensureDockerSeemsAccessible = function (docker) { const { exitWithExpectedError } = require('../errors'); return docker .ping() diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index d459ebaa..04ca52d1 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -30,7 +30,7 @@ export function getGroupDefaults(group: { }): { [name: string]: string | number | undefined } { return _.chain(group) .get('options') - .map(question => [question.name, question.default]) + .map((question) => [question.name, question.default]) .fromPairs() .value(); } @@ -96,7 +96,7 @@ export async function sudo( export function runCommand(command: string): Bluebird { const capitano = require('capitano'); - return Bluebird.fromCallback(resolver => capitano.run(command, resolver)); + return Bluebird.fromCallback((resolver) => capitano.run(command, resolver)); } export async function getManifest( @@ -122,7 +122,7 @@ export async function osProgressHandler(step: InitializeEmitter) { step.on('stdout', process.stdout.write.bind(process.stdout)); step.on('stderr', process.stderr.write.bind(process.stderr)); - step.on('state', function(state) { + step.on('state', function (state) { if (state.operation.command === 'burn') { return; } @@ -135,7 +135,7 @@ export async function osProgressHandler(step: InitializeEmitter) { check: new visuals.Progress('Validating Device OS'), }; - step.on('burn', state => progressBars[state.type].update(state)); + step.on('burn', (state) => progressBars[state.type].update(state)); await new Promise((resolve, reject) => { step.on('error', reject); @@ -149,7 +149,7 @@ export function getAppWithArch( return Bluebird.join( getApplication(applicationName), getBalenaSdk().models.config.getDeviceTypes(), - function(app, deviceTypes) { + function (app, deviceTypes) { const config = _.find(deviceTypes, { slug: app.device_type, }); @@ -214,8 +214,9 @@ export function retry( promise = promise.catch((err: Error) => { const delay = backoffScaler ** count * delayMs; console.log( - `Retrying "${label}" after ${(delay / 1000).toFixed(2)}s (${count + - 1} of ${times}) due to: ${err}`, + `Retrying "${label}" after ${(delay / 1000).toFixed(2)}s (${ + count + 1 + } of ${times}) due to: ${err}`, ); return Bluebird.delay(delay).then(() => retry(func, times, label, delayMs, backoffScaler, count + 1), @@ -255,7 +256,7 @@ export function getManualSortCompareFunction( manuallySortedArray: U[], equalityFunc: (a: T, x: U, index: number, array: U[]) => boolean, ): (a: T, b: T) => number { - return function(a: T, b: T): number { + return function (a: T, b: T): number { const indexA = manuallySortedArray.findIndex((x, index, array) => equalityFunc(a, x, index, array), ); @@ -302,10 +303,10 @@ export function shellEscape(args: string[], detectShell = false): string[] { ? isWindowsComExeShell() : process.platform === 'win32'; if (isCmdExe) { - return args.map(v => windowsCmdExeEscapeArg(v)); + return args.map((v) => windowsCmdExeEscapeArg(v)); } else { const shellEscapeFunc: typeof ShellEscape = require('shell-escape'); - return args.map(v => shellEscapeFunc([v])); + return args.map((v) => shellEscapeFunc([v])); } } diff --git a/lib/utils/ignore.ts b/lib/utils/ignore.ts index 15113a23..4a95f13f 100644 --- a/lib/utils/ignore.ts +++ b/lib/utils/ignore.ts @@ -93,7 +93,7 @@ export class FileIgnorer { ): Promise { const contents = await fs.readFile(fullPath, 'utf8'); - contents.split('\n').forEach(line => { + contents.split('\n').forEach((line) => { // ignore empty lines and comments if (/\s*#/.test(line) || _.isEmpty(line)) { return; @@ -205,7 +205,7 @@ async function listFiles( const files: FileStats[] = []; const dirEntries = await fs.readdir(dir); await Promise.all( - dirEntries.map(async entry => { + dirEntries.map(async (entry) => { const filePath = path.join(dir, entry); const stats = await fs.stat(filePath); if (stats.isDirectory()) { diff --git a/lib/utils/logger.ts b/lib/utils/logger.ts index 461182df..c822ffe7 100644 --- a/lib/utils/logger.ts +++ b/lib/utils/logger.ts @@ -80,7 +80,7 @@ class Logger { livepush: logger.createLogStream('live'), }; - _.forEach(this.streams, function(stream, key) { + _.forEach(this.streams, function (stream, key) { if (key !== 'debug') { stream.pipe(process.stdout); } else if (process.env.DEBUG) { @@ -139,14 +139,14 @@ class Logger { * Log a message for output later, ignore duplicates. */ public deferredLog(msg: string, level: Level) { - if (!this.deferredLogMessages.find(entry => entry[0] === msg)) { + if (!this.deferredLogMessages.find((entry) => entry[0] === msg)) { this.deferredLogMessages.push([msg, level]); } } /** Output any messages that have been queued for deferred output */ public outputDeferredMessages() { - this.deferredLogMessages.forEach(m => { + this.deferredLogMessages.forEach((m) => { this.streams[m[1]].write(m[0] + eol); }); this.deferredLogMessages = []; diff --git a/lib/utils/oclif-utils.ts b/lib/utils/oclif-utils.ts index e113573a..c22cd4f1 100644 --- a/lib/utils/oclif-utils.ts +++ b/lib/utils/oclif-utils.ts @@ -37,8 +37,8 @@ export class CommandHelp { return CommandHelp.compact([ // this.command.id, (this.command.args || []) - .filter(a => !a.hidden) - .map(a => this.arg(a)) + .filter((a) => !a.hidden) + .map((a) => this.arg(a)) .join(' '), ]).join(' '); } @@ -54,6 +54,6 @@ export function capitanoizeOclifUsage( ): string { return (oclifUsage || '') .toString() - .replace(/(?<=\s)[A-Z]+(?=(\s|$))/g, match => `<${match}>`) + .replace(/(?<=\s)[A-Z]+(?=(\s|$))/g, (match) => `<${match}>`) .toLowerCase(); } diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index 868ab982..948bc83f 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -123,9 +123,9 @@ export function askLoginType() { export function selectDeviceType() { return getBalenaSdk() .models.config.getDeviceTypes() - .then(deviceTypes => { + .then((deviceTypes) => { deviceTypes = _.sortBy(deviceTypes, 'name').filter( - dt => dt.state !== 'DISCONTINUED', + (dt) => dt.state !== 'DISCONTINUED', ); return getForm().ask({ message: 'Device Type', @@ -144,7 +144,7 @@ export function confirm( yesMessage?: string, exitIfDeclined = false, ) { - return Bluebird.try(function() { + return Bluebird.try(function () { if (yesOption) { if (yesMessage) { console.log(yesMessage); @@ -157,7 +157,7 @@ export function confirm( type: 'confirm', default: false, }); - }).then(function(confirmed) { + }).then(function (confirmed) { if (!confirmed) { const err = new Error('Aborted'); if (exitIfDeclined) { @@ -174,7 +174,7 @@ export function selectApplication( const balena = getBalenaSdk(); return balena.models.application .hasAny() - .then(function(hasAnyApplications) { + .then(function (hasAnyApplications) { if (!hasAnyApplications) { throw new Error("You don't have any applications"); } @@ -182,11 +182,11 @@ export function selectApplication( return balena.models.application.getAll(); }) .filter(filter || _.constant(true)) - .then(applications => { + .then((applications) => { return getForm().ask({ message: 'Select an application', type: 'list', - choices: _.map(applications, application => ({ + choices: _.map(applications, (application) => ({ name: `${application.app_name} (${application.device_type})`, value: application.app_name, })), @@ -198,17 +198,17 @@ export function selectOrCreateApplication() { const balena = getBalenaSdk(); return balena.models.application .hasAny() - .then(hasAnyApplications => { + .then((hasAnyApplications) => { if (!hasAnyApplications) { // Just to make TS happy return Promise.resolve(undefined); } - return balena.models.application.getAll().then(applications => { + return balena.models.application.getAll().then((applications) => { const appOptions = _.map< BalenaSdk.Application, { name: string; value: string | null } - >(applications, application => ({ + >(applications, (application) => ({ name: `${application.app_name} (${application.device_type})`, value: application.app_name, })); @@ -225,7 +225,7 @@ export function selectOrCreateApplication() { }); }); }) - .then(application => { + .then((application) => { if (application) { return application; } @@ -240,14 +240,14 @@ export function selectOrCreateApplication() { export function awaitDevice(uuid: string) { const balena = getBalenaSdk(); - return balena.models.device.getName(uuid).then(deviceName => { + return balena.models.device.getName(uuid).then((deviceName) => { const visuals = getVisuals(); const spinner = new visuals.Spinner( `Waiting for ${deviceName} to come online`, ); const poll = (): Bluebird => { - return balena.models.device.isOnline(uuid).then(function(isOnline) { + return balena.models.device.isOnline(uuid).then(function (isOnline) { if (isOnline) { spinner.stop(); console.info(`The device **${deviceName}** is online!`); @@ -270,7 +270,7 @@ export function awaitDevice(uuid: string) { export function awaitDeviceOsUpdate(uuid: string, targetOsVersion: string) { const balena = getBalenaSdk(); - return balena.models.device.getName(uuid).then(deviceName => { + return balena.models.device.getName(uuid).then((deviceName) => { const visuals = getVisuals(); const progressBar = new visuals.Progress( `Updating the OS of ${deviceName} to v${targetOsVersion}`, @@ -314,15 +314,13 @@ export function inferOrSelectDevice(preferredUuid: string) { const balena = getBalenaSdk(); return balena.models.device .getAll() - .filter(device => device.is_online) - .then(onlineDevices => { + .filter((device) => device.is_online) + .then((onlineDevices) => { if (_.isEmpty(onlineDevices)) { throw new Error("You don't have any devices online"); } - const defaultUuid = _(onlineDevices) - .map('uuid') - .includes(preferredUuid) + const defaultUuid = _(onlineDevices).map('uuid').includes(preferredUuid) ? preferredUuid : onlineDevices[0].uuid; @@ -330,7 +328,7 @@ export function inferOrSelectDevice(preferredUuid: string) { message: 'Select a device', type: 'list', default: defaultUuid, - choices: _.map(onlineDevices, device => ({ + choices: _.map(onlineDevices, (device) => ({ name: `${device.device_name || 'Untitled'} (${device.uuid.slice( 0, 7, @@ -385,7 +383,7 @@ export async function getOnlineTargetUuid( message: 'Select a device', type: 'list', default: devices[0].uuid, - choices: _.map(devices, device => ({ + choices: _.map(devices, (device) => ({ name: `${device.device_name || 'Untitled'} (${device.uuid.slice( 0, 7, @@ -419,7 +417,7 @@ export function selectFromList( return getForm().ask({ message, type: 'list', - choices: _.map(choices, s => ({ + choices: _.map(choices, (s) => ({ name: s.name, value: s, })), diff --git a/lib/utils/promote.ts b/lib/utils/promote.ts index 1dcc506b..7a02f416 100644 --- a/lib/utils/promote.ts +++ b/lib/utils/promote.ts @@ -93,7 +93,7 @@ async function execCommand( const spinner = new visuals.Spinner(`[${deviceIp}] Connecting...`); const innerSpinner = spinner.spinner; - const stream = through(function(data, _enc, cb) { + const stream = through(function (data, _enc, cb) { innerSpinner.setSpinnerTitle(`%s [${deviceIp}] ${msg}`); cb(null, data); }); @@ -160,7 +160,7 @@ async function getOrSelectLocalDevice(deviceIp?: string): Promise { const through = await import('through2'); let ip: string | null = null; - const stream = through(function(data, _enc, cb) { + const stream = through(function (data, _enc, cb) { const match = /^==> Selected device: (.*)$/m.exec(data.toString()); if (match) { ip = match[1]; @@ -195,7 +195,7 @@ async function selectAppFromList(applications: BalenaSdk.Application[]) { // name (user/appname) and allows them to select. return selectFromList( 'Select application', - _.map(applications, app => { + _.map(applications, (app) => { return { name: app.slug, ...app }; }), ); @@ -216,7 +216,7 @@ async function getOrSelectApplication( } const compatibleDeviceTypes = _(allDeviceTypes) .filter( - dt => + (dt) => sdk.models.os.isArchitectureCompatibleWith( deviceTypeManifest.arch, dt.arch, @@ -224,7 +224,7 @@ async function getOrSelectApplication( !!dt.isDependent === !!deviceTypeManifest.isDependent && dt.state !== 'DISCONTINUED', ) - .map(type => type.slug) + .map((type) => type.slug) .value(); if (!appName) { @@ -270,7 +270,7 @@ async function getOrSelectApplication( // We've found at least one app with the given name. // Filter out apps for non-matching device types and see what we're left with. - const validApplications = applications.filter(app => + const validApplications = applications.filter((app) => _.includes(compatibleDeviceTypes, app.device_type), ); @@ -382,7 +382,8 @@ async function generateApplicationConfig( const manifest = await sdk.models.device.getManifestBySlug(app.device_type); const opts = - manifest.options && manifest.options.filter(opt => opt.name !== 'network'); + manifest.options && + manifest.options.filter((opt) => opt.name !== 'network'); const values = { ...(opts ? await form.run(opts) : {}), ...options, diff --git a/lib/utils/qemu.ts b/lib/utils/qemu.ts index af155ec1..909be839 100644 --- a/lib/utils/qemu.ts +++ b/lib/utils/qemu.ts @@ -39,13 +39,13 @@ export function copyQemu(context: string, arch: string) { const binPath = path.join(binDir, QEMU_BIN_NAME); return Bluebird.resolve(fs.mkdir(binDir)) - .catch({ code: 'EEXIST' }, function() { + .catch({ code: 'EEXIST' }, function () { // noop }) .then(() => getQemuPath(arch)) .then( - qemu => - new Bluebird(function(resolve, reject) { + (qemu) => + new Bluebird(function (resolve, reject) { const read = fs.createReadStream(qemu); const write = fs.createWriteStream(binPath); @@ -60,14 +60,14 @@ export function copyQemu(context: string, arch: string) { .then(() => path.relative(context, binPath)); } -export const getQemuPath = function(arch: string) { +export const getQemuPath = function (arch: string) { const balena = getBalenaSdk(); const path = require('path') as typeof import('path'); const fs = require('mz/fs') as typeof import('mz/fs'); - return balena.settings.get('binDirectory').then(binDir => + return balena.settings.get('binDirectory').then((binDir) => Bluebird.resolve(fs.mkdir(binDir)) - .catch({ code: 'EEXIST' }, function() { + .catch({ code: 'EEXIST' }, function () { // noop }) .then(() => @@ -83,8 +83,8 @@ export function installQemu(arch: string) { const tar = require('tar-stream') as typeof import('tar-stream'); return getQemuPath(arch).then( - qemuPath => - new Bluebird(function(resolve, reject) { + (qemuPath) => + new Bluebird(function (resolve, reject) { const installStream = fs.createWriteStream(qemuPath); const qemuArch = balenaArchToQemuArch(arch); @@ -96,7 +96,7 @@ export function installQemu(arch: string) { const qemuUrl = `https://github.com/balena-io/qemu/releases/download/${urlVersion}/${urlFile}`; const extract = tar.extract(); - extract.on('entry', function(header, stream, next) { + extract.on('entry', function (header, stream, next) { stream.on('end', next); if (header.name.includes(`qemu-${qemuArch}-static`)) { stream.pipe(installStream); @@ -111,7 +111,7 @@ export function installQemu(arch: string) { .on('error', reject) .pipe(extract) .on('error', reject) - .on('finish', function() { + .on('finish', function () { fs.chmodSync(qemuPath, '755'); resolve(); }); @@ -119,7 +119,7 @@ export function installQemu(arch: string) { ); } -const balenaArchToQemuArch = function(arch: string) { +const balenaArchToQemuArch = function (arch: string) { switch (arch) { case 'armv7hf': case 'rpi': diff --git a/lib/utils/remote-build.ts b/lib/utils/remote-build.ts index fbe73346..d56f96c8 100644 --- a/lib/utils/remote-build.ts +++ b/lib/utils/remote-build.ts @@ -101,7 +101,12 @@ async function getBuilderEndpoint( }); // Note that using https (rather than http) is a requirement when using the // --registry-secrets feature, as the secrets are not otherwise encrypted. - return `https://builder.${baseUrl}/v3/build?${args}`; + let builderUrl = + process.env.BALENARC_BUILDER_URL || `https://builder.${baseUrl}`; + if (builderUrl.endsWith('/')) { + builderUrl = builderUrl.slice(0, -1); + } + return `${builderUrl}/v3/build?${args}`; } export async function startRemoteBuild(build: RemoteBuild): Promise { diff --git a/lib/utils/ssh.ts b/lib/utils/ssh.ts index ac22dbbe..b941521d 100644 --- a/lib/utils/ssh.ts +++ b/lib/utils/ssh.ts @@ -95,7 +95,7 @@ export async function execBuffered( await exec( deviceIp, cmd, - through(function(data, _enc, cb) { + through(function (data, _enc, cb) { buffer.push(data.toString(enc)); cb(); }), diff --git a/lib/utils/streams.ts b/lib/utils/streams.ts index 6d2034a1..6f197868 100644 --- a/lib/utils/streams.ts +++ b/lib/utils/streams.ts @@ -23,14 +23,11 @@ export function buffer( ): Promise { const fileWriteStream = fs.createWriteStream(bufferFile); - return new Promise(function(resolve, reject) { - stream - .on('error', reject) - .on('end', resolve) - .pipe(fileWriteStream); + return new Promise(function (resolve, reject) { + stream.on('error', reject).on('end', resolve).pipe(fileWriteStream); }).then( () => - new Promise(function(resolve, reject) { + new Promise(function (resolve, reject) { const fstream = fs.createReadStream(bufferFile); fstream.on('open', () => resolve(fstream)).on('error', reject); diff --git a/lib/utils/sudo.ts b/lib/utils/sudo.ts index 70c762e1..781035d2 100644 --- a/lib/utils/sudo.ts +++ b/lib/utils/sudo.ts @@ -86,7 +86,7 @@ async function spawnAndPipe( await new Promise((resolve, reject) => { const ps: ChildProcess = spawn(spawnCmd, spawnArgs, spawnOpts); ps.on('error', reject); - ps.on('exit', codeOrSignal => { + ps.on('exit', (codeOrSignal) => { if (codeOrSignal !== 0) { const errMsgCmd = `[${[spawnCmd, ...spawnArgs].join()}]`; reject( diff --git a/lib/utils/tunnel.ts b/lib/utils/tunnel.ts index 1d02104b..3893a63b 100644 --- a/lib/utils/tunnel.ts +++ b/lib/utils/tunnel.ts @@ -54,14 +54,14 @@ export const tunnelConnectionToDevice = ( return (client: Socket): Bluebird => openPortThroughProxy(vpnUrl, 3128, auth, uuid, port) - .then(remote => { + .then((remote) => { client.pipe(remote); remote.pipe(client); - remote.on('error', err => { + remote.on('error', (err) => { console.error('Remote: ' + err); client.end(); }); - client.on('error', err => { + client.on('error', (err) => { console.error('Client: ' + err); remote.end(); }); diff --git a/lib/utils/version.ts b/lib/utils/version.ts index 1ca510db..04a36c41 100644 --- a/lib/utils/version.ts +++ b/lib/utils/version.ts @@ -26,7 +26,9 @@ let v12: boolean; export function isV12(): boolean { if (v12 === undefined) { - v12 = isVersionGTE('12.0.0'); + // This is the `Change-type: major` PR that will produce v12.0.0. + // Enable the v12 feature switches and run all v12 tests. + v12 = true; // v12 = isVersionGTE('12.0.0'); } return v12; } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f510f045..3ab24718 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -13,6 +13,53 @@ "@babel/highlight": "^7.0.0" } }, + "@babel/generator": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", + "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", + "dev": true, + "requires": { + "@babel/types": "^7.10.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", + "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.1", + "@babel/template": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", + "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", + "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "dev": true, + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==", + "dev": true + }, "@babel/highlight": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", @@ -44,18 +91,141 @@ } }, "@babel/parser": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.7.tgz", - "integrity": "sha512-9JWls8WilDXFGxs0phaXAZgpxTZhSk/yOYH2hTHC0X1yC7Z78IJfvR1vJ+rmJKq3I35td2XzXzN6ZLYlna+r/A==", + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", + "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==", "dev": true }, "@babel/runtime": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", - "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz", + "integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", + "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/parser": "^7.10.1", + "@babel/types": "^7.10.1" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", + "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.1" + } + }, + "@babel/highlight": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", + "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/traverse": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", + "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/generator": "^7.10.1", + "@babel/helper-function-name": "^7.10.1", + "@babel/helper-split-export-declaration": "^7.10.1", + "@babel/parser": "^7.10.1", + "@babel/types": "^7.10.1", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", + "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.1" + } + }, + "@babel/highlight": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", + "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", + "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" } }, "@balena.io/usb": { @@ -109,36 +279,40 @@ "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" }, + "@balena/es-version": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@balena/es-version/-/es-version-1.0.0.tgz", + "integrity": "sha512-o3/sRDyrXC75BUUziMAs+W5C02aVST0YqY5Ny31Ot3a+7CzK2XDRinMGywvK93tm2QVdL83HGkN483S62Xo9Dw==" + }, "@balena/lint": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-4.1.1.tgz", - "integrity": "sha512-lvIDG0yXguO53PkY9zF/8O5NLc4YZPv1EgoreSSjAxYEN7ptf+WPr9pAtN8OS1uGQk0/RoTF26IL8flQhInQWg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@balena/lint/-/lint-5.1.0.tgz", + "integrity": "sha512-ktyLx8bi2PwhED7KYKyi93RhpS9mI5X2KHA076hcpr/fD8W+XQF5DBga8Y40oNrt+qG8ZjVyMMXA3vvuVAslRA==", "dev": true, "requires": { - "@types/depcheck": "^0.6.0", "@types/glob": "^7.1.1", - "@types/lodash": "^4.14.149", - "@types/node": "^8.10.59", + "@types/lodash": "^4.14.150", + "@types/node": "^10.17.21", "@types/optimist": "0.0.29", - "@types/prettier": "^1.18.3", + "@types/prettier": "^2.0.0", "coffee-script": "^1.10.0", "coffeelint": "^1.15.0", "coffeescope2": "^0.4.5", - "depcheck": "^0.6.7", + "depcheck": "^0.9.2", "glob": "^7.1.6", "lodash": "^4.17.15", "optimist": "^0.6.1", - "prettier": "^1.19.1", - "tslint": "^5.20.1", + "prettier": "^2.0.5", + "tslint": "^6.1.2", "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", - "typescript": "^3.7.5" + "typescript": "^3.9.2" }, "dependencies": { "@types/node": { - "version": "8.10.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", + "version": "10.17.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.26.tgz", + "integrity": "sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw==", "dev": true }, "glob": { @@ -950,12 +1124,6 @@ "@types/node": "*" } }, - "@types/depcheck": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/depcheck/-/depcheck-0.6.0.tgz", - "integrity": "sha512-l/1wJTM4G+aWVzonZJ8vx/xJp3flBLWgZMUrCWBaGysiCutl+q3Eu1lKPq6GYFasP7L19KZ3L/y1kv3X08R71w==", - "dev": true - }, "@types/dockerode": { "version": "2.5.27", "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.27.tgz", @@ -1045,6 +1213,15 @@ "integrity": "sha512-JPhCJHHu5/5HuQ78rmbrnCk+XlyxKuAopk+/8rP5MfAeL7KwiCH/gFFNtAqIr0/JFqWFk+jXWZjEWSP8dzGpMg==", "dev": true }, + "@types/http-proxy": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz", + "integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/inquirer": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz", @@ -1108,9 +1285,9 @@ "integrity": "sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w==" }, "@types/memoizee": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@types/memoizee/-/memoizee-0.4.3.tgz", - "integrity": "sha512-N6QT0c9ZbEKl33n1wyoTxZs4cpN+YXjs0Aqy5Qim8ipd9PBNIPqOh/p5Pixc4601tqr5GErsdxUbfqviDfubNw==" + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@types/memoizee/-/memoizee-0.4.4.tgz", + "integrity": "sha512-c9+1g6+6vEqcw5UuM0RbfQV0mssmZcoG9+hNC5ptDCsv4G+XJW1Z4pE13wV5zbc9e0+YrDydALBTiD3nWG1a3g==" }, "@types/mime": { "version": "2.0.1", @@ -1214,9 +1391,9 @@ "dev": true }, "@types/prettier": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.0.tgz", - "integrity": "sha512-gDE8JJEygpay7IjA/u3JiIURvwZW08f0cZSZLAzFoX/ZmeqvS0Sqv+97aKuHpNsalAMMhwPe+iAS6fQbfmbt7A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz", + "integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==", "dev": true }, "@types/prettyjson": { @@ -1987,91 +2164,6 @@ } } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -2095,9 +2187,9 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "balena-auth": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/balena-auth/-/balena-auth-3.0.1.tgz", - "integrity": "sha512-ImeAWuJK1FL7vkMxIKNkMUMSExE4Is8dc09aKKMm1xwbdRbLVllMYpVhr9wbMFXEbe6GX9x5RwVF2YlFrvixBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/balena-auth/-/balena-auth-3.1.0.tgz", + "integrity": "sha512-RMdRmxvhfZsoahq+GApdf0TN97a55kHprLsWkvZYJugdMThookcRw7B79pJFbEPXTPLX8vlpD3RbJ5IgUf7C6w==", "requires": { "@types/bluebird": "^3.5.29", "@types/jwt-decode": "^2.2.1", @@ -2122,6 +2214,33 @@ "version": "8.10.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" + }, + "balena-sdk": { + "version": "12.33.0", + "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.33.0.tgz", + "integrity": "sha512-riqcJeA8SYMf20bgt1lhgw/eWiWXVwivGHgBer54/dxDIo48RIwys98LvkisIO2sTco1AmTL4Q0rePsMGjEYwQ==", + "requires": { + "@types/bluebird": "^3.5.30", + "@types/lodash": "^4.14.149", + "@types/memoizee": "^0.4.3", + "@types/node": "^8.10.59", + "abortcontroller-polyfill": "^1.4.0", + "balena-auth": "^3.0.1", + "balena-device-status": "^3.2.1", + "balena-errors": "^4.3.0", + "balena-hup-action-utils": "~4.0.0", + "balena-pine": "^10.1.1", + "balena-register-device": "^6.0.1", + "balena-request": "^10.0.8", + "balena-semver": "^2.2.0", + "balena-settings-client": "^4.0.4", + "bluebird": "^3.7.2", + "lodash": "^4.17.15", + "memoizee": "^0.4.14", + "moment": "^2.24.0", + "ndjson": "^1.5.0", + "semver": "^7.1.3" + } } } }, @@ -2191,6 +2310,33 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" }, + "balena-sdk": { + "version": "12.33.0", + "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.33.0.tgz", + "integrity": "sha512-riqcJeA8SYMf20bgt1lhgw/eWiWXVwivGHgBer54/dxDIo48RIwys98LvkisIO2sTco1AmTL4Q0rePsMGjEYwQ==", + "requires": { + "@types/bluebird": "^3.5.30", + "@types/lodash": "^4.14.149", + "@types/memoizee": "^0.4.3", + "@types/node": "^8.10.59", + "abortcontroller-polyfill": "^1.4.0", + "balena-auth": "^3.0.1", + "balena-device-status": "^3.2.1", + "balena-errors": "^4.3.0", + "balena-hup-action-utils": "~4.0.0", + "balena-pine": "^10.1.1", + "balena-register-device": "^6.0.1", + "balena-request": "^10.0.8", + "balena-semver": "^2.2.0", + "balena-settings-client": "^4.0.4", + "bluebird": "^3.7.2", + "lodash": "^4.17.15", + "memoizee": "^0.4.14", + "moment": "^2.24.0", + "ndjson": "^1.5.0", + "semver": "^7.1.3" + } + }, "mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", @@ -2240,6 +2386,40 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" }, + "balena-sdk": { + "version": "12.33.0", + "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.33.0.tgz", + "integrity": "sha512-riqcJeA8SYMf20bgt1lhgw/eWiWXVwivGHgBer54/dxDIo48RIwys98LvkisIO2sTco1AmTL4Q0rePsMGjEYwQ==", + "requires": { + "@types/bluebird": "^3.5.30", + "@types/lodash": "^4.14.149", + "@types/memoizee": "^0.4.3", + "@types/node": "^8.10.59", + "abortcontroller-polyfill": "^1.4.0", + "balena-auth": "^3.0.1", + "balena-device-status": "^3.2.1", + "balena-errors": "^4.3.0", + "balena-hup-action-utils": "~4.0.0", + "balena-pine": "^10.1.1", + "balena-register-device": "^6.0.1", + "balena-request": "^10.0.8", + "balena-semver": "^2.2.0", + "balena-settings-client": "^4.0.4", + "bluebird": "^3.7.2", + "lodash": "^4.17.15", + "memoizee": "^0.4.14", + "moment": "^2.24.0", + "ndjson": "^1.5.0", + "semver": "^7.1.3" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + } + } + }, "docker-progress": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/docker-progress/-/docker-progress-3.0.5.tgz", @@ -2290,12 +2470,23 @@ } }, "balena-register-device": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/balena-register-device/-/balena-register-device-6.1.0.tgz", - "integrity": "sha512-iPCm8HiZUG2xwNLVECaHx8g589jnqBDtCpLJ0ow7828xd0Spk9nF/GncRqiqmeo5OUZPnXW7prLkaRwu4s2Otg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/balena-register-device/-/balena-register-device-6.1.6.tgz", + "integrity": "sha512-kS8JZoLyucZ9oUFicspN/k3jRrvdtQ4UYXVCHHyw91C3y2Z1T5CQpwfMBSqA8dp5wQKyP527K/0+lMUa2ncLhA==", "requires": { "bluebird": "^3.7.2", - "randomstring": "^1.1.5" + "randomstring": "^1.1.5", + "typed-error": "^2.0.0" + }, + "dependencies": { + "typed-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-error/-/typed-error-2.0.0.tgz", + "integrity": "sha1-05j9hin8K3nIOfm30b/Ay7ZSYBI=", + "requires": { + "tslib": "^1.7.1" + } + } } }, "balena-release": { @@ -2337,43 +2528,86 @@ }, "dependencies": { "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" } } }, "balena-sdk": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.33.0.tgz", - "integrity": "sha512-riqcJeA8SYMf20bgt1lhgw/eWiWXVwivGHgBer54/dxDIo48RIwys98LvkisIO2sTco1AmTL4Q0rePsMGjEYwQ==", + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-13.8.0.tgz", + "integrity": "sha512-EhaXZq7kNCewhp0oSMeeTslvRIhvaV0QxlS7u3SWmdMoDjH1gdg01sG2I3b20DkN8oDAIoNkaD14eo2eTf0C8w==", "requires": { "@types/bluebird": "^3.5.30", - "@types/lodash": "^4.14.149", + "@types/lodash": "^4.14.150", "@types/memoizee": "^0.4.3", - "@types/node": "^8.10.59", + "@types/node": "^10.17.20", "abortcontroller-polyfill": "^1.4.0", "balena-auth": "^3.0.1", - "balena-device-status": "^3.2.1", - "balena-errors": "^4.3.0", - "balena-hup-action-utils": "~4.0.0", - "balena-pine": "^10.1.1", - "balena-register-device": "^6.0.1", - "balena-request": "^10.0.8", - "balena-semver": "^2.2.0", + "balena-errors": "^4.4.0", + "balena-hup-action-utils": "~4.0.1", + "balena-pine": "^11.0.1", + "balena-register-device": "^6.1.1", + "balena-request": "^10.0.9", + "balena-semver": "^2.3.0", "balena-settings-client": "^4.0.4", "bluebird": "^3.7.2", "lodash": "^4.17.15", "memoizee": "^0.4.14", - "moment": "^2.24.0", + "moment": "~2.24.0 || ^2.25.1", "ndjson": "^1.5.0", - "semver": "^7.1.3" + "semver": "^7.3.2" }, "dependencies": { - "@types/node": { - "version": "8.10.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", - "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" + "balena-errors": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz", + "integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==", + "requires": { + "tslib": "^1.11.1", + "typed-error": "^3.0.0" + } + }, + "balena-pine": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/balena-pine/-/balena-pine-11.2.1.tgz", + "integrity": "sha512-K8g9HQ9opaT2ZiSCxkvW8VVCsXzOZ0gBejLOs+BgsA5ECVNLu5uS0H9Qfpf1R2Yg2CB3sopBqes2nMIhq7qGJQ==", + "requires": { + "@balena/es-version": "^1.0.0", + "@types/bluebird": "^3.5.32", + "balena-errors": "^4.2.1", + "bluebird": "^3.7.2", + "pinejs-client-core": "^5.8.0" + }, + "dependencies": { + "@types/bluebird": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz", + "integrity": "sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g==" + } + } + }, + "balena-semver": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz", + "integrity": "sha512-dlUBaYz22ZxHh3umciI/87aLcwX+HRlT1RjkpuiClO8wjkuR8U/2ZvtS16iMNe30rm2kNgAV2myamQ5eA8SxVQ==", + "requires": { + "@types/lodash": "^4.14.149", + "@types/semver": "^7.1.0", + "lodash": "^4.17.15", + "semver": "^7.1.3" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" } } }, @@ -2411,9 +2645,9 @@ } }, "balena-settings-storage": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/balena-settings-storage/-/balena-settings-storage-5.0.0.tgz", - "integrity": "sha512-alDQKM//J4wvjQXuv99ma3NsamZvHnwkxChwWpbBl9JMVr2kQHZpnsaw193wp7fdFKfNh3UFDdp1RYezDysXMA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/balena-settings-storage/-/balena-settings-storage-5.0.2.tgz", + "integrity": "sha512-379S8kNo1NYZvTE5+iRHQ0Q4+uSXIqsE+83gxF+onBjX6ecyF7b7Z+ZVvRbmIeo5rn6xGO2P5iW72ehL5tXgaw==", "requires": { "@resin.io/types-node-localstorage": "^1.3.0", "@types/bluebird": "^3.5.8", @@ -2423,9 +2657,9 @@ }, "dependencies": { "@types/node": { - "version": "8.10.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", - "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" + "version": "8.10.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz", + "integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==" } } }, @@ -2460,6 +2694,33 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" }, + "balena-sdk": { + "version": "12.33.0", + "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.33.0.tgz", + "integrity": "sha512-riqcJeA8SYMf20bgt1lhgw/eWiWXVwivGHgBer54/dxDIo48RIwys98LvkisIO2sTco1AmTL4Q0rePsMGjEYwQ==", + "requires": { + "@types/bluebird": "^3.5.30", + "@types/lodash": "^4.14.149", + "@types/memoizee": "^0.4.3", + "@types/node": "^8.10.59", + "abortcontroller-polyfill": "^1.4.0", + "balena-auth": "^3.0.1", + "balena-device-status": "^3.2.1", + "balena-errors": "^4.3.0", + "balena-hup-action-utils": "~4.0.0", + "balena-pine": "^10.1.1", + "balena-register-device": "^6.0.1", + "balena-request": "^10.0.8", + "balena-semver": "^2.2.0", + "balena-settings-client": "^4.0.4", + "bluebird": "^3.7.2", + "lodash": "^4.17.15", + "memoizee": "^0.4.14", + "moment": "^2.24.0", + "ndjson": "^1.5.0", + "semver": "^7.1.3" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2900,9 +3161,9 @@ "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" }, "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", "dev": true }, "byline": { @@ -2975,6 +3236,23 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -3862,12 +4140,6 @@ "is-plain-object": "^2.0.1" } }, - "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3979,31 +4251,45 @@ } }, "cross-env": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-3.2.4.tgz", - "integrity": "sha1-ngWF8neGTtQhznVvgamA/w1piro=", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, "requires": { - "cross-spawn": "^5.1.0", - "is-windows": "^1.0.0" + "cross-spawn": "^7.0.1" }, "dependencies": { "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "requires": { - "isexe": "^2.0.0" + "shebang-regex": "^3.0.0" } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true } } }, @@ -4090,6 +4376,12 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -4264,148 +4556,177 @@ } }, "depcheck": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.6.11.tgz", - "integrity": "sha512-wTVJ8cNilB8NfkzoBblcYqsB8LRfbjqKEwAOLD3YXIRigktSM7/lS9xQfVkAVujhjstmiQMZR0hkdHSnQxzb9A==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-0.9.2.tgz", + "integrity": "sha512-w5f+lSZqLJJkk58s44eOd0Vor7hLZot4PlFL0y2JsIX5LuHQ2eAjHlDVeGBD4Mj6ZQSKakvKWRRCcPlvrdU2Sg==", "dev": true, "requires": { - "babel-traverse": "^6.7.3", - "babylon": "^6.1.21", - "builtin-modules": "^1.1.1", - "deprecate": "^1.0.0", + "@babel/parser": "^7.7.7", + "@babel/traverse": "^7.7.4", + "builtin-modules": "^3.0.0", + "camelcase": "^5.3.1", + "cosmiconfig": "^5.2.1", + "debug": "^4.1.1", "deps-regex": "^0.1.4", "js-yaml": "^3.4.2", - "lodash": "^4.5.1", + "lodash": "^4.17.15", "minimatch": "^3.0.2", + "node-sass-tilde-importer": "^1.0.2", + "please-upgrade-node": "^3.2.0", "require-package-name": "^2.0.1", - "walkdir": "0.0.11", - "yargs": "^8.0.2" + "resolve": "^1.14.1", + "vue-template-compiler": "^2.6.11", + "walkdir": "^0.4.1", + "yargs": "^15.0.2" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "caller-callsite": "^2.0.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "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" + } + }, + "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 + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "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 }, - "locate-path": { + "import-fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" } }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" + "p-locate": "^4.1.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "read-pkg-up": { + "require-main-filename": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "path-parse": "^1.0.6" } }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" + "ansi-regex": "^5.0.0" } }, "which-module": { @@ -4414,46 +4735,50 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - }, - "dependencies": { - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - } + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" } }, "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -4463,12 +4788,6 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, - "deprecate": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deprecate/-/deprecate-1.1.1.tgz", - "integrity": "sha512-ZGDXefq1xknT292LnorMY5s8UVU08/WKdzDZCUT6t9JzsiMSP4uzUhgpqugffNVcT5WC6wMBiSQ+LFjlv3v7iQ==", - "dev": true - }, "deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", @@ -5182,9 +5501,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.2.tgz", + "integrity": "sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A==", "dev": true, "requires": { "esprima": "^4.0.1", @@ -5689,6 +6008,12 @@ } } }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -6007,9 +6332,9 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fastq": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.1.tgz", - "integrity": "sha512-mpIH5sKYueh3YyeJwqtVo8sORi0CgtmkVbK6kZStpQlZBYQuTzG2CZ7idSiJuA7bY0SFCWUc5WIs+oYumGCQNw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -6163,6 +6488,12 @@ } } }, + "find-parent-dir": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", + "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", + "dev": true + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -6810,23 +7141,28 @@ } }, "global-agent": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.8.tgz", - "integrity": "sha512-VpBe/rhY6Rw2VDOTszAMNambg+4Qv8j0yiTNDYEXXXxkUNGWLHp8A3ztK4YDBbFNcWF4rgsec6/5gPyryya/+A==", + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.12.tgz", + "integrity": "sha512-caAljRMS/qcDo69X9BfkgrihGUgGx44Fb4QQToNQjsiWh+YlQ66uqYVAdA8Olqit+5Ng0nkz09je3ZzANMZcjg==", "requires": { - "boolean": "^3.0.0", - "core-js": "^3.6.4", + "boolean": "^3.0.1", + "core-js": "^3.6.5", "es6-error": "^4.1.1", - "matcher": "^2.1.0", - "roarr": "^2.15.2", - "semver": "^7.1.2", - "serialize-error": "^5.0.0" + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" }, "dependencies": { "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" } } }, @@ -7402,6 +7738,17 @@ } } }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -7818,15 +8165,6 @@ "p-is-promise": "^3.0.0" } }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -7966,6 +8304,12 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, "is-docker": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", @@ -8768,15 +9112,6 @@ } } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -8971,17 +9306,17 @@ } }, "matcher": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz", - "integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "requires": { - "escape-string-regexp": "^2.0.0" + "escape-string-regexp": "^4.0.0" }, "dependencies": { "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" } } }, @@ -8999,15 +9334,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, "mem-fs": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.3.tgz", @@ -13287,6 +13613,15 @@ } } }, + "node-sass-tilde-importer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-sass-tilde-importer/-/node-sass-tilde-importer-1.0.2.tgz", + "integrity": "sha512-Swcmr38Y7uB78itQeBm3mThjxBy9/Ah/ykPIaURY/L6Nec9AyRoL/jJ7ECfMR+oZeCTVQNxVMu/aHU+TLRVbdg==", + "dev": true, + "requires": { + "find-parent-dir": "^0.3.0" + } + }, "node-unzip-2": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/node-unzip-2/-/node-unzip-2-0.2.8.tgz", @@ -13450,6 +13785,25 @@ "rimraf": "^2.6.1" }, "dependencies": { + "cross-env": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-3.2.4.tgz", + "integrity": "sha1-ngWF8neGTtQhznVvgamA/w1piro=", + "requires": { + "cross-spawn": "^5.1.0", + "is-windows": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -13457,6 +13811,14 @@ "requires": { "glob": "^7.1.3" } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } } } }, @@ -14153,9 +14515,12 @@ } }, "pinejs-client-core": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/pinejs-client-core/-/pinejs-client-core-5.7.0.tgz", - "integrity": "sha512-89WcvoAXVP1ep0LcLa71du9OkVc0rJXcGNQgXiK9TdgLSBlUJSKFDGjnK8OKibkak0ydkjVghBRWEwlICcv7zw==" + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pinejs-client-core/-/pinejs-client-core-5.8.0.tgz", + "integrity": "sha512-O+tDtZMnj63WqMQd9LRJC6qJJn6fFHdpOt0Sm3W6MQWDUmFUu02VYR0p0PCegePC8XPp0TMR0TSejYRrg4NMtQ==", + "requires": { + "@balena/es-version": "^1.0.0" + } }, "pinkie": { "version": "2.0.4", @@ -14171,23 +14536,23 @@ } }, "pkg": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-4.4.2.tgz", - "integrity": "sha512-FEFX43fzHVyEl7fBTTaKxjN3OsWowNfcDGO7+NaxfUsMTMvy8aQX6DscjgoTNnbOehObRK/UqMUGKXt3mvnArg==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-4.4.8.tgz", + "integrity": "sha512-Fqqv0iaX48U3CFZxd6Dq6JKe7BrAWbgRAqMJkz/m8W3H5cqJ6suvsUWe5AJPRlN/AhbBYXBJ0XG9QlYPTXcVFA==", "dev": true, "requires": { - "@babel/parser": "^7.7.5", - "@babel/runtime": "^7.7.5", + "@babel/parser": "^7.9.4", + "@babel/runtime": "^7.9.2", "chalk": "^3.0.0", - "escodegen": "^1.12.0", + "escodegen": "^1.14.1", "fs-extra": "^8.1.0", - "globby": "^10.0.1", + "globby": "^11.0.0", "into-stream": "^5.1.1", - "minimist": "^1.2.0", + "minimist": "^1.2.5", "multistream": "^2.1.1", - "pkg-fetch": "^2.6.4", + "pkg-fetch": "^2.6.7", "progress": "^2.0.3", - "resolve": "^1.13.1", + "resolve": "^1.15.1", "stream-meter": "^1.0.4" }, "dependencies": { @@ -14233,14 +14598,6 @@ "merge2": "^1.3.0", "micromatch": "^4.0.2", "picomatch": "^2.2.1" - }, - "dependencies": { - "merge2": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", - "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", - "dev": true - } } }, "fill-range": { @@ -14253,27 +14610,25 @@ } }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", "dev": true, "requires": { - "@types/glob": "^7.1.1", "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", "slash": "^3.0.0" } }, @@ -14283,6 +14638,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", @@ -14293,6 +14654,12 @@ "picomatch": "^2.0.5" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -14300,9 +14667,9 @@ "dev": true }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -14371,17 +14738,17 @@ } }, "pkg-fetch": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-2.6.4.tgz", - "integrity": "sha512-4j4jiuo6RRIuD9e9xUE6OQYnIkQCArZjkHXNYsSJjxhJeiHE16MA+rENMblvGLbeWsTY3BPfcYVCGFXzpfJetA==", + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-2.6.8.tgz", + "integrity": "sha512-CFG7jOeVD38lltLGA7xCJxYsD//GKLjl1P9tc/n9By2a4WEHQjfkBMrYdMS8WOHVP+r9L20fsZNbaKcubDAiQg==", "dev": true, "requires": { - "@babel/runtime": "^7.7.5", + "@babel/runtime": "^7.9.2", "byline": "^5.0.0", "chalk": "^3.0.0", "expand-template": "^2.0.3", "fs-extra": "^8.1.0", - "minimist": "^1.2.0", + "minimist": "^1.2.5", "progress": "^2.0.3", "request": "^2.88.0", "request-progress": "^3.0.0", @@ -14389,6 +14756,12 @@ "unique-temp-dir": "^1.0.0" }, "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -14468,9 +14841,9 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "dev": true }, "pretty-bytes": { @@ -15081,55 +15454,6 @@ } } }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", @@ -15398,9 +15722,9 @@ } }, "regenerator-runtime": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", - "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", "dev": true }, "regex-not": { @@ -15592,6 +15916,12 @@ } } }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, "resin-bundle-resolve": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/resin-bundle-resolve/-/resin-bundle-resolve-4.3.0.tgz", @@ -15950,9 +16280,9 @@ } }, "roarr": { - "version": "2.15.2", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.2.tgz", - "integrity": "sha512-jmaDhK9CO4YbQAV8zzCnq9vjAqeO489MS5ehZ+rXmFiPFFE6B+S9KYO6prjmLJ5A0zY3QxVlQdrIya7E/azz/Q==", + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.3.tgz", + "integrity": "sha512-AEjYvmAhlyxOeB9OqPUzQCo3kuAkNfuDk/HqWbZdFsqDFpapkTjiw+p4svNEoRLvuqNTxqfL+s+gtD4eDgZ+CA==", "requires": { "boolean": "^3.0.0", "detect-node": "^2.0.4", @@ -16135,17 +16465,17 @@ } }, "serialize-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz", - "integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "requires": { - "type-fest": "^0.8.0" + "type-fest": "^0.13.1" }, "dependencies": { "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" } } }, @@ -17369,9 +17699,9 @@ "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" }, "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "to-object-path": { @@ -17478,9 +17808,9 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.2.tgz", + "integrity": "sha512-UyNrLdK3E0fQG/xWNqAFAC5ugtFyPO4JJR1KyyfQAyzR8W0fTRrC91A8Wej4BntFzcvETdCSDa/4PnNYJQLYiA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -17491,13 +17821,19 @@ "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", + "mkdirp": "^0.5.3", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.8.0", + "tslib": "^1.10.0", "tsutils": "^2.29.0" }, "dependencies": { + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -17515,6 +17851,21 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -18173,10 +18524,20 @@ } } }, + "vue-template-compiler": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", + "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, "walkdir": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.11.tgz", - "integrity": "sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", "dev": true }, "wcwidth": { diff --git a/package.json b/package.json index 676dff6b..a2c26734 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,9 @@ "build/auth/pages/*.ejs", "build/hooks", "node_modules/resin-discoverable-services/services/**/*", + "node_modules/balena-sdk/node_modules/balena-pine/**/*", + "node_modules/balena-pine/**/*", + "node_modules/pinejs-client-core/**/*", "node_modules/opn/xdg-open", "node_modules/open/xdg-open", "node_modules/windosu/*.bat", @@ -53,7 +56,10 @@ "package": "npm run build:fast && npm run build:standalone && npm run build:installer", "release": "ts-node --transpile-only automation/run.ts release", "pretest": "npm run build", - "test": "mocha --timeout 6000 -r ts-node/register/transpile-only \"tests/**/*.spec.ts\"", + "test": "npm run test:source && npm run test:standalone", + "test:source": "mocha --timeout 6000 -r ts-node/register/transpile-only \"tests/**/*.spec.ts\"", + "test:standalone": "npm run build:standalone && cross-env BALENA_CLI_TEST_TYPE=standalone npm run test:source", + "test:standalone:fast": "cross-env BALENA_CLI_TEST_TYPE=standalone npm run test:source", "test:fast": "npm run build:fast && mocha --timeout 6000 -r ts-node/register/transpile-only \"tests/**/*.spec.ts\"", "test:only": "npm run build:fast && mocha --timeout 6000 -r ts-node/register/transpile-only \"tests/**/${npm_config_test}.spec.ts\"", "catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted", @@ -75,7 +81,7 @@ "author": "Juan Cruz Viotti ", "license": "Apache-2.0", "engines": { - "node": ">=8.0" + "node": ">=10.0.0" }, "husky": { "hooks": { @@ -94,7 +100,7 @@ } }, "devDependencies": { - "@balena/lint": "^4.1.1", + "@balena/lint": "^5.1.0", "@oclif/config": "^1.14.0", "@oclif/dev-cli": "1.22.0", "@oclif/parser": "^3.8.4", @@ -114,6 +120,7 @@ "@types/fs-extra": "^8.1.0", "@types/global-agent": "^2.1.0", "@types/global-tunnel-ng": "^2.1.0", + "@types/http-proxy": "^1.17.4", "@types/intercept-stdout": "^0.1.0", "@types/is-root": "^2.1.2", "@types/js-yaml": "^3.12.3", @@ -145,18 +152,20 @@ "catch-uncommitted": "^1.5.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", + "cross-env": "^7.0.2", "ent": "^2.2.0", "filehound": "^1.17.4", "fs-extra": "^8.0.1", "gulp": "^4.0.1", "gulp-inline-source": "^2.1.0", + "http-proxy": "^1.18.1", "husky": "^4.2.5", "intercept-stdout": "^0.1.2", "mocha": "^6.2.3", "mock-require": "^3.0.3", "nock": "^12.0.3", "parse-link-header": "~1.0.1", - "pkg": "^4.4.2", + "pkg": "^4.4.8", "publish-release": "^1.6.1", "rewire": "^4.0.1", "simple-git": "^1.131.0", @@ -181,7 +190,7 @@ "balena-image-manager": "^6.1.2", "balena-preload": "^8.4.0", "balena-release": "^2.1.0", - "balena-sdk": "^12.33.0", + "balena-sdk": "^13.6.0", "balena-semver": "^2.2.0", "balena-settings-client": "^4.0.5", "balena-sync": "^10.2.0", @@ -206,7 +215,7 @@ "express": "^4.13.3", "fast-boot2": "^1.0.9", "get-stdin": "^7.0.0", - "global-agent": "^2.1.8", + "global-agent": "^2.1.12", "global-tunnel-ng": "^2.1.1", "humanize": "0.0.9", "ignore": "^5.1.4", diff --git a/patches/pkg+4.4.2.dev.patch b/patches/pkg+4.4.8.dev.patch similarity index 87% rename from patches/pkg+4.4.2.dev.patch rename to patches/pkg+4.4.8.dev.patch index a4f355e4..5db5e8ec 100644 --- a/patches/pkg+4.4.2.dev.patch +++ b/patches/pkg+4.4.8.dev.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/pkg/lib-es5/packer.js b/node_modules/pkg/lib-es5/packer.js -index 607c847..4e3fb55 100644 +index 7295bb6..76805a3 100644 --- a/node_modules/pkg/lib-es5/packer.js +++ b/node_modules/pkg/lib-es5/packer.js @@ -128,6 +128,7 @@ function _default({ @@ -11,10 +11,10 @@ index 607c847..4e3fb55 100644 stripes.push({ snap, diff --git a/node_modules/pkg/prelude/bootstrap.js b/node_modules/pkg/prelude/bootstrap.js -index 216579e..5cff8a8 100644 +index 0d19f1d..db69015 100644 --- a/node_modules/pkg/prelude/bootstrap.js +++ b/node_modules/pkg/prelude/bootstrap.js -@@ -866,8 +866,10 @@ function payloadFileSync (pointer) { +@@ -925,8 +925,10 @@ function payloadFileSync (pointer) { var isFileValue = s.isFileValue; var isDirectoryValue = s.isDirectoryValue; @@ -25,7 +25,7 @@ index 216579e..5cff8a8 100644 s.isFile = function () { return isFileValue; -@@ -875,6 +877,9 @@ function payloadFileSync (pointer) { +@@ -934,6 +936,9 @@ function payloadFileSync (pointer) { s.isDirectory = function () { return isDirectoryValue; }; diff --git a/tests/app-common.spec.ts b/tests/app-common.spec.ts index 72510c03..25b73590 100644 --- a/tests/app-common.spec.ts +++ b/tests/app-common.spec.ts @@ -22,7 +22,7 @@ import { makeUrlFromTunnelNgConfig, } from '../build/app-common'; -describe('makeUrlFromTunnelNgConfig() function', function() { +describe('makeUrlFromTunnelNgConfig() function', function () { it('should return a URL given a GlobalTunnelNgConfig object', () => { const tunnelNgConfig: GlobalTunnelNgConfig = { host: 'proxy.company.com', diff --git a/tests/auth/server.spec.ts b/tests/auth/server.spec.ts index 6f6465d9..f178ba55 100644 --- a/tests/auth/server.spec.ts +++ b/tests/auth/server.spec.ts @@ -51,8 +51,8 @@ async function getPage(name: string): Promise { return compiledTpl(); } -describe('Server:', function() { - it('should get 404 if posting to an unknown path', function(done) { +describe('Server:', function () { + it('should get 404 if posting to an unknown path', function (done) { const promise = server.awaitForToken(options); expect(promise).to.be.rejectedWith('Unknown path or verb'); @@ -63,7 +63,7 @@ describe('Server:', function() { token: tokens.johndoe.token, }, }, - function(error, response, body) { + function (error, response, body) { expect(error).to.not.exist; expect(response.statusCode).to.equal(404); expect(body).to.equal('Not found'); @@ -72,7 +72,7 @@ describe('Server:', function() { ); }); - it('should get 404 if not using the correct verb', function(done) { + it('should get 404 if not using the correct verb', function (done) { const promise = server.awaitForToken(options); expect(promise).to.be.rejectedWith('Unknown path or verb'); @@ -83,7 +83,7 @@ describe('Server:', function() { token: tokens.johndoe.token, }, }, - function(error, response, body) { + function (error, response, body) { expect(error).to.not.exist; expect(response.statusCode).to.equal(404); expect(body).to.equal('Not found'); @@ -92,17 +92,17 @@ describe('Server:', function() { ); }); - describe('given the token authenticates with the server', function() { - beforeEach(function() { + describe('given the token authenticates with the server', function () { + beforeEach(function () { this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid'); return this.loginIfTokenValidStub.returns(Bluebird.resolve(true)); }); - afterEach(function() { + afterEach(function () { return this.loginIfTokenValidStub.restore(); }); - return it('should eventually be the token', function(done) { + return it('should eventually be the token', function (done) { const promise = server.awaitForToken(options); expect(promise).to.eventually.equal(tokens.johndoe.token); @@ -113,10 +113,10 @@ describe('Server:', function() { token: tokens.johndoe.token, }, }, - function(error, response, body) { + function (error, response, body) { expect(error).to.not.exist; expect(response.statusCode).to.equal(200); - return getPage('success').then(function(expectedBody) { + return getPage('success').then(function (expectedBody) { expect(body).to.equal(expectedBody); return done(); }); @@ -125,17 +125,17 @@ describe('Server:', function() { }); }); - return describe('given the token does not authenticate with the server', function() { - beforeEach(function() { + return describe('given the token does not authenticate with the server', function () { + beforeEach(function () { this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid'); return this.loginIfTokenValidStub.returns(Bluebird.resolve(false)); }); - afterEach(function() { + afterEach(function () { return this.loginIfTokenValidStub.restore(); }); - it('should be rejected', function(done) { + it('should be rejected', function (done) { const promise = server.awaitForToken(options); expect(promise).to.be.rejectedWith('Invalid token'); @@ -146,10 +146,10 @@ describe('Server:', function() { token: tokens.johndoe.token, }, }, - function(error, response, body) { + function (error, response, body) { expect(error).to.not.exist; expect(response.statusCode).to.equal(401); - return getPage('error').then(function(expectedBody) { + return getPage('error').then(function (expectedBody) { expect(body).to.equal(expectedBody); return done(); }); @@ -157,7 +157,7 @@ describe('Server:', function() { ); }); - it('should be rejected if no token', function(done) { + it('should be rejected if no token', function (done) { const promise = server.awaitForToken(options); expect(promise).to.be.rejectedWith('No token'); @@ -168,10 +168,10 @@ describe('Server:', function() { token: '', }, }, - function(error, response, body) { + function (error, response, body) { expect(error).to.not.exist; expect(response.statusCode).to.equal(401); - return getPage('error').then(function(expectedBody) { + return getPage('error').then(function (expectedBody) { expect(body).to.equal(expectedBody); return done(); }); @@ -179,7 +179,7 @@ describe('Server:', function() { ); }); - return it('should be rejected if token is malformed', function(done) { + return it('should be rejected if token is malformed', function (done) { const promise = server.awaitForToken(options); expect(promise).to.be.rejectedWith('Invalid token'); @@ -190,10 +190,10 @@ describe('Server:', function() { token: 'asdf', }, }, - function(error, response, body) { + function (error, response, body) { expect(error).to.not.exist; expect(response.statusCode).to.equal(401); - return getPage('error').then(function(expectedBody) { + return getPage('error').then(function (expectedBody) { expect(body).to.equal(expectedBody); return done(); }); diff --git a/tests/auth/utils.spec.ts b/tests/auth/utils.spec.ts index 6e339bb1..8536bac6 100644 --- a/tests/auth/utils.spec.ts +++ b/tests/auth/utils.spec.ts @@ -9,8 +9,8 @@ import tokens from './tokens'; const utils = rewire('../../build/auth/utils'); const balena = getBalenaSdk(); -describe('Utils:', function() { - describe('.getDashboardLoginURL()', function() { +describe('Utils:', function () { + describe('.getDashboardLoginURL()', function () { it('should eventually be a valid url', () => utils .getDashboardLoginURL('https://127.0.0.1:3000/callback') @@ -22,7 +22,7 @@ describe('Utils:', function() { Promise.props({ dashboardUrl: balena.settings.get('dashboardUrl'), loginUrl: utils.getDashboardLoginURL('https://127.0.0.1:3000/callback'), - }).then(function({ dashboardUrl, loginUrl }) { + }).then(function ({ dashboardUrl, loginUrl }) { const { protocol } = url.parse(loginUrl); return expect(protocol).to.equal(url.parse(dashboardUrl).protocol); })); @@ -31,7 +31,7 @@ describe('Utils:', function() { Promise.props({ dashboardUrl: balena.settings.get('dashboardUrl'), loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000'), - }).then(function({ dashboardUrl, loginUrl }) { + }).then(function ({ dashboardUrl, loginUrl }) { const expectedUrl = `${dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000`; return expect(loginUrl).to.equal(expectedUrl); })); @@ -40,55 +40,55 @@ describe('Utils:', function() { Promise.props({ dashboardUrl: balena.settings.get('dashboardUrl'), loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000/callback'), - }).then(function({ dashboardUrl, loginUrl }) { + }).then(function ({ dashboardUrl, loginUrl }) { const expectedUrl = `${dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000%252Fcallback`; return expect(loginUrl).to.equal(expectedUrl); })); }); - return describe('.loginIfTokenValid()', function() { - it('should eventually be false if token is undefined', function() { + return describe('.loginIfTokenValid()', function () { + it('should eventually be false if token is undefined', function () { const promise = utils.loginIfTokenValid(undefined); return expect(promise).to.eventually.be.false; }); - it('should eventually be false if token is null', function() { + it('should eventually be false if token is null', function () { const promise = utils.loginIfTokenValid(null); return expect(promise).to.eventually.be.false; }); - it('should eventually be false if token is an empty string', function() { + it('should eventually be false if token is an empty string', function () { const promise = utils.loginIfTokenValid(''); return expect(promise).to.eventually.be.false; }); - it('should eventually be false if token is a string containing only spaces', function() { + it('should eventually be false if token is a string containing only spaces', function () { const promise = utils.loginIfTokenValid(' '); return expect(promise).to.eventually.be.false; }); - describe('given the token does not authenticate with the server', function() { - beforeEach(function() { + describe('given the token does not authenticate with the server', function () { + beforeEach(function () { this.balenaAuthIsLoggedInStub = sinon.stub(balena.auth, 'isLoggedIn'); return this.balenaAuthIsLoggedInStub.returns(Promise.resolve(false)); }); - afterEach(function() { + afterEach(function () { return this.balenaAuthIsLoggedInStub.restore(); }); - it('should eventually be false', function() { + it('should eventually be false', function () { const promise = utils.loginIfTokenValid(tokens.johndoe.token); return expect(promise).to.eventually.be.false; }); - describe('given there was a token already', function() { + describe('given there was a token already', function () { beforeEach(() => balena.auth.loginWithToken(tokens.janedoe.token)); return it('should preserve the old token', () => balena.auth .getToken() - .then(function(originalToken: string) { + .then(function (originalToken: string) { expect(originalToken).to.equal(tokens.janedoe.token); return utils.loginIfTokenValid(tokens.johndoe.token); }) @@ -98,7 +98,7 @@ describe('Utils:', function() { )); }); - return describe('given there was no token', function() { + return describe('given there was no token', function () { beforeEach(() => balena.auth.logout()); return it('should stay without a token', () => @@ -109,17 +109,17 @@ describe('Utils:', function() { }); }); - return describe('given the token does authenticate with the server', function() { - beforeEach(function() { + return describe('given the token does authenticate with the server', function () { + beforeEach(function () { this.balenaAuthIsLoggedInStub = sinon.stub(balena.auth, 'isLoggedIn'); return this.balenaAuthIsLoggedInStub.returns(Promise.resolve(true)); }); - afterEach(function() { + afterEach(function () { return this.balenaAuthIsLoggedInStub.restore(); }); - return it('should eventually be true', function() { + return it('should eventually be true', function () { const promise = utils.loginIfTokenValid(tokens.johndoe.token); return expect(promise).to.eventually.be.true; }); diff --git a/tests/balena-api-mock.ts b/tests/balena-api-mock.ts index 891fb661..eabac375 100644 --- a/tests/balena-api-mock.ts +++ b/tests/balena-api-mock.ts @@ -28,7 +28,7 @@ const jHeader = { 'Content-Type': 'application/json' }; export class BalenaAPIMock extends NockMock { constructor() { - super('https://api.balena-cloud.com'); + super(/api\.balena-cloud\.com/); } public expectGetApplication(opts: ScopeOpts = {}) { @@ -172,6 +172,17 @@ export class BalenaAPIMock extends NockMock { }); } + public expectGetDeviceStatus(opts: ScopeOpts = {}) { + this.optGet( + /^\/v\d+\/device\?.+&\$select=overall_status$/, + opts, + ).replyWithFile( + 200, + path.join(apiResponsePath, 'device-status.json'), + jHeader, + ); + } + public expectGetAppEnvVars(opts: ScopeOpts = {}) { this.optGet(/^\/v\d+\/application_environment_variable($|\?)/, opts).reply( 200, @@ -206,7 +217,7 @@ export class BalenaAPIMock extends NockMock { public expectGetAppServiceVars(opts: ScopeOpts = {}) { this.optGet(/^\/v\d+\/service_environment_variable($|\?)/, opts).reply( - function(uri, _requestBody) { + function (uri, _requestBody) { const match = uri.match(/service_name%20eq%20%27(.+?)%27/); const serviceName = (match && match[1]) || undefined; let varArray: any[]; @@ -214,7 +225,7 @@ export class BalenaAPIMock extends NockMock { const varObj = appServiceVarsByService[serviceName]; varArray = varObj ? [varObj] : []; } else { - varArray = _.map(appServiceVarsByService, value => value); + varArray = _.map(appServiceVarsByService, (value) => value); } return [200, { d: varArray }]; }, @@ -254,7 +265,7 @@ export class BalenaAPIMock extends NockMock { this.optGet( /^\/v\d+\/device_service_environment_variable($|\?)/, opts, - ).reply(function(uri, _requestBody) { + ).reply(function (uri, _requestBody) { const match = uri.match(/service_name%20eq%20%27(.+?)%27/); const serviceName = (match && match[1]) || undefined; let varArray: any[]; @@ -262,7 +273,7 @@ export class BalenaAPIMock extends NockMock { const varObj = deviceServiceVarsByService[serviceName]; varArray = varObj ? [varObj] : []; } else { - varArray = _.map(deviceServiceVarsByService, value => value); + varArray = _.map(deviceServiceVarsByService, (value) => value); } return [200, { d: varArray }]; }); diff --git a/tests/builder-mock.ts b/tests/builder-mock.ts index e75d2c1d..f77ba2d2 100644 --- a/tests/builder-mock.ts +++ b/tests/builder-mock.ts @@ -27,7 +27,7 @@ export const builderResponsePath = path.normalize( export class BuilderMock extends NockMock { constructor() { - super('https://builder.balena-cloud.com'); + super(/builder\.balena-cloud\.com/); } public expectPostBuild(opts: { @@ -38,7 +38,7 @@ export class BuilderMock extends NockMock { checkURI: (uri: string) => Promise; checkBuildRequestBody: (requestBody: string | Buffer) => Promise; }) { - this.optPost(/^\/v3\/build($|[(?])/, opts).reply(async function( + this.optPost(/^\/v3\/build($|[(?])/, opts).reply(async function ( uri, requestBody, callback, @@ -48,7 +48,7 @@ export class BuilderMock extends NockMock { await opts.checkURI(uri); if (typeof requestBody === 'string') { const gzipped = Buffer.from(requestBody, 'hex'); - const gunzipped = await Bluebird.fromCallback(cb => { + const gunzipped = await Bluebird.fromCallback((cb) => { zlib.gunzip(gzipped, cb); }); await opts.checkBuildRequestBody(gunzipped); diff --git a/tests/commands/app/create.spec.ts b/tests/commands/app/create.spec.ts index 7a0fda47..7ed25dc3 100644 --- a/tests/commands/app/create.spec.ts +++ b/tests/commands/app/create.spec.ts @@ -41,7 +41,7 @@ Options: --type, -t application device type (Check available types with \`balena devices supported\`) `; -describe('balena app create', function() { +describe('balena app create', function () { let api: BalenaAPIMock; beforeEach(() => { diff --git a/tests/commands/build.spec.ts b/tests/commands/build.spec.ts index 7eb2b3e0..974a2343 100644 --- a/tests/commands/build.spec.ts +++ b/tests/commands/build.spec.ts @@ -62,7 +62,10 @@ const commonComposeQueryParams = [ ['labels', ''], ]; -describe('balena build', function() { +// "itSS" means "it() Skip Standalone" +const itSS = process.env.BALENA_CLI_TEST_TYPE === 'standalone' ? it.skip : it; + +describe('balena build', function () { let api: BalenaAPIMock; let docker: DockerMock; const isWindows = process.platform === 'win32'; @@ -135,7 +138,7 @@ describe('balena build', function() { }); }); - it('should create the expected tar stream (--emulated)', async () => { + itSS('should create the expected tar stream (--emulated)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const isV12W = isWindows && isV12(); const transposedDockerfile = @@ -357,7 +360,7 @@ describe('balena build', function() { }); }); -describe('balena build: project validation', function() { +describe('balena build: project validation', function () { it('should raise ExpectedError if a Dockerfile cannot be found', async () => { const projectPath = path.join( projectsPath, @@ -370,10 +373,10 @@ describe('balena build: project validation', function() { `found in source folder "${projectPath}"`, ]; - const { out, err } = await runCommand(`build ${projectPath} -a testApp`); - expect( - cleanOutput(err).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedErrorLines); + const { out, err } = await runCommand( + `build ${projectPath} -A amd64 -d nuc`, + ); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); expect(out).to.be.empty; }); }); diff --git a/tests/commands/deploy.spec.ts b/tests/commands/deploy.spec.ts index be96d28a..a1ae5a6f 100644 --- a/tests/commands/deploy.spec.ts +++ b/tests/commands/deploy.spec.ts @@ -56,7 +56,7 @@ const commonQueryParams = [ ['labels', ''], ]; -describe('balena deploy', function() { +describe('balena deploy', function () { let api: BalenaAPIMock; let docker: DockerMock; let sentryStatus: boolean | undefined; @@ -173,6 +173,9 @@ describe('balena deploy', function() { const expectedResponseLines = ['[Error] Deploy failed']; const errMsg = 'Patch Image Error'; const expectedErrorLines = [errMsg]; + // The SDK should produce an "unexpected" BalenaRequestError, which + // causes the CLI to call process.exit() with process.exitCode = 1 + const expectedExitCode = 1; // Mock this patch HTTP request to return status code 500, in which case // the release status should be saved as "failed" rather than "success" @@ -202,21 +205,29 @@ describe('balena deploy', function() { expectedFilesByService: { main: expectedFiles }, expectedQueryParamsByService: { main: commonQueryParams }, expectedErrorLines, + expectedExitCode, expectedResponseLines, projectPath, responseBody, responseCode: 200, services: ['main'], }); - // The SDK should produce an "unexpected" BalenaRequestError, which - // causes the CLI to call process.exit() with process.exitCode = 1 - // @ts-ignore - sinon.assert.calledWith(process.exit); - expect(process.exitCode).to.equal(1); }); }); -describe('balena deploy: project validation', function() { +describe('balena deploy: project validation', function () { + let api: BalenaAPIMock; + this.beforeEach(() => { + api = new BalenaAPIMock(); + api.expectGetWhoAmI({ optional: true, persist: true }); + api.expectGetMixpanel({ optional: true }); + }); + + this.afterEach(() => { + // Check all expected api calls have been made and clean up. + api.done(); + }); + it('should raise ExpectedError if a Dockerfile cannot be found', async () => { const projectPath = path.join( projectsPath, @@ -232,9 +243,7 @@ describe('balena deploy: project validation', function() { const { out, err } = await runCommand( `deploy testApp --source ${projectPath}`, ); - expect( - cleanOutput(err).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedErrorLines); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); expect(out).to.be.empty; }); }); diff --git a/tests/commands/device/device-move.spec.ts b/tests/commands/device/device-move.spec.ts index dc761521..41591119 100644 --- a/tests/commands/device/device-move.spec.ts +++ b/tests/commands/device/device-move.spec.ts @@ -36,7 +36,7 @@ Options: --application, -a, --app application name `; -describe('balena device move', function() { +describe('balena device move', function () { let api: BalenaAPIMock; beforeEach(() => { diff --git a/tests/commands/device/device.spec.ts b/tests/commands/device/device.spec.ts index 15fc0b4d..7f6b71b1 100644 --- a/tests/commands/device/device.spec.ts +++ b/tests/commands/device/device.spec.ts @@ -31,7 +31,7 @@ Examples: \t$ balena device 7cf02a6 `; -describe('balena device', function() { +describe('balena device', function () { let api: BalenaAPIMock; beforeEach(() => { @@ -68,11 +68,13 @@ describe('balena device', function() { }); it('should list device details for provided uuid', async () => { - api.expectGetWhoAmI({ optional: true }); + api.expectGetWhoAmI({ optional: true, persist: true }); api.expectGetMixpanel({ optional: true }); - + api.expectGetDeviceStatus(); api.scope - .get(/^\/v5\/device/) + .get( + /^\/v5\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/, + ) .replyWithFile(200, path.join(apiResponsePath, 'device.json'), { 'Content-Type': 'application/json', }); @@ -91,11 +93,13 @@ describe('balena device', function() { it('correctly handles devices with missing application', async () => { // Devices with missing applications will have application name set to `N/a`. // e.g. When user has a device associated with app that user is no longer a collaborator of. - api.expectGetWhoAmI({ optional: true }); + api.expectGetWhoAmI({ optional: true, persist: true }); api.expectGetMixpanel({ optional: true }); - + api.expectGetDeviceStatus(); api.scope - .get(/^\/v5\/device/) + .get( + /^\/v5\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/, + ) .replyWithFile( 200, path.join(apiResponsePath, 'device-missing-app.json'), diff --git a/tests/commands/device/devices.spec.ts b/tests/commands/device/devices.spec.ts index c122ea48..a600ad7d 100644 --- a/tests/commands/device/devices.spec.ts +++ b/tests/commands/device/devices.spec.ts @@ -40,7 +40,7 @@ Options: --application, -a, --app application name `; -describe('balena devices', function() { +describe('balena devices', function () { let api: BalenaAPIMock; beforeEach(() => { @@ -64,7 +64,7 @@ describe('balena devices', function() { }); it('should list devices from own and collaborator apps', async () => { - api.expectGetWhoAmI({ optional: true }); + api.expectGetWhoAmI({ optional: true, persist: true }); api.expectGetMixpanel({ optional: true }); api.scope @@ -85,11 +85,11 @@ describe('balena devices', function() { ); expect(lines).to.have.lengthOf.at.least(2); - expect(lines.some(l => l.includes('test app'))).to.be.true; + expect(lines.some((l) => l.includes('test app'))).to.be.true; // Devices with missing applications will have application name set to `N/a`. // e.g. When user has a device associated with app that user is no longer a collaborator of. - expect(lines.some(l => l.includes('N/a'))).to.be.true; + expect(lines.some((l) => l.includes('N/a'))).to.be.true; expect(err).to.eql([]); }); diff --git a/tests/commands/device/supported.spec.ts b/tests/commands/device/supported.spec.ts index 19d91e66..a66c7127 100644 --- a/tests/commands/device/supported.spec.ts +++ b/tests/commands/device/supported.spec.ts @@ -21,7 +21,7 @@ import { isV12 } from '../../../build/utils/version'; import { BalenaAPIMock } from '../../balena-api-mock'; import { cleanOutput, runCommand } from '../../helpers'; -describe('balena devices supported', function() { +describe('balena devices supported', function () { let api: BalenaAPIMock; beforeEach(() => { @@ -59,11 +59,11 @@ describe('balena devices supported', function() { expect(lines).to.have.lengthOf.at.least(2); // Discontinued devices should be filtered out from results - expect(lines.some(l => l.includes('DISCONTINUED'))).to.be.false; + expect(lines.some((l) => l.includes('DISCONTINUED'))).to.be.false; // Experimental devices should be listed as beta - expect(lines.some(l => l.includes('EXPERIMENTAL'))).to.be.false; - expect(lines.some(l => l.includes('NEW'))).to.be.true; + expect(lines.some((l) => l.includes('EXPERIMENTAL'))).to.be.false; + expect(lines.some((l) => l.includes('NEW'))).to.be.true; expect(err).to.eql([]); }); diff --git a/tests/commands/env/add.spec.ts b/tests/commands/env/add.spec.ts index 87ed7ddf..9032d916 100644 --- a/tests/commands/env/add.spec.ts +++ b/tests/commands/env/add.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import { BalenaAPIMock } from '../../balena-api-mock'; import { runCommand } from '../../helpers'; -describe('balena env add', function() { +describe('balena env add', function () { let api: BalenaAPIMock; beforeEach(() => { diff --git a/tests/commands/env/envs.spec.ts b/tests/commands/env/envs.spec.ts index 5c0c21ef..ca5284fd 100644 --- a/tests/commands/env/envs.spec.ts +++ b/tests/commands/env/envs.spec.ts @@ -22,7 +22,7 @@ import { isV12 } from '../../../lib/utils/version'; import { BalenaAPIMock } from '../../balena-api-mock'; import { runCommand } from '../../helpers'; -describe('balena envs', function() { +describe('balena envs', function () { const appName = 'test'; let fullUUID: string; let shortUUID: string; @@ -33,9 +33,7 @@ describe('balena envs', function() { api.expectGetWhoAmI({ optional: true, persist: true }); api.expectGetMixpanel({ optional: true }); // Random device UUID used to frustrate _.memoize() in utils/cloud.ts - fullUUID = require('crypto') - .randomBytes(16) - .toString('hex'); + fullUUID = require('crypto').randomBytes(16).toString('hex'); shortUUID = fullUUID.substring(0, 7); }); diff --git a/tests/commands/env/rename.spec.ts b/tests/commands/env/rename.spec.ts index 27d632e9..30f55cbc 100644 --- a/tests/commands/env/rename.spec.ts +++ b/tests/commands/env/rename.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import { BalenaAPIMock } from '../../balena-api-mock'; import { runCommand } from '../../helpers'; -describe('balena env rename', function() { +describe('balena env rename', function () { let api: BalenaAPIMock; beforeEach(() => { diff --git a/tests/commands/env/rm.spec.ts b/tests/commands/env/rm.spec.ts index e2e9b4bc..b92746b9 100644 --- a/tests/commands/env/rm.spec.ts +++ b/tests/commands/env/rm.spec.ts @@ -20,7 +20,7 @@ import { expect } from 'chai'; import { BalenaAPIMock } from '../../balena-api-mock'; import { runCommand } from '../../helpers'; -describe('balena env rm', function() { +describe('balena env rm', function () { let api: BalenaAPIMock; beforeEach(() => { diff --git a/tests/commands/help.spec.ts b/tests/commands/help.spec.ts index 2ad6110b..56e40c80 100644 --- a/tests/commands/help.spec.ts +++ b/tests/commands/help.spec.ts @@ -92,7 +92,7 @@ const ONLINE_RESOURCES = ` For bug reports or feature requests, see: https://github.com/balena-io/balena-cli/issues/ `; -describe('balena help', function() { +describe('balena help', function () { it('should list primary command summaries', async () => { const { out, err } = await runCommand('help'); diff --git a/tests/commands/logs.spec.ts b/tests/commands/logs.spec.ts new file mode 100644 index 00000000..49148920 --- /dev/null +++ b/tests/commands/logs.spec.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { BalenaAPIMock } from '../balena-api-mock'; +import { cleanOutput, runCommand } from '../helpers'; +import { SupervisorMock } from '../supervisor-mock'; + +const itS = process.env.BALENA_CLI_TEST_TYPE === 'standalone' ? it : it.skip; + +describe('balena logs', function () { + let api: BalenaAPIMock; + let supervisor: SupervisorMock; + + this.beforeEach(() => { + api = new BalenaAPIMock(); + supervisor = new SupervisorMock(); + api.expectGetWhoAmI(); + api.expectGetMixpanel({ optional: true }); + }); + + this.afterEach(() => { + // Check all expected api calls have been made and clean up. + api.done(); + supervisor.done(); + }); + + // skip non-standalone tests because nock's mock socket causes the error: + // "setKeepAliveInterval expects an instance of socket as its first argument" + // in utils/device/api.ts: NetKeepalive.setKeepAliveInterval(sock, 5000); + itS('should reach the expected endpoints on a local device', async () => { + supervisor.expectGetPing(); + supervisor.expectGetLogs(); + + const { err, out } = await runCommand('logs 1.2.3.4'); + + expect(err).to.be.empty; + + const removeTimestamps = (logLine: string) => + logLine.replace(/(?<=\[Logs\]) \[.+?\]/, ''); + const cleanedOut = cleanOutput(out, true).map((l) => removeTimestamps(l)); + + expect(cleanedOut).to.deep.equal([ + '[Logs] Streaming logs', + '[Logs] [bar] bar 8 (332) Linux 4e3f81149d71 4.19.75 #1 SMP PREEMPT Mon Mar 23 11:50:49 UTC 2020 aarch64 GNU/Linux', + '[Logs] [foo] foo 8 (200) Linux cc5df60d89ee 4.19.75 #1 SMP PREEMPT Mon Mar 23 11:50:49 UTC 2020 aarch64 GNU/Linux', + '[Error] Connection to device lost', + ]); + }); +}); diff --git a/tests/commands/os/configure.spec.ts b/tests/commands/os/configure.spec.ts index 406ac945..6770cad1 100644 --- a/tests/commands/os/configure.spec.ts +++ b/tests/commands/os/configure.spec.ts @@ -6,7 +6,7 @@ import { runCommand } from '../../helpers'; import { BalenaAPIMock } from '../../balena-api-mock'; if (process.platform !== 'win32') { - describe('balena os configure', function() { + describe('balena os configure', function () { let api: BalenaAPIMock; beforeEach(async () => { diff --git a/tests/commands/push.spec.ts b/tests/commands/push.spec.ts index 8c3a7aec..f631ac79 100644 --- a/tests/commands/push.spec.ts +++ b/tests/commands/push.spec.ts @@ -80,7 +80,7 @@ const commonQueryParams = [ const itSkipWindows = process.platform === 'win32' ? it.skip : it; -describe('balena push', function() { +describe('balena push', function () { let api: BalenaAPIMock; let builder: BuilderMock; const isWindows = process.platform === 'win32'; @@ -167,7 +167,7 @@ describe('balena push', function() { path.join(builderResponsePath, responseFilename), 'utf8', ); - const expectedQueryParams = commonQueryParams.map(i => + const expectedQueryParams = commonQueryParams.map((i) => i[0] === 'dockerfilePath' ? ['dockerfilePath', 'Dockerfile-alt'] : i, ); @@ -471,7 +471,7 @@ describe('balena push', function() { }); }); -describe('balena push: project validation', function() { +describe('balena push: project validation', function () { it('should raise ExpectedError if the project folder is not a directory', async () => { const projectPath = path.join( projectsPath, @@ -486,9 +486,7 @@ describe('balena push: project validation', function() { const { out, err } = await runCommand( `push testApp --source ${projectPath} --nogitignore`, ); - expect( - cleanOutput(err).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedErrorLines); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); expect(out).to.be.empty; }); @@ -507,9 +505,7 @@ describe('balena push: project validation', function() { const { out, err } = await runCommand( `push testApp --source ${projectPath}`, ); - expect( - cleanOutput(err).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedErrorLines); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); expect(out).to.be.empty; }); @@ -536,12 +532,8 @@ describe('balena push: project validation', function() { const { out, err } = await runCommand( `push testApp --source ${projectPath} --nolive`, ); - expect( - cleanOutput(err).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedErrorLines); - expect( - cleanOutput(out).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedOutputLines); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); + expect(cleanOutput(out, true)).to.include.members(expectedOutputLines); }); it('should suppress a parent folder check with --noparent-check', async () => { @@ -558,9 +550,7 @@ describe('balena push: project validation', function() { const { out, err } = await runCommand( `push testApp --source ${projectPath} --nolive --noparent-check`, ); - expect( - cleanOutput(err).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedErrorLines); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); expect(out).to.be.empty; }); }); diff --git a/tests/commands/version.spec.ts b/tests/commands/version.spec.ts index 08012c60..55031e93 100644 --- a/tests/commands/version.spec.ts +++ b/tests/commands/version.spec.ts @@ -1,3 +1,19 @@ +/** + * @license + * Copyright 2019-2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { expect } from 'chai'; import * as fs from 'fs'; import { runCommand } from '../helpers'; @@ -7,31 +23,37 @@ const nodeVersion = process.version.startsWith('v') ? process.version.slice(1) : process.version; -describe('balena version', function() { +describe('balena version', function () { it('should print the installed version of the CLI', async () => { - const { out } = await runCommand('version'); - + const { err, out } = await runCommand('version'); + expect(err).to.be.empty; expect(out.join('')).to.equal(`${packageJSON.version}\n`); }); it('should print additional version information with the -a flag', async () => { - const { out } = await runCommand('version -a'); - - expect(out.join('')).to.equal( - `balena-cli version "${packageJSON.version}" -Node.js version "${nodeVersion}" -`, + const { err, out } = await runCommand('version -a'); + expect(err).to.be.empty; + expect(out[0].trim()).to.equal( + `balena-cli version "${packageJSON.version}"`, ); + + if (process.env.BALENA_CLI_TEST_TYPE === 'standalone') { + expect(out[1]).to.match(/Node.js version "\d+\.\d+.\d+"/); + } else { + expect(out[1].trim()).to.equal(`Node.js version "${nodeVersion}"`); + } }); it('should print version information as JSON with the the -j flag', async () => { - const { out } = await runCommand('version -j'); - + const { err, out } = await runCommand('version -j'); + expect(err).to.be.empty; const json = JSON.parse(out.join('')); + expect(json['balena-cli']).to.equal(packageJSON.version); - expect(json).to.deep.equal({ - 'balena-cli': packageJSON.version, - 'Node.js': nodeVersion, - }); + if (process.env.BALENA_CLI_TEST_TYPE === 'standalone') { + expect(json['Node.js']).to.match(/\d+\.\d+.\d+/); + } else { + expect(json['Node.js']).to.equal(nodeVersion); + } }); }); diff --git a/tests/docker-build.ts b/tests/docker-build.ts index 819df91a..37650872 100644 --- a/tests/docker-build.ts +++ b/tests/docker-build.ts @@ -21,6 +21,7 @@ import * as _ from 'lodash'; import { fs } from 'mz'; import * as path from 'path'; import { PathUtils } from 'resin-multibuild'; +import * as sinon from 'sinon'; import { Readable } from 'stream'; import * as tar from 'tar-stream'; import { streamToBuffer } from 'tar-utils'; @@ -88,7 +89,7 @@ export async function inspectTarStream( }); expect(found).to.deep.equal( - _.mapValues(expectedFiles, v => _.omit(v, 'testStream', 'contents')), + _.mapValues(expectedFiles, (v) => _.omit(v, 'testStream', 'contents')), ); } @@ -139,6 +140,7 @@ export async function testDockerBuildStream(o: { expectedFilesByService: ExpectedTarStreamFilesByService; expectedQueryParamsByService: { [service: string]: string[][] }; expectedErrorLines?: string[]; + expectedExitCode?: number; expectedResponseLines: string[]; projectPath: string; responseCode: number; @@ -174,21 +176,25 @@ export async function testDockerBuildStream(o: { o.dockerMock.expectGetImages(); } - const { out, err } = await runCommand(o.commandLine); - - const cleanLines = (lines: string[]) => - cleanOutput(lines).map(line => line.replace(/\s{2,}/g, ' ')); + const { exitCode, out, err } = await runCommand(o.commandLine); if (expectedErrorLines.length) { - expect(cleanLines(err)).to.include.members(expectedErrorLines); + expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); } else { expect(err).to.be.empty; } if (expectedResponseLines.length) { - expect(cleanLines(out)).to.include.members(expectedResponseLines); + expect(cleanOutput(out, true)).to.include.members(expectedResponseLines); } else { expect(out).to.be.empty; } + if (o.expectedExitCode != null) { + if (process.env.BALENA_CLI_TEST_TYPE !== 'standalone') { + // @ts-ignore + sinon.assert.calledWith(process.exit); + } + expect(o.expectedExitCode).to.equal(exitCode); + } } /** @@ -214,14 +220,12 @@ export async function testPushBuildStream(o: { const queryParams = Array.from(url.searchParams.entries()); expect(queryParams).to.have.deep.members(expectedQueryParams); }, - checkBuildRequestBody: buildRequestBody => + checkBuildRequestBody: (buildRequestBody) => inspectTarStream(buildRequestBody, o.expectedFiles, o.projectPath), }); const { out, err } = await runCommand(o.commandLine); expect(err).to.be.empty; - expect( - cleanOutput(out).map(line => line.replace(/\s{2,}/g, ' ')), - ).to.include.members(expectedResponseLines); + expect(cleanOutput(out, true)).to.include.members(expectedResponseLines); } diff --git a/tests/docker-mock.ts b/tests/docker-mock.ts index 5b8da0d4..8fd552d8 100644 --- a/tests/docker-mock.ts +++ b/tests/docker-mock.ts @@ -80,7 +80,7 @@ export class DockerMock extends NockMock { this.optPost( new RegExp(`^/build\\?t=${_.escapeRegExp(opts.tag)}&`), opts, - ).reply(async function(uri, requestBody, cb) { + ).reply(async function (uri, requestBody, cb) { let error: Error | null = null; try { await opts.checkURI(uri); diff --git a/tests/errors.spec.ts b/tests/errors.spec.ts index ec756201..861843c9 100644 --- a/tests/errors.spec.ts +++ b/tests/errors.spec.ts @@ -124,7 +124,7 @@ describe('handleError() function', () => { 'to be one of', ]; - messagesToMatch.forEach(message => { + messagesToMatch.forEach((message) => { it(`should match as expected: "${message}"`, async () => { await ErrorsModule.handleError(new Error(message)); @@ -146,7 +146,7 @@ describe('handleError() function', () => { new BalenaExpiredToken('test'), ]; - typedErrorsToMatch.forEach(typedError => { + typedErrorsToMatch.forEach((typedError) => { it(`should treat typedError ${typedError.name} as expected`, async () => { await ErrorsModule.handleError(typedError); diff --git a/tests/helpers.ts b/tests/helpers.ts index 0ca5684a..8a996fed 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -18,37 +18,63 @@ // tslint:disable-next-line:no-var-requires require('./config-tests'); // required for side effects +import { execFile } from 'child_process'; import intercept = require('intercept-stdout'); import * as _ from 'lodash'; +import { fs } from 'mz'; import * as nock from 'nock'; import * as path from 'path'; import * as balenaCLI from '../build/app'; import { setupSentry } from '../build/app-common'; -export const runCommand = async (cmd: string) => { +const balenaExe = process.platform === 'win32' ? 'balena.exe' : 'balena'; +const standalonePath = path.resolve(__dirname, '..', 'build-bin', balenaExe); + +interface TestOutput { + err: string[]; // stderr + out: string[]; // stdout + exitCode?: number; // process.exitCode +} + +/** + * Filter stdout / stderr lines to remove lines that start with `[debug]` and + * other lines that can be ignored for testing purposes. + * @param testOutput + */ +function filterCliOutputForTests(testOutput: TestOutput): TestOutput { + return { + exitCode: testOutput.exitCode, + err: testOutput.err.filter( + (line: string) => + !line.match(/\[debug\]/i) && + // TODO stop this warning message from appearing when running + // sdk.setSharedOptions multiple times in the same process + !line.startsWith('Shared SDK options') && + // Node 12: '[DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated' + !line.includes('[DEP0066]'), + ), + out: testOutput.out.filter((line: string) => !line.match(/\[debug\]/i)), + }; +} + +/** + * Run the CLI in this same process, by calling the run() function in `app.ts`. + * @param cmd Command to execute, e.g. `push myApp` (without 'balena' prefix) + */ +async function runCommanInProcess(cmd: string): Promise { const preArgs = [process.argv[0], path.join(process.cwd(), 'bin', 'balena')]; const err: string[] = []; const out: string[] = []; const stdoutHook = (log: string | Buffer) => { - // Skip over debug messages - if (typeof log === 'string' && !log.startsWith('[debug]')) { + if (typeof log === 'string') { out.push(log); } }; const stderrHook = (log: string | Buffer) => { - // Skip over debug messages - if ( - typeof log === 'string' && - !log.match(/\[debug\]/i) && - // TODO stop this warning message from appearing when running - // sdk.setSharedOptions multiple times in the same process - !log.startsWith('Shared SDK options') && - // Node 12: '[DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated' - !log.includes('[DEP0066]') - ) { + if (typeof log === 'string') { err.push(log); } }; @@ -58,40 +84,150 @@ export const runCommand = async (cmd: string) => { await balenaCLI.run(preArgs.concat(cmd.split(' ')), { noFlush: true, }); - return { - err, - out, - }; } finally { unhookIntercept(); } -}; + return filterCliOutputForTests({ + err, + out, + // this makes sense if `process.exit()` was stubbed with sinon + exitCode: process.exitCode, + }); +} + +/** + * Run the command (e.g. `balena xxx args`) in a child process, instead of + * the same process as mocha. This is slow and does not allow mocking the + * source code, but it is useful for testing the standalone zip package binary. + * (Every now and then, bugs surface because of missing entries in the + * `pkg.assets` section of `package.json`, usually because of updated + * dependencies that don't clearly declare the have compatibility issues + * with `pkg`.) + * + * `mocha` runs on the parent process, and many of the tests inspect network + * traffic intercepted with `nock`. But this interception only works in the + * parent process itself. To get around this, we run a HTTP proxy server on + * the parent process, and get the child process to use it (the CLI already had + * support for proxy servers as a product feature, and this testing arrangement + * also exercises the proxy capabilities). + * + * @param cmd Command to execute, e.g. `push myApp` (without 'balena' prefix) + * @param proxyPort TCP port number for the HTTP proxy server running on the + * parent process + */ +async function runCommandInSubprocess( + cmd: string, + proxyPort: number, +): Promise { + let exitCode = 0; + let stdout = ''; + let stderr = ''; + const addedEnvs = { + // Use http instead of https, so we can intercept and test the data, + // for example the contents of tar streams sent by the CLI to Docker + BALENARC_API_URL: 'http://api.balena-cloud.com', + BALENARC_BUILDER_URL: 'http://builder.balena-cloud.com', + BALENARC_PROXY: `http://127.0.0.1:${proxyPort}`, + // override default proxy exclusion to allow proxying of requests to 127.0.0.1 + BALENARC_DO_PROXY: '127.0.0.1,localhost', + }; + await new Promise((resolve) => { + const child = execFile( + standalonePath, + cmd.split(' '), + { env: { ...process.env, ...addedEnvs } }, + ($error, $stdout, $stderr) => { + stderr = $stderr || ''; + stdout = $stdout || ''; + // $error will be set if the CLI child process exits with a + // non-zero exit code. Usually this is harmless/expected, as + // the CLI child process is tested for error conditions. + if ($error && process.env.DEBUG) { + console.error(` +[debug] Error (possibly expected) executing child CLI process "${standalonePath}" +------------------------------------------------------------------ +${$error} +------------------------------------------------------------------`); + } + resolve(); + }, + ); + child.on('exit', (code: number, signal: string) => { + if (process.env.DEBUG) { + console.error( + `CLI child process exited with code=${code} signal=${signal}`, + ); + } + exitCode = code; + }); + }); + + const splitLines = (lines: string) => + lines + .split(/[\r\n]/) // includes '\r' in isolation, used in progress bars + .filter((l) => l) + .map((l) => l + '\n'); + + return filterCliOutputForTests({ + exitCode, + err: splitLines(stderr), + out: splitLines(stdout), + }); +} + +/** + * Run a CLI command and capture its stdout, stderr and exit code for testing. + * If the BALENA_CLI_TEST_TYPE env var is set to 'standalone', then the command + * will be executed in a separate child process, and a proxy server will be + * started in order to intercept and test HTTP requests. + * Otherwise, simply call the CLI's run() entry point in this same process. + * @param cmd Command to execute, e.g. `push myApp` (without 'balena' prefix) + */ +export async function runCommand(cmd: string): Promise { + if (process.env.BALENA_CLI_TEST_TYPE === 'standalone') { + const semver = await import('semver'); + if (semver.lt(process.version, '10.16.0')) { + throw new Error( + `The standalone tests require Node.js >= v10.16.0 because of net/proxy features ('global-agent' npm package)`, + ); + } + if (!(await fs.exists(standalonePath))) { + throw new Error(`Standalone executable not found: "${standalonePath}"`); + } + const proxy = await import('./proxy-server'); + const [proxyPort] = await proxy.createProxyServerOnce(); + return runCommandInSubprocess(cmd, proxyPort); + } else { + return runCommanInProcess(cmd); + } +} export const balenaAPIMock = () => { if (!nock.isActive()) { nock.activate(); } - return nock(/./) - .get('/config/vars') - .reply(200, { - reservedNames: [], - reservedNamespaces: [], - invalidRegex: '/^d|W/', - whiteListedNames: [], - whiteListedNamespaces: [], - blackListedNames: [], - configVarSchema: [], - }); + return nock(/./).get('/config/vars').reply(200, { + reservedNames: [], + reservedNamespaces: [], + invalidRegex: '/^d|W/', + whiteListedNames: [], + whiteListedNamespaces: [], + blackListedNames: [], + configVarSchema: [], + }); }; -export function cleanOutput(output: string[] | string): string[] { +export function cleanOutput( + output: string[] | string, + collapseBlank = false, +): string[] { + const cleanLine = collapseBlank + ? (line: string) => monochrome(line.trim()).replace(/\s{2,}/g, ' ') + : (line: string) => monochrome(line.trim()); + return _(_.castArray(output)) - .map((log: string) => { - return log.split('\n').map(line => { - return monochrome(line.trim()); - }); - }) + .map((log: string) => log.split('\n').map(cleanLine)) .flatten() .compact() .value(); @@ -106,7 +242,7 @@ export function cleanOutput(output: string[] | string): string[] { * coded from observation of a few samples only, and may not cover all cases. */ export function monochrome(text: string): string { - return text.replace(/\u001b\[\??\d+?[a-zA-Z]\r?/g, ''); + return text.replace(/\u001b\[\??(\d+;)*\d+[a-zA-Z]\r?/g, ''); } /** @@ -142,7 +278,7 @@ export function fillTemplateArray( templateStringArray: Array, templateVars: object, ): Array { - return templateStringArray.map(i => + return templateStringArray.map((i) => Array.isArray(i) ? fillTemplateArray(i, templateVars) : fillTemplate(i, templateVars), diff --git a/tests/nock-mock.ts b/tests/nock-mock.ts index 365e298a..07a212c9 100644 --- a/tests/nock-mock.ts +++ b/tests/nock-mock.ts @@ -91,7 +91,7 @@ export class NockMock { inspectRequest: (uri: string, requestBody: nock.Body) => void, replyBody: nock.ReplyBody, ) { - return function( + return function ( this: nock.ReplyFnContext, uri: string, requestBody: nock.Body, @@ -133,11 +133,22 @@ export class NockMock { } protected handleUnexpectedRequest(req: any) { + const { + interceptorServerPort, + } = require('./proxy-server') as typeof import('./proxy-server'); const o = req.options || {}; const u = o.uri || {}; + const method = req.method; + const proto = req.protocol || req.proto || o.proto || u.protocol; + const host = req.host || req.headers?.host || o.host || u.host; + const path = req.path || o.path || u.path; + + // Requests made by the local proxy/interceptor server are OK + if (host === `127.0.0.1:${interceptorServerPort}`) { + return; + } console.error( - `Unexpected http request!: ${req.method} ${o.proto || - u.protocol}//${o.host || u.host}${req.path || o.path || u.path}`, + `NockMock: Unexpected HTTP request: ${method} ${proto}//${host}${path}`, ); // Errors thrown here are not causing the tests to fail for some reason. // Possibly due to CLI global error handlers? (error.js) @@ -155,13 +166,13 @@ export class NockMock { let mocks = scope.pendingMocks(); console.error(`pending mocks ${mocks.length}: ${mocks}`); - this.scope.on('request', function(_req, _interceptor, _body) { + this.scope.on('request', function (_req, _interceptor, _body) { console.log(`>> REQUEST:` + _req.path); mocks = scope.pendingMocks(); console.error(`pending mocks ${mocks.length}: ${mocks}`); }); - this.scope.on('replied', function(_req) { + this.scope.on('replied', function (_req) { console.log(`<< REPLIED:` + _req.path); }); } diff --git a/tests/proxy-server.ts b/tests/proxy-server.ts new file mode 100644 index 00000000..819c6f6b --- /dev/null +++ b/tests/proxy-server.ts @@ -0,0 +1,217 @@ +/** + * @license + * Copyright 2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This module creates two HTTP servers listening on the local machine: + * * The "proxy server" which is a standard HTTP proxy server that handles the + * CONNECT HTTP verb, using the `http-proxy` dependency. + * * The "interceptor server" which actually handles the proxied requests. + * + * The proxy server proxies the client request to the interceptor server. (This + * two-server approach (proxy + interceptor) is mainly a result of accommodating + * the typical setup documented by the `http-proxy` dependency.) + * + * The use case for these servers is to test the standalone executable (CLI's + * standalone zip package) in a child process. Most of the CLI's automated tests + * currently test HTTP requests using `nock`, but `nock` can only mock/test the + * same process (Node's built-in `http` library). However, the CLI has support + * for proxy servers as a product feature, so the idea was to proxy the child + * process requests to the parent process, where the proxy / interceptor servers + * run. The interceptor server then forwards the request (mostly unchanged) with + * the expectation that `nock` will intercept the requests for testing (in the + * parent process) as usual. + * + * 1. A `mocha` test case calls `runCommand('push test-rpi')`, with `nock` setup + * to intercept HTTP requests (in the same process that runs `mocha`). + * 2. The proxy and interceptor servers are started in the parent process (only + * once: singleton) at free TCP port numbers randomly allocated by the OS. + * 3. A CLI child process gets spawned to run the command (`balena push test-rpi`) + * with environment variables including BALENARC_PROXY (set to + * 'http://127.0.0.1:${proxyPort}'). (Additional env vars instruct the + * child process to use HTTP instead of HTTPS for the balena API and builder.) + * 4. The child process sends the HTTP requests to the proxy server. + * 5. The proxy server forwards the request to the interceptor server. + * 6. The interceptor server simply re-issues the HTTP request (unchange), with + * the expectation that `nock` will intercept it. + * 7. `nock` (running on the parent process, same process that runs `mocha`) + * intercepts the HTTP request, test it and replies with a mocked response. + * 8. `nocks` response is returned to the interceptor server, which returns it + * to the proxy server, which returns it to the child process, which continues + * CLI command execution. + */ + +import * as http from 'http'; + +const proxyServers: http.Server[] = []; + +after(function () { + if (proxyServers.length) { + if (process.env.DEBUG) { + console.error( + `[debug] Closing proxy servers (count=${proxyServers.length})`, + ); + } + proxyServers.forEach((s) => s.close()); + proxyServers.splice(0); + } +}); + +export let proxyServerPort = 0; +export let interceptorServerPort = 0; + +export async function createProxyServerOnce(): Promise<[number, number]> { + if (proxyServerPort === 0) { + [proxyServerPort, interceptorServerPort] = await createProxyServer(); + } + return [proxyServerPort, interceptorServerPort]; +} + +async function createProxyServer(): Promise<[number, number]> { + const httpProxy = require('http-proxy') as typeof import('http-proxy'); + + const interceptorPort = await createInterceptorServer(); + + const proxy = httpProxy.createProxyServer(); + proxy.on('error', function ( + err: Error, + _req: http.IncomingMessage, + res: http.ServerResponse, + ) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + const msg = `Proxy server error: ${err}`; + console.error(msg); + res.end(msg); + }); + + const server = http.createServer(function ( + req: http.IncomingMessage, + res: http.ServerResponse, + ) { + if (process.env.DEBUG) { + console.error(`[debug] Proxy forwarding for ${req.url}`); + } + proxy.web(req, res, { target: `http://127.0.0.1:${interceptorPort}` }); + }); + proxyServers.push(server); + + server.on('error', (err: Error) => { + console.error(`Proxy server error (http.createServer):\n${err}`); + }); + + let proxyPort = 0; // TCP port number, 0 means automatic allocation + + await new Promise((resolve, reject) => { + const listener = server.listen(0, '127.0.0.1', (err: Error) => { + if (err) { + console.error(`Error starting proxy server:\n${err}`); + reject(err); + } else { + const info: any = listener.address(); + proxyPort = info.port; + console.error( + `[Info] Proxy server listening on ${info.address}:${proxyPort}`, + ); + resolve(); + } + }); + }); + + return [proxyPort, interceptorPort]; +} + +async function createInterceptorServer(): Promise { + const url = await import('url'); + + const server = http.createServer(); + proxyServers.push(server); + + server + .on('error', (err: Error) => { + console.error(`Interceptor server error: ${err}`); + }) + .on( + 'request', + (cliReq: http.IncomingMessage, cliRes: http.ServerResponse) => { + const proxiedFor = `http://${cliReq.headers.host}${cliReq.url}`; + if (process.env.DEBUG) { + console.error(`[debug] Interceptor forwarding for ${proxiedFor}`); + } + // tslint:disable-next-line:prefer-const + let { protocol, hostname, port, path: urlPath, hash } = url.parse( + proxiedFor, + ); + protocol = (protocol || 'http:').toLowerCase(); + port = port || (protocol === 'https:' ? '443' : '80'); + const reqOpts = { + protocol, + port, + host: hostname, + path: `${urlPath || ''}${hash || ''}`, + method: cliReq.method, + headers: cliReq.headers, + }; + const srvReq = http.request(reqOpts); + srvReq + .on('error', (err) => { + console.error( + `Interceptor server error in onward request:\n${err}`, + ); + }) + .on('response', (srvRes: http.IncomingMessage) => { + // Copy headers, status code and status message from interceptor to client + for (const [key, val] of Object.entries(srvRes.headers)) { + if (key && val) { + cliRes.setHeader(key, val); + } + } + cliRes.statusCode = srvRes.statusCode || cliRes.statusCode; + cliRes.statusMessage = srvRes.statusMessage || cliRes.statusMessage; + srvRes.pipe(cliRes).on('error', (err: Error) => { + console.error( + `Interceptor server error piping response to proxy server:\n${err}`, + ); + cliRes.end(); + }); + }); + cliReq.pipe(srvReq).on('error', (err: Error) => { + console.error( + `Proxy server error piping client request onward:\n${err}`, + ); + }); + }, + ); + + let interceptorPort = 0; + + await new Promise((resolve, reject) => { + const listener = server.listen(0, '127.0.0.1', (err: Error) => { + if (err) { + console.error(`Error starting interceptor server:\n${err}`); + reject(err); + } else { + const info: any = listener.address(); + interceptorPort = info.port; + console.error( + `[Info] Interceptor server listening on ${info.address}:${interceptorPort}`, + ); + resolve(); + } + }); + }); + + return interceptorPort; +} diff --git a/tests/supervisor-mock.ts b/tests/supervisor-mock.ts new file mode 100644 index 00000000..fa2c5bbc --- /dev/null +++ b/tests/supervisor-mock.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as _ from 'lodash'; +import * as path from 'path'; +import { Readable } from 'stream'; + +import { NockMock, ScopeOpts } from './nock-mock'; + +export const dockerResponsePath = path.normalize( + path.join(__dirname, 'test-data', 'docker-response'), +); + +export class SupervisorMock extends NockMock { + constructor() { + super(/1\.2\.3\.4:48484/); + } + + public expectGetPing(opts: ScopeOpts = {}) { + this.optGet('/ping', opts).reply(200, 'OK'); + } + + public expectGetLogs(opts: ScopeOpts = {}) { + const chunks = [ + '\n', + '{"message":"Streaming logs","isSystem":true}\n', + '{"serviceName":"bar","serviceId":1,"imageId":1,"isStdout":true,"timestamp":1591991625223,"message":"bar 8 (332) Linux 4e3f81149d71 4.19.75 #1 SMP PREEMPT Mon Mar 23 11:50:49 UTC 2020 aarch64 GNU/Linux"}\n', + '{"serviceName":"foo","serviceId":2,"imageId":2,"isStdout":true,"timestamp":1591991628757,"message":"foo 8 (200) Linux cc5df60d89ee 4.19.75 #1 SMP PREEMPT Mon Mar 23 11:50:49 UTC 2020 aarch64 GNU/Linux"}\n', + ]; + let chunkCount = 0; + const chunkedStream = new Readable({ + read(_size) { + setTimeout(() => { + this.push(chunkCount === chunks.length ? null : chunks[chunkCount++]); + }, 10); + }, + }); + this.optGet('/v2/local/logs', opts).reply((_uri, _reqBody, cb) => { + cb(null, [200, chunkedStream]); + }); + } +} diff --git a/tests/test-data/api-response/device-status.json b/tests/test-data/api-response/device-status.json new file mode 100644 index 00000000..643f64c8 --- /dev/null +++ b/tests/test-data/api-response/device-status.json @@ -0,0 +1,8 @@ +{ + "d": [ + { + "overall_status": "offline", + "__metadata": {} + } + ] +} diff --git a/tests/utils/eol-conversion.spec.ts b/tests/utils/eol-conversion.spec.ts index 1e858545..55a31608 100644 --- a/tests/utils/eol-conversion.spec.ts +++ b/tests/utils/eol-conversion.spec.ts @@ -24,7 +24,7 @@ import { detectEncoding, } from '../../build/utils/eol-conversion'; -describe('convertEolInPlace() function', function() { +describe('convertEolInPlace() function', function () { it('should return expected values', () => { // pairs of [given input, expected output] const testVector = [ @@ -59,7 +59,7 @@ describe('convertEolInPlace() function', function() { }); }); -describe('detectEncoding() function', function() { +describe('detectEncoding() function', function () { it('should correctly detect the encoding of a few selected files', async () => { const sampleBinary = [ 'ext2fs/build/Release/bindings.node', diff --git a/tests/utils/helpers.spec.ts b/tests/utils/helpers.spec.ts index 7913c8db..5b19a870 100644 --- a/tests/utils/helpers.spec.ts +++ b/tests/utils/helpers.spec.ts @@ -19,7 +19,7 @@ import { expect } from 'chai'; import { getProxyConfig } from '../../build/utils/helpers'; -describe('getProxyConfig() function', function() { +describe('getProxyConfig() function', function () { let originalProxyConfig: [boolean, object | undefined]; let originalHttpProxy: [boolean, string | undefined]; let originalHttpsProxy: [boolean, string | undefined]; diff --git a/tests/utils/ignore.spec.ts b/tests/utils/ignore.spec.ts index e5ee0236..76dc733a 100644 --- a/tests/utils/ignore.spec.ts +++ b/tests/utils/ignore.spec.ts @@ -7,8 +7,8 @@ import { FileIgnorer, IgnoreFileType } from '../../build/utils/ignore'; // of the FileIgnorer class to prevent a Typescript compilation error (this // behavior is by design: see // https://github.com/microsoft/TypeScript/issues/19335 ) -describe('File ignorer', function() { - it('should detect ignore files', function() { +describe('File ignorer', function () { + it('should detect ignore files', function () { const f = new FileIgnorer(`.${path.sep}`); expect(f.getIgnoreFileType('.gitignore')).to.equal( IgnoreFileType.GitIgnore, @@ -36,7 +36,7 @@ describe('File ignorer', function() { return expect(f.getIgnoreFileType('./file')).to.equal(null); }); - it('should filter files from the root directory', function() { + it('should filter files from the root directory', function () { const ignore = new FileIgnorer(`.${path.sep}`); ignore['gitIgnoreEntries'] = [ { pattern: '*.ignore', filePath: '.gitignore' }, @@ -61,7 +61,7 @@ describe('File ignorer', function() { ]); }); - return it('should filter files from subdirectories', function() { + return it('should filter files from subdirectories', function () { const ignore = new FileIgnorer(`.${path.sep}`); ignore['gitIgnoreEntries'] = [ { pattern: '*.ignore', filePath: 'lib/.gitignore' }, diff --git a/tests/utils/qemu.spec.ts b/tests/utils/qemu.spec.ts index 99e62145..7d8af509 100644 --- a/tests/utils/qemu.spec.ts +++ b/tests/utils/qemu.spec.ts @@ -17,7 +17,7 @@ import { expect } from 'chai'; -describe('resin-multibuild consistency', function() { +describe('resin-multibuild consistency', function () { it('should use the same values for selected constants', async () => { const { QEMU_BIN_NAME: MQEMU_BIN_NAME } = await import('resin-multibuild'); const { QEMU_BIN_NAME } = await import('../../build/utils/qemu'); diff --git a/tests/utils/tarDirectory.spec.ts b/tests/utils/tarDirectory.spec.ts index 53bf77af..2ec71a3d 100644 --- a/tests/utils/tarDirectory.spec.ts +++ b/tests/utils/tarDirectory.spec.ts @@ -38,7 +38,7 @@ interface TarFiles { const itSkipWindows = process.platform === 'win32' ? it.skip : it; -describe('compare new and old tarDirectory implementations', function() { +describe('compare new and old tarDirectory implementations', function () { const extraContent = 'extra'; const extraEntry: tar.Headers = { name: 'extra.txt', @@ -61,7 +61,7 @@ describe('compare new and old tarDirectory implementations', function() { // (with a mismatched fileSize 13 vs 5 for 'symlink-a.txt'), ensure that the // `core.symlinks` property is set to `true` in the `.git/config` file. Ref: // https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresymlinks - it('should produce the expected file list', async function() { + it('should produce the expected file list', async function () { const dockerignoreProjDir = path.join( projectsPath, 'no-docker-compose', @@ -92,7 +92,7 @@ describe('compare new and old tarDirectory implementations', function() { expect(fileList).to.deep.equal(expectedFiles); }); - it('should produce the expected file list (symbolic links)', async function() { + it('should produce the expected file list (symbolic links)', async function () { const projectPath = path.join( projectsPath, 'no-docker-compose', @@ -115,7 +115,7 @@ describe('compare new and old tarDirectory implementations', function() { // Skip Windows because the old tarDirectory() implementation (still used when // '--nogitignore' is not provided) uses the old `zeit/dockerignore` npm package // that is broken on Windows (reason why we created `@balena/dockerignore`). - itSkipWindows('should produce a compatible tar stream', async function() { + itSkipWindows('should produce a compatible tar stream', async function () { const dockerignoreProjDir = path.join( projectsPath, 'no-docker-compose', @@ -146,7 +146,7 @@ describe('compare new and old tarDirectory implementations', function() { itSkipWindows( 'should produce a compatible tar stream (symbolic links)', - async function() { + async function () { const dockerignoreProjDir = path.join( projectsPath, 'no-docker-compose', @@ -179,7 +179,7 @@ async function getTarPackFiles( return await new Promise((resolve, reject) => { extract .on('error', reject) - .on('entry', async function(header, stream, next) { + .on('entry', async function (header, stream, next) { expect(fileList).to.not.have.property(header.name); fileList[header.name] = { fileSize: header.size, @@ -188,7 +188,7 @@ async function getTarPackFiles( await drainStream(stream); next(); }) - .on('finish', function() { + .on('finish', function () { resolve(fileList); }); pack.pipe(extract);