Merge pull request #1867 from balena-io/1770-major-sdk-pkg-proxy

Release CLI v12
This commit is contained in:
bulldozer-balena[bot] 2020-06-16 08:00:33 +00:00 committed by GitHub
commit d709e06f48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 2098 additions and 1226 deletions

View File

@ -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:

View File

@ -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

View File

@ -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('" "')}"`);

View File

@ -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),
),
);
}

View File

@ -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"

View File

@ -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];

View File

@ -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:

View File

@ -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'),
);

View File

@ -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(

View File

@ -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(

View File

@ -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 &#60;docker&#62;
@ -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 &#60;docker&#62;

View File

@ -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,
);
});

View File

@ -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,
},
];

View File

@ -80,7 +80,7 @@ export default class DevicesSupportedCmd extends Command {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
let deviceTypes: Array<Partial<SDK.DeviceType>> = 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<SDK.DeviceType>),
deviceTypes.map((d) => _.pick(d, fields) as Partial<SDK.DeviceType>),
fields,
);
if (options.json) {

View File

@ -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),
);

View File

@ -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',

View File

@ -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'),
},
];

View File

@ -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,
},
];

View File

@ -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,
},
];

View File

@ -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 };
});

View File

@ -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;
});

View File

@ -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),

View File

@ -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<any>} */ function() {
/** @returns {Promise<any>} */ 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));
}

View File

@ -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),

View File

@ -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;
});

View File

@ -100,7 +100,7 @@ export const osUpdate: CommandDefinition<OsUpdate.Args, OsUpdate.Options> = {
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)`

View File

@ -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);
}

View File

@ -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(

View File

@ -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);
}

View File

@ -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);
}
}
}
},

View File

@ -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;
}

View File

@ -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<import('balena-sdk').Application>} */
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();
}

View File

@ -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;

View File

@ -101,7 +101,7 @@ async function getContainerId(
});
const containers = await new Promise<string>((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(

View File

@ -130,7 +130,7 @@ export const tunnel: CommandDefinition<Args, Options> = {
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<Args, Options> = {
})
.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<Args, Options> = {
remotePort,
);
})
.catch(err =>
.catch((err) =>
logConnection(
client.remoteAddress || '',
client.remotePort || 0,
@ -195,7 +195,7 @@ export const tunnel: CommandDefinition<Args, Options> = {
}),
)
.then(
server =>
(server) =>
new Bluebird.Promise<Server>((resolve, reject) => {
server.on('error', reject);
server.listen(localPort, localAddress, () => {

View File

@ -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>"
// ("applicationOrDevice" is NOT replaced with its actual value)

View File

@ -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 =

View File

@ -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.)

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
});

View File

@ -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,

View File

@ -19,7 +19,7 @@ import { Hook } from '@oclif/config';
let trackResolve: (result: Promise<any>) => 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 =

View File

@ -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);

View File

@ -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<import('./compose-types').Release>}
*/
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<Array<import('./compose-types').TaggedImage>>}
*/
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<string>}
*/
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<import('./compose-types').TaggedImage>} 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}`;

View File

@ -188,7 +188,7 @@ export async function tarDirectory(
let readFile: (file: string) => Promise<Buffer>;
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);
};

View File

@ -72,7 +72,7 @@ export function generateBaseConfig(
application.app_name,
options,
) as Promise<ImgConfig & { apiKey?: string }>;
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;
});
}

View File

@ -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;
}

View File

@ -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 },

View File

@ -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<void> {
const failures: LocalPushErrors.BuildFailure[] = [];
_.each(images, image => {
_.each(images, (image) => {
if (!image.successful) {
failures.push({
error: image.error!,

View File

@ -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';
}

View File

@ -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 }) => {

View File

@ -40,7 +40,7 @@ export function displayDeviceLogs(
filterServices?: string[],
): Bluebird<void> {
return new Bluebird((resolve, reject) => {
logs.on('data', log => {
logs.on('data', (log) => {
displayLogLine(log, logger, system, filterServices);
});

View File

@ -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(

View File

@ -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()

View File

@ -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<T>(command: string): Bluebird<T> {
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<BalenaSdk.DeviceType>(deviceTypes, {
slug: app.device_type,
});
@ -214,8 +214,9 @@ export function retry<T>(
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<T, U = T>(
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]));
}
}

View File

@ -93,7 +93,7 @@ export class FileIgnorer {
): Promise<void> {
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()) {

View File

@ -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 = [];

View File

@ -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();
}

View File

@ -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<void> => {
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<BalenaSdk.Device>(device => device.is_online)
.then(onlineDevices => {
.filter<BalenaSdk.Device>((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<T>(
return getForm().ask<T>({
message,
type: 'list',
choices: _.map(choices, s => ({
choices: _.map(choices, (s) => ({
name: s.name,
value: s,
})),

View File

@ -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<string> {
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,

View File

@ -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':

View File

@ -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<void> {

View File

@ -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();
}),

View File

@ -23,14 +23,11 @@ export function buffer(
): Promise<NodeJS.ReadableStream> {
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);

View File

@ -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(

View File

@ -54,14 +54,14 @@ export const tunnelConnectionToDevice = (
return (client: Socket): Bluebird<void> =>
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();
});

View File

@ -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;
}

1307
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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 <juan@balena.io>",
"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",

View File

@ -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;
};

View File

@ -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',

View File

@ -51,8 +51,8 @@ async function getPage(name: string): Promise<string> {
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();
});

View File

@ -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;
});

View File

@ -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 }];
});

View File

@ -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<void>;
checkBuildRequestBody: (requestBody: string | Buffer) => Promise<void>;
}) {
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<Buffer>(cb => {
const gunzipped = await Bluebird.fromCallback<Buffer>((cb) => {
zlib.gunzip(gzipped, cb);
});
await opts.checkBuildRequestBody(gunzipped);

View File

@ -41,7 +41,7 @@ Options:
--type, -t <type> application device type (Check available types with \`balena devices supported\`)
`;
describe('balena app create', function() {
describe('balena app create', function () {
let api: BalenaAPIMock;
beforeEach(() => {

View File

@ -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;
});
});

View File

@ -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;
});
});

View File

@ -36,7 +36,7 @@ Options:
--application, -a, --app <application> application name
`;
describe('balena device move', function() {
describe('balena device move', function () {
let api: BalenaAPIMock;
beforeEach(() => {

View File

@ -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'),

View File

@ -40,7 +40,7 @@ Options:
--application, -a, --app <application> 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([]);
});

View File

@ -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([]);
});

View File

@ -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(() => {

View File

@ -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);
});

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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');

View File

@ -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',
]);
});
});

View File

@ -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 () => {

View File

@ -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;
});
});

View File

@ -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);
}
});
});

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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<TestOutput> {
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<TestOutput> {
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<TestOutput> {
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<string | string[]>,
templateVars: object,
): Array<string | string[]> {
return templateStringArray.map(i =>
return templateStringArray.map((i) =>
Array.isArray(i)
? fillTemplateArray(i, templateVars)
: fillTemplate(i, templateVars),

View File

@ -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);
});
}

217
tests/proxy-server.ts Normal file
View File

@ -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<number> {
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;
}

56
tests/supervisor-mock.ts Normal file
View File

@ -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]);
});
}
}

View File

@ -0,0 +1,8 @@
{
"d": [
{
"overall_status": "offline",
"__metadata": {}
}
]
}

Some files were not shown because too many files have changed in this diff Show More