Update @balena/lint to v9.1.3

Update @balena/lint from 8.0.0 to 9.1.3

Change-type: patch
This commit is contained in:
Otavio Jacobi 2024-12-16 11:11:57 -03:00
parent 36077cacda
commit c0e7ae9c91
67 changed files with 1777 additions and 902 deletions

View File

@ -145,7 +145,7 @@ export async function getCapitanoDoc(): Promise<typeof capitanoDoc> {
throw new Error(`Error parsing section title`); throw new Error(`Error parsing section title`);
} }
// match[1] has the title, match[2] has the rest // match[1] has the title, match[2] has the rest
return match && match[2]; return match?.[2];
}), }),
mdParser.getSectionOfTitle('Installation'), mdParser.getSectionOfTitle('Installation'),
mdParser.getSectionOfTitle('Choosing a shell (command prompt/terminal)'), mdParser.getSectionOfTitle('Choosing a shell (command prompt/terminal)'),

View File

@ -3,7 +3,7 @@ import * as semver from 'semver';
const changeTypes = ['major', 'minor', 'patch'] as const; const changeTypes = ['major', 'minor', 'patch'] as const;
const validateChangeType = (maybeChangeType: string = 'minor') => { const validateChangeType = (maybeChangeType = 'minor') => {
maybeChangeType = maybeChangeType.toLowerCase(); maybeChangeType = maybeChangeType.toLowerCase();
switch (maybeChangeType) { switch (maybeChangeType) {
case 'patch': case 'patch':
@ -136,5 +136,4 @@ async function main() {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-floating-promises void main();
main();

View File

@ -19,6 +19,7 @@ import { spawn } from 'child_process';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { diffTrimmedLines } from 'diff'; import { diffTrimmedLines } from 'diff';
import * as whichMod from 'which';
export const ROOT = path.join(__dirname, '..'); export const ROOT = path.join(__dirname, '..');
@ -101,7 +102,6 @@ export function loadPackageJson() {
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE' * @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
*/ */
export async function which(program: string): Promise<string> { export async function which(program: string): Promise<string> {
const whichMod = await import('which');
let programPath: string; let programPath: string;
try { try {
programPath = await whichMod(program); programPath = await whichMod(program);
@ -132,7 +132,7 @@ export async function whichSpawn(
.on('error', reject) .on('error', reject)
.on('close', resolve); .on('close', resolve);
} catch (err) { } catch (err) {
reject(err); reject(err as Error);
} }
}); });
} catch (err) { } catch (err) {

View File

@ -57,7 +57,10 @@ require('ts-node').register({
project: path.join(rootDir, 'tsconfig.json'), project: path.join(rootDir, 'tsconfig.json'),
transpileOnly: true, transpileOnly: true,
}); });
require('../src/app').run(undefined, { dir: __dirname, development: true }); void require('../src/app').run(undefined, {
dir: __dirname,
development: true,
});
// Modify package.json oclif paths from build/ -> src/, or vice versa // Modify package.json oclif paths from build/ -> src/, or vice versa
function modifyOclifPaths(revert) { function modifyOclifPaths(revert) {

View File

@ -5,7 +5,7 @@
process.env.UV_THREADPOOL_SIZE = '64'; process.env.UV_THREADPOOL_SIZE = '64';
// Disable oclif registering ts-node // Disable oclif registering ts-node
process.env.OCLIF_TS_NODE = 0; process.env.OCLIF_TS_NODE = '0';
async function run() { async function run() {
// Use fast-boot to cache require lookups, speeding up startup // Use fast-boot to cache require lookups, speeding up startup
@ -18,4 +18,4 @@ async function run() {
await require('../build/app').run(undefined, { dir: __dirname }); await require('../build/app').run(undefined, { dir: __dirname });
} }
run(); void run();

32
eslint.config.js Normal file
View File

@ -0,0 +1,32 @@
const { FlatCompat } = require('@eslint/eslintrc');
const compat = new FlatCompat({
baseDirectory: __dirname,
});
module.exports = [
...require('@balena/lint/config/eslint.config'),
...compat.config({
parserOptions: {
project: 'tsconfig.dev.json',
},
ignorePatterns: ['**/generate-completion.js', '**/bin/**/*'],
rules: {
ignoreDefinitionFiles: 0,
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
'no-restricted-imports': ['error', {
paths: ['resin-cli-visuals', 'chalk', 'common-tags', 'resin-cli-form'],
}],
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}],
},
}),
];

2145
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -111,7 +111,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@balena/lint": "^8.0.0", "@balena/lint": "^9.1.3",
"@electron/notarize": "^2.0.0", "@electron/notarize": "^2.0.0",
"@types/archiver": "^6.0.2", "@types/archiver": "^6.0.2",
"@types/bluebird": "^3.5.36", "@types/bluebird": "^3.5.36",

View File

@ -50,9 +50,9 @@ export default class RevokeCmd extends Command {
return; return;
} }
await Promise.all( await Promise.all(
apiKeyIds.map( apiKeyIds.map(async (id) => {
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)), await getBalenaSdk().models.apiKey.revoke(Number(id));
), }),
); );
console.log('Successfully revoked the given API keys'); console.log('Successfully revoked the given API keys');
} }

View File

@ -91,7 +91,7 @@ export default class DeviceDetectCmd extends Command {
try { try {
await docker.ping(); await docker.ping();
return true; return true;
} catch (err) { } catch {
return false; return false;
} }
}), }),

View File

@ -155,7 +155,7 @@ export default class DeviceInitCmd extends Command {
try { try {
logger.logDebug(`Process failed, removing device ${device.uuid}`); logger.logDebug(`Process failed, removing device ${device.uuid}`);
await balena.models.device.remove(device.uuid); await balena.models.device.remove(device.uuid);
} catch (e) { } catch {
// Ignore removal failures, and throw original error // Ignore removal failures, and throw original error
} }
throw e; throw e;

View File

@ -135,7 +135,7 @@ export default class DeviceLogsCmd extends Command {
logger.logDebug('Checking we can access device'); logger.logDebug('Checking we can access device');
try { try {
await deviceApi.ping(); await deviceApi.ping();
} catch (e) { } catch {
const { ExpectedError } = await import('../../errors'); const { ExpectedError } = await import('../../errors');
throw new ExpectedError( throw new ExpectedError(
`Cannot access device at address ${params.device}. Device may not be in local mode.`, `Cannot access device at address ${params.device}. Device may not be in local mode.`,

View File

@ -76,6 +76,6 @@ export default class DeviceRegisterCmd extends Command {
options.deviceType, options.deviceType,
); );
return result && result.uuid; return result.uuid;
} }
} }

View File

@ -82,7 +82,7 @@ export default class DeviceSSHCmd extends Command {
SSH server port number (default 22222) if the target is an IP address or .local SSH server port number (default 22222) if the target is an IP address or .local
hostname. Otherwise, port number for the balenaCloud gateway (default 22).`, hostname. Otherwise, port number for the balenaCloud gateway (default 22).`,
char: 'p', char: 'p',
parse: async (p) => parseAsInteger(p, 'port'), parse: (p) => parseAsInteger(p, 'port'),
}), }),
tty: Flags.boolean({ tty: Flags.boolean({
default: false, default: false,
@ -110,13 +110,14 @@ export default class DeviceSSHCmd extends Command {
// Local connection // Local connection
if (validateLocalHostnameOrIp(params.fleetOrDevice)) { if (validateLocalHostnameOrIp(params.fleetOrDevice)) {
const { performLocalDeviceSSH } = await import('../../utils/device/ssh'); const { performLocalDeviceSSH } = await import('../../utils/device/ssh');
return await performLocalDeviceSSH({ await performLocalDeviceSSH({
hostname: params.fleetOrDevice, hostname: params.fleetOrDevice,
port: options.port || 'local', port: options.port || 'local',
forceTTY: options.tty, forceTTY: options.tty,
verbose: options.verbose, verbose: options.verbose,
service: params.service, service: params.service,
}); });
return;
} }
// Remote connection // Remote connection
@ -132,7 +133,7 @@ export default class DeviceSSHCmd extends Command {
const useProxy = !!proxyConfig && !options.noproxy; const useProxy = !!proxyConfig && !options.noproxy;
// this will be a tunnelled SSH connection... // this will be a tunnelled SSH connection...
await checkNotUsingOfflineMode(); checkNotUsingOfflineMode();
await checkLoggedIn(); await checkLoggedIn();
const deviceUuid = await getOnlineTargetDeviceUuid( const deviceUuid = await getOnlineTargetDeviceUuid(
sdk, sdk,

View File

@ -41,7 +41,7 @@ export default class EnvRenameCmd extends Command {
id: Args.integer({ id: Args.integer({
required: true, required: true,
description: "variable's numeric database ID", description: "variable's numeric database ID",
parse: async (input) => parseAsInteger(input, 'id'), parse: (input) => parseAsInteger(input, 'id'),
}), }),
value: Args.string({ value: Args.string({
required: true, required: true,

View File

@ -46,7 +46,7 @@ export default class EnvRmCmd extends Command {
id: Args.integer({ id: Args.integer({
required: true, required: true,
description: "variable's numeric database ID", description: "variable's numeric database ID",
parse: async (input) => parseAsInteger(input, 'id'), parse: (input) => parseAsInteger(input, 'id'),
}), }),
}; };

View File

@ -252,10 +252,7 @@ export default class LocalConfigureCmd extends Command {
`${this.CONNECTIONS_FOLDER}/resin-sample.ignore`, `${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
{ encoding: 'utf8' }, { encoding: 'utf8' },
); );
return await writeFileAsync( await writeFileAsync(`${this.CONNECTIONS_FOLDER}/resin-wifi`, contents);
`${this.CONNECTIONS_FOLDER}/resin-wifi`,
contents,
);
}); });
} else if (_.includes(files, 'resin-sample')) { } else if (_.includes(files, 'resin-sample')) {
// Legacy mode, to be removed later // Legacy mode, to be removed later
@ -269,13 +266,13 @@ export default class LocalConfigureCmd extends Command {
} else { } else {
// In case there's no file at all (shouldn't happen normally, but the file might have been removed) // In case there's no file at all (shouldn't happen normally, but the file might have been removed)
await imagefs.interact(target, bootPartition, async (_fs) => { await imagefs.interact(target, bootPartition, async (_fs) => {
return await promisify(_fs.writeFile)( await promisify(_fs.writeFile)(
`${this.CONNECTIONS_FOLDER}/resin-wifi`, `${this.CONNECTIONS_FOLDER}/resin-wifi`,
this.CONNECTION_FILE, this.CONNECTION_FILE,
); );
}); });
} }
return await this.getConfigurationSchema(bootPartition, connectionFileName); return this.getConfigurationSchema(bootPartition, connectionFileName);
} }
async removeHostname(schema: any) { async removeHostname(schema: any) {

View File

@ -132,7 +132,7 @@ export default class LoginCmd extends Command {
// We can safely assume this won't be undefined as doLogin will throw if this call fails // We can safely assume this won't be undefined as doLogin will throw if this call fails
// We also don't need to worry too much about the amount of calls to whoami // We also don't need to worry too much about the amount of calls to whoami
// as these are cached by the SDK // as these are cached by the SDK
const whoamiResult = (await balena.auth.whoami()) as WhoamiResult; const whoamiResult = (await balena.auth.whoami())!;
if (whoamiResult.actorType !== 'user' && !options.hideExperimentalWarning) { if (whoamiResult.actorType !== 'user' && !options.hideExperimentalWarning) {
console.info(stripIndent` console.info(stripIndent`
@ -168,7 +168,7 @@ ${messages.reachingOut}`);
async doLogin( async doLogin(
loginOptions: FlagsDef, loginOptions: FlagsDef,
balenaUrl: string = 'balena-cloud.com', balenaUrl = 'balena-cloud.com',
token?: string, token?: string,
): Promise<void> { ): Promise<void> {
// Token // Token

View File

@ -292,7 +292,7 @@ export default class OsConfigureCmd extends Command {
for (const { name, content } of files) { for (const { name, content } of files) {
await imagefs.interact(image, bootPartition, async (_fs) => { await imagefs.interact(image, bootPartition, async (_fs) => {
return await promisify(_fs.writeFile)( await promisify(_fs.writeFile)(
path.join(CONNECTIONS_FOLDER, name), path.join(CONNECTIONS_FOLDER, name),
content, content,
); );

View File

@ -109,7 +109,7 @@ https://github.com/balena-io-examples/staged-releases\
'additional-space': Flags.integer({ 'additional-space': Flags.integer({
description: description:
'expand the image by this amount of bytes instead of automatically estimating the required amount', 'expand the image by this amount of bytes instead of automatically estimating the required amount',
parse: async (x) => parseAsInteger(x, 'additional-space'), parse: (x) => parseAsInteger(x, 'additional-space'),
}), }),
'add-certificate': Flags.string({ 'add-certificate': Flags.string({
description: `\ description: `\
@ -126,7 +126,7 @@ Can be repeated to add multiple certificates.\
dockerPort: Flags.integer({ dockerPort: Flags.integer({
description: description:
'Docker daemon TCP port number (hint: 2375 for balena devices)', 'Docker daemon TCP port number (hint: 2375 for balena devices)',
parse: async (p) => parseAsInteger(p, 'dockerPort'), parse: (p) => parseAsInteger(p, 'dockerPort'),
}), }),
}; };
@ -155,7 +155,7 @@ Can be repeated to add multiple certificates.\
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
`); `);
} }
} catch (error) { } catch {
throw new ExpectedError( throw new ExpectedError(
`The provided image path does not exist: ${params.image}`, `The provided image path does not exist: ${params.image}`,
); );
@ -192,11 +192,11 @@ Can be repeated to add multiple certificates.\
event.name, event.name,
)); ));
if (event.action === 'start') { if (event.action === 'start') {
return spinner.start(); spinner.start();
} else { return;
console.log();
return spinner.stop();
} }
console.log();
spinner.stop();
}; };
const commit = this.isCurrentCommit(options.commit || '') const commit = this.isCurrentCommit(options.commit || '')

View File

@ -24,7 +24,7 @@ import { tryAsInteger } from '../../utils/validation';
import { jsonInfo } from '../../utils/messages'; import { jsonInfo } from '../../utils/messages';
export const commitOrIdArg = Args.custom({ export const commitOrIdArg = Args.custom({
parse: async (commitOrId: string) => tryAsInteger(commitOrId), parse: tryAsInteger,
}); });
type FlagsDef = Interfaces.InferredFlags<typeof ReleaseCmd.flags>; type FlagsDef = Interfaces.InferredFlags<typeof ReleaseCmd.flags>;

View File

@ -34,7 +34,7 @@ export default class SSHKeyCmd extends Command {
public static args = { public static args = {
id: Args.integer({ id: Args.integer({
description: 'balenaCloud ID for the SSH key', description: 'balenaCloud ID for the SSH key',
parse: async (x) => parseAsInteger(x, 'id'), parse: (x) => parseAsInteger(x, 'id'),
required: true, required: true,
}), }),
}; };

View File

@ -40,7 +40,7 @@ export default class SSHKeyRmCmd extends Command {
public static args = { public static args = {
id: Args.integer({ id: Args.integer({
description: 'balenaCloud ID for the SSH key', description: 'balenaCloud ID for the SSH key',
parse: async (x) => parseAsInteger(x, 'id'), parse: (x) => parseAsInteger(x, 'id'),
required: true, required: true,
}), }),
}; };

View File

@ -69,10 +69,9 @@ export default class VersionCmd extends Command {
const { flags: options } = await this.parse(VersionCmd); const { flags: options } = await this.parse(VersionCmd);
const versions: JsonVersions = { const versions: JsonVersions = {
'balena-cli': (await import('../../../package.json')).version, 'balena-cli': (await import('../../../package.json')).version,
'Node.js': 'Node.js': process.version.startsWith('v')
process.version && process.version.startsWith('v') ? process.version.slice(1)
? process.version.slice(1) : process.version,
: process.version,
}; };
if (options.json) { if (options.json) {
console.log(JSON.stringify(versions, null, 4)); console.log(JSON.stringify(versions, null, 4));

View File

@ -93,7 +93,7 @@ function interpret(error: Error): string {
if (hasCode(error)) { if (hasCode(error)) {
const errorCodeHandler = messages[error.code]; const errorCodeHandler = messages[error.code];
const message = errorCodeHandler && errorCodeHandler(error); const message = errorCodeHandler?.(error);
if (message) { if (message) {
return message; return message;
@ -229,7 +229,7 @@ async function sentryCaptureException(error: Error) {
Sentry.captureException(error); Sentry.captureException(error);
try { try {
await Sentry.close(1000); await Sentry.close(1000);
} catch (e) { } catch {
if (process.env.DEBUG) { if (process.env.DEBUG) {
console.error('[debug] Timeout reporting error to sentry.io'); console.error('[debug] Timeout reporting error to sentry.io');
} }

View File

@ -209,12 +209,12 @@ See: https://git.io/JRHUW#deprecation-policy`,
return indent(body, 2); return indent(body, 2);
} }
protected formatDescription(desc: string = '') { protected formatDescription(desc = '') {
const chalk = getChalk(); const chalk = getChalk();
desc = desc.split('\n')[0]; desc = desc.split('\n')[0];
// Remove any ending . // Remove any ending .
if (desc[desc.length - 1] === '.') { if (desc.endsWith('.')) {
desc = desc.substring(0, desc.length - 1); desc = desc.substring(0, desc.length - 1);
} }
// Lowercase first letter if second char is lowercase, to preserve e.g. 'SSH ...') // Lowercase first letter if second char is lowercase, to preserve e.g. 'SSH ...')

View File

@ -103,7 +103,7 @@ const hook: Hook<'prerun'> = async function (options) {
.offlineCompatible ?? DEFAULT_OFFLINE_COMPATIBLE .offlineCompatible ?? DEFAULT_OFFLINE_COMPATIBLE
) )
) { ) {
await checkNotUsingOfflineMode(); checkNotUsingOfflineMode();
} }
} catch (error) { } catch (error) {
this.error(error); this.error(error);

View File

@ -48,7 +48,7 @@ export async function preparseArgs(argv: string[]): Promise<string[]> {
if ( if (
cmdSlice.length > 1 && cmdSlice.length > 1 &&
cmdSlice[0] === 'help' && cmdSlice[0] === 'help' &&
cmdSlice[1][0] !== '-' !cmdSlice[1].startsWith('-')
) { ) {
cmdSlice.shift(); cmdSlice.shift();
cmdSlice.push('--help'); cmdSlice.push('--help');

View File

@ -164,9 +164,8 @@ export async function downloadOSImage(
stream.on('progress', (state: any) => { stream.on('progress', (state: any) => {
if (state != null) { if (state != null) {
return bar.update(state); return bar.update(state);
} else {
return spinner.start();
} }
spinner.start();
}); });
stream.on('end', () => { stream.on('end', () => {

View File

@ -386,7 +386,7 @@ export class BuildProgressUI implements Renderer {
.map(function (service) { .map(function (service) {
const stream = through.obj(function (event, _enc, cb) { const stream = through.obj(function (event, _enc, cb) {
eventHandler(service, event); eventHandler(service, event);
return cb(); cb();
}); });
stream.pipe(tty.stream, { end: false }); stream.pipe(tty.stream, { end: false });
return [service, stream]; return [service, stream];
@ -471,17 +471,20 @@ export class BuildProgressUI implements Renderer {
const { status, progress, error } = serviceToDataMap[service] ?? {}; const { status, progress, error } = serviceToDataMap[service] ?? {};
if (error) { if (error) {
return `${error}`; return `${error}`;
} else if (progress) { }
if (progress) {
const bar = renderProgressBar(progress, 20); const bar = renderProgressBar(progress, 20);
if (status) { if (status) {
return `${bar} ${status}`; return `${bar} ${status}`;
} }
return `${bar}`; return bar;
} else if (status) {
return `${status}`;
} else {
return 'Waiting...';
} }
if (status) {
return status;
}
return 'Waiting...';
}) })
.map((data, index) => [services[index], data]) .map((data, index) => [services[index], data])
.fromPairs() .fromPairs()
@ -552,7 +555,7 @@ export class BuildProgressInline implements Renderer {
.map(function (service) { .map(function (service) {
const stream = through.obj(function (event, _enc, cb) { const stream = through.obj(function (event, _enc, cb) {
eventHandler(service, event); eventHandler(service, event);
return cb(); cb();
}); });
stream.pipe(outStream, { end: false }); stream.pipe(outStream, { end: false });
return [service, stream]; return [service, stream];
@ -606,11 +609,11 @@ export class BuildProgressInline implements Renderer {
const { status, error } = event; const { status, error } = event;
if (error) { if (error) {
return `${error}`; return `${error}`;
} else if (status) {
return `${status}`;
} else {
return 'Waiting...';
} }
if (status) {
return status;
}
return 'Waiting...';
})(); })();
const prefix = _.padEnd(getChalk().bold(service), this._prefixWidth); const prefix = _.padEnd(getChalk().bold(service), this._prefixWidth);

View File

@ -966,7 +966,7 @@ export async function makeBuildTasks(
deviceInfo: DeviceInfo, deviceInfo: DeviceInfo,
logger: Logger, logger: Logger,
projectName: string, projectName: string,
releaseHash: string = 'unavailable', releaseHash = 'unavailable',
preprocessHook?: (dockerfile: string) => string, preprocessHook?: (dockerfile: string) => string,
): Promise<MultiBuild.BuildTask[]> { ): Promise<MultiBuild.BuildTask[]> {
const multiBuild = await import('@balena/compose/dist/multibuild'); const multiBuild = await import('@balena/compose/dist/multibuild');
@ -1492,7 +1492,7 @@ export function createRunLoop(tick: (...args: any[]) => void) {
}, },
end() { end() {
clearInterval(timerId); clearInterval(timerId);
return runloop.onEnd(); runloop.onEnd();
}, },
}; };
return runloop; return runloop;
@ -1549,7 +1549,7 @@ function dropEmptyLinesStream() {
if (str.trim()) { if (str.trim()) {
this.push(str); this.push(str);
} }
return cb(); cb();
}); });
} }
@ -1570,7 +1570,7 @@ function buildLogCapture(objectMode: boolean, buffer: string[]) {
buffer.push(data); buffer.push(data);
} }
return cb(null, data); cb(null, data);
}); });
} }
@ -1585,14 +1585,16 @@ function buildProgressAdapter(inline: boolean) {
return through({ objectMode: true }, function (str, _enc, cb) { return through({ objectMode: true }, function (str, _enc, cb) {
if (str == null) { if (str == null) {
return cb(null, str); cb(null, str);
return;
} }
if (inline) { if (inline) {
return cb(null, { status: str }); cb(null, { status: str });
return;
} }
if (!/^Successfully tagged /.test(str)) { if (!str.startsWith('Successfully tagged ')) {
const match = stepRegex.exec(str); const match = stepRegex.exec(str);
if (match) { if (match) {
step = match[1]; step = match[1];
@ -1607,7 +1609,7 @@ function buildProgressAdapter(inline: boolean) {
} }
} }
return cb(null, { status: str, progress }); cb(null, { status: str, progress });
}); });
} }

View File

@ -81,7 +81,7 @@ export async function generateApplicationConfig(
)) as ImgConfig; )) as ImgConfig;
// merge sshKeys to config, when they have been specified // merge sshKeys to config, when they have been specified
if (options.os && options.os.sshKeys) { if (options.os?.sshKeys) {
// Create config.os object if it does not exist // Create config.os object if it does not exist
config.os = config.os ? config.os : {}; config.os = config.os ? config.os : {};
config.os.sshKeys = config.os.sshKeys config.os.sshKeys = config.os.sshKeys

View File

@ -86,7 +86,7 @@ const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
obj = JSON.parse(data); obj = JSON.parse(data);
} catch (e) { } catch (e) {
logger.logError('Error parsing reply from remote side'); logger.logError('Error parsing reply from remote side');
reject(e); reject(e as Error);
return; return;
} }
@ -232,7 +232,7 @@ export const deployLegacy = async function (
username, username,
appName, appName,
]); ]);
await uploadLogs(...args); uploadLogs(...args);
} }
return buildId; return buildId;

View File

@ -74,7 +74,7 @@ export class DeviceAPI {
public constructor( public constructor(
private logger: Logger, private logger: Logger,
addr: string, addr: string,
port: number = 48484, port = 48484,
) { ) {
this.deviceAddress = `http://${addr}:${port}/`; this.deviceAddress = `http://${addr}:${port}/`;
} }
@ -201,7 +201,7 @@ export class DeviceAPI {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = request.get(url); const req = request.get(url);
req.on('error', reject).on('response', async (res) => { req.on('error', reject).on('response', (res) => {
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
reject( reject(
new ApiErrors.DeviceAPIError( new ApiErrors.DeviceAPIError(
@ -213,7 +213,7 @@ export class DeviceAPI {
try { try {
res.socket.setKeepAlive(true, 1000); res.socket.setKeepAlive(true, 1000);
} catch (error) { } catch (error) {
reject(error); reject(error as Error);
} }
resolve(res); resolve(res);
}); });
@ -238,7 +238,7 @@ export class DeviceAPI {
if (_.isObject(opts) && (opts as ObjectWithUrl).url != null) { if (_.isObject(opts) && (opts as ObjectWithUrl).url != null) {
// the `as string` shouldn't be necessary, but the type system // the `as string` shouldn't be necessary, but the type system
// is getting a little confused // is getting a little confused
url = (opts as ObjectWithUrl).url as string; url = (opts as ObjectWithUrl).url!;
} else if (typeof opts === 'string') { } else if (typeof opts === 'string') {
url = opts; url = opts;
} }
@ -252,21 +252,26 @@ export class DeviceAPI {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
return request(opts, (err, response, body) => { return request(opts, (err, response, body) => {
if (err) { if (err) {
return reject(err); reject(err as Error);
return;
} }
switch (response.statusCode) { switch (response.statusCode) {
case 200: case 200: {
return resolve(body); resolve(body);
case 400: return;
return reject( }
new ApiErrors.BadRequestDeviceAPIError(body.message), case 400: {
); reject(new ApiErrors.BadRequestDeviceAPIError(body.message));
case 503: return;
return reject( }
new ApiErrors.ServiceUnavailableAPIError(body.message), case 503: {
); reject(new ApiErrors.ServiceUnavailableAPIError(body.message));
default: return;
return reject(new ApiErrors.DeviceAPIError(body.message)); }
default: {
reject(new ApiErrors.DeviceAPIError(body.message));
return;
}
} }
}); });
}); });

View File

@ -74,11 +74,11 @@ interface ParsedEnvironment {
[serviceName: string]: { [key: string]: string }; [serviceName: string]: { [key: string]: string };
} }
async function environmentFromInput( function environmentFromInput(
envs: string[], envs: string[],
serviceNames: string[], serviceNames: string[],
logger: Logger, logger: Logger,
): Promise<ParsedEnvironment> { ): ParsedEnvironment {
// A normal environment variable regex, with an added part // A normal environment variable regex, with an added part
// to find a colon followed servicename at the start // to find a colon followed servicename at the start
const varRegex = /^(?:([^\s:]+):)?([^\s]+?)=(.*)$/; const varRegex = /^(?:([^\s:]+):)?([^\s]+?)=(.*)$/;
@ -143,7 +143,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
try { try {
globalLogger.logDebug('Checking we can access device'); globalLogger.logDebug('Checking we can access device');
await api.ping(); await api.ping();
} catch (e) { } catch {
throw new ExpectedError(stripIndent` throw new ExpectedError(stripIndent`
Could not communicate with device supervisor at address ${opts.deviceHost}:${port}. Could not communicate with device supervisor at address ${opts.deviceHost}:${port}.
Device may not have local mode enabled. Check with: Device may not have local mode enabled. Check with:
@ -191,10 +191,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
}); });
// Attempt to attach to the device's docker daemon // Attempt to attach to the device's docker daemon
const docker = connectToDocker( const docker = connectToDocker(opts.deviceHost, opts.devicePort ?? 2375);
opts.deviceHost,
opts.devicePort != null ? opts.devicePort : 2375,
);
await checkBuildSecretsRequirements(docker, opts.source); await checkBuildSecretsRequirements(docker, opts.source);
globalLogger.logDebug('Tarring all non-ignored files...'); globalLogger.logDebug('Tarring all non-ignored files...');
@ -231,7 +228,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
// Print a newline to clearly separate build time and runtime // Print a newline to clearly separate build time and runtime
console.log(); console.log();
const envs = await environmentFromInput( const envs = environmentFromInput(
opts.env, opts.env,
Object.getOwnPropertyNames(project.composition.services), Object.getOwnPropertyNames(project.composition.services),
globalLogger, globalLogger,
@ -388,7 +385,7 @@ async function performBuilds(
); );
// Check for failures // Check for failures
await inspectBuildResults(localImages); inspectBuildResults(localImages);
const imagesToRemove: string[] = []; const imagesToRemove: string[] = [];
@ -497,7 +494,7 @@ export async function rebuildSingleTask(
} }
await assignDockerBuildOpts(docker, [task], opts); await assignDockerBuildOpts(docker, [task], opts);
await assignOutputHandlers([task], logger, logHandler); assignOutputHandlers([task], logger, logHandler);
const [localImage] = await multibuild.performBuilds( const [localImage] = await multibuild.performBuilds(
[task], [task],
@ -568,7 +565,7 @@ async function assignDockerBuildOpts(
globalLogger.logDebug(`Using ${images.length} on-device images for cache...`); globalLogger.logDebug(`Using ${images.length} on-device images for cache...`);
await Promise.all( await Promise.all(
buildTasks.map(async (task: BuildTask) => { buildTasks.map((task: BuildTask) => {
task.dockerOpts = { task.dockerOpts = {
...(task.dockerOpts || {}), ...(task.dockerOpts || {}),
...{ ...{
@ -666,7 +663,7 @@ export function generateTargetState(
return targetState; return targetState;
} }
async function inspectBuildResults(images: LocalImage[]): Promise<void> { function inspectBuildResults(images: LocalImage[]): void {
const failures: LocalPushErrors.BuildFailure[] = []; const failures: LocalPushErrors.BuildFailure[] = [];
_.each(images, (image) => { _.each(images, (image) => {
@ -679,6 +676,6 @@ async function inspectBuildResults(images: LocalImage[]): Promise<void> {
}); });
if (failures.length > 0) { if (failures.length > 0) {
throw new LocalPushErrors.BuildError(failures).toString(); throw new LocalPushErrors.BuildError(failures);
} }
} }

View File

@ -191,8 +191,8 @@ export class LivepushManager {
); );
const eventQueue = this.updateEventsWaiting[$serviceName]; const eventQueue = this.updateEventsWaiting[$serviceName];
eventQueue.push(changedPath); eventQueue.push(changedPath);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.getDebouncedEventHandler($serviceName)(); void this.getDebouncedEventHandler($serviceName)();
}; };
const monitor = this.setupFilesystemWatcher( const monitor = this.setupFilesystemWatcher(
@ -252,7 +252,7 @@ export class LivepushManager {
try { try {
// sync because chokidar defines a sync interface // sync because chokidar defines a sync interface
stats = fs.lstatSync(filePath); stats = fs.lstatSync(filePath);
} catch (err) { } catch {
// OK: the file may have been deleted. See also: // OK: the file may have been deleted. See also:
// https://github.com/paulmillr/chokidar/blob/3.4.3/lib/fsevents-handler.js#L326-L328 // https://github.com/paulmillr/chokidar/blob/3.4.3/lib/fsevents-handler.js#L326-L328
// https://github.com/paulmillr/chokidar/blob/3.4.3/lib/nodefs-handler.js#L364 // https://github.com/paulmillr/chokidar/blob/3.4.3/lib/nodefs-handler.js#L364
@ -267,15 +267,15 @@ export class LivepushManager {
return dockerignore.ignores(relPath); return dockerignore.ignores(relPath);
}, },
}); });
monitor.on('add', (changedPath: string) => monitor.on('add', (changedPath: string) => {
changedPathHandler(serviceName, changedPath), changedPathHandler(serviceName, changedPath);
); });
monitor.on('change', (changedPath: string) => monitor.on('change', (changedPath: string) => {
changedPathHandler(serviceName, changedPath), changedPathHandler(serviceName, changedPath);
); });
monitor.on('unlink', (changedPath: string) => monitor.on('unlink', (changedPath: string) => {
changedPathHandler(serviceName, changedPath), changedPathHandler(serviceName, changedPath);
); });
return monitor; return monitor;
} }

View File

@ -57,7 +57,7 @@ export const dockerConnectionCliFlags = {
description: description:
'Docker daemon TCP port number (hint: 2375 for balena devices)', 'Docker daemon TCP port number (hint: 2375 for balena devices)',
char: 'p', char: 'p',
parse: async (p) => parseAsInteger(p, 'dockerPort'), parse: (p) => parseAsInteger(p, 'dockerPort'),
}), }),
ca: Flags.string({ ca: Flags.string({
description: 'Docker host TLS certificate authority file', description: 'Docker host TLS certificate authority file',
@ -169,9 +169,7 @@ export async function isBalenaEngine(docker: dockerode): Promise<boolean> {
// version of balenaEngine, but it was at one point (mis)spelt 'balaena': // version of balenaEngine, but it was at one point (mis)spelt 'balaena':
// https://github.com/balena-os/balena-engine/pull/32/files // https://github.com/balena-os/balena-engine/pull/32/files
const dockerVersion = (await docker.version()) as BalenaEngineVersion; const dockerVersion = (await docker.version()) as BalenaEngineVersion;
return !!( return !!dockerVersion.Engine?.match(/balena|balaena/);
dockerVersion.Engine && dockerVersion.Engine.match(/balena|balaena/)
);
} }
export async function getDocker( export async function getDocker(

View File

@ -84,7 +84,7 @@ export async function readFileWithEolConversion(
} }
// Analyse encoding // Analyse encoding
const encoding = await detectEncoding(fileBuffer); const encoding = detectEncoding(fileBuffer);
// Skip further processing of non-convertible encodings // Skip further processing of non-convertible encodings
if (!CONVERTIBLE_ENCODINGS.includes(encoding)) { if (!CONVERTIBLE_ENCODINGS.includes(encoding)) {
@ -132,10 +132,10 @@ export async function readFileWithEolConversion(
* @param fileBuffer File contents whose encoding should be detected * @param fileBuffer File contents whose encoding should be detected
* @param bytesRead Optional "file size" if smaller than the buffer size * @param bytesRead Optional "file size" if smaller than the buffer size
*/ */
export async function detectEncoding( export function detectEncoding(
fileBuffer: Buffer, fileBuffer: Buffer,
bytesRead = fileBuffer.length, bytesRead = fileBuffer.length,
): Promise<string> { ): string {
// empty file // empty file
if (bytesRead === 0) { if (bytesRead === 0) {
return ''; return '';

View File

@ -281,8 +281,7 @@ export function isWindowsComExeShell() {
// neither bash nor sh (e.g. not MSYS, MSYS2, Cygwin, WSL) // neither bash nor sh (e.g. not MSYS, MSYS2, Cygwin, WSL)
process.env.SHELL == null && process.env.SHELL == null &&
// Windows cmd.exe or PowerShell // Windows cmd.exe or PowerShell
process.env.ComSpec != null && process.env.ComSpec?.endsWith('cmd.exe')
process.env.ComSpec.endsWith('cmd.exe')
); );
} }
@ -366,7 +365,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
let url: InstanceType<typeof URL>; let url: InstanceType<typeof URL>;
try { try {
url = new URL(proxyUrl); url = new URL(proxyUrl);
} catch (_e) { } catch {
return; return;
} }
return { return {
@ -469,7 +468,7 @@ export function pickAndRename<T extends Dictionary<any>>(
let renameFrom = f; let renameFrom = f;
let renameTo = f; let renameTo = f;
const match = f.match(/(?<from>\S+)\s+=>\s+(?<to>\S+)/); const match = f.match(/(?<from>\S+)\s+=>\s+(?<to>\S+)/);
if (match && match.groups) { if (match?.groups) {
renameFrom = match.groups.from; renameFrom = match.groups.from;
renameTo = match.groups.to; renameTo = match.groups.to;
} }

View File

@ -118,7 +118,7 @@ export const isImageFresh = async (deviceType: string, version: string) => {
*/ */
export const isESR = (version: string) => { export const isESR = (version: string) => {
const match = version.match(/^v?(\d+)\.\d+\.\d+/); const match = version.match(/^v?(\d+)\.\d+\.\d+/);
const major = parseInt((match && match[1]) || '', 10); const major = parseInt(match?.[1] || '', 10);
return major >= 2018; // note: (NaN >= 2018) is false return major >= 2018; // note: (NaN >= 2018) is false
}; };

View File

@ -39,7 +39,7 @@ export async function disambiguateReleaseParam(
// Accepting short hashes of 7,8,9 chars. // Accepting short hashes of 7,8,9 chars.
const possibleUuidHashLength = [7, 8, 9, 32, 40, 62].includes(release.length); const possibleUuidHashLength = [7, 8, 9, 32, 40, 62].includes(release.length);
const hasLeadingZero = release[0] === '0'; const hasLeadingZero = release.startsWith('0');
const isOnlyNumerical = /^[0-9]+$/.test(release); const isOnlyNumerical = /^[0-9]+$/.test(release);
// Reject non-numerical values with invalid uuid/hash lengths // Reject non-numerical values with invalid uuid/hash lengths
@ -78,14 +78,16 @@ export async function disambiguateReleaseParam(
/** /**
* Convert to lowercase if looks like slug * Convert to lowercase if looks like slug
*/ */
export async function lowercaseIfSlug(s: string) { export function lowercaseIfSlug(s: string) {
return s.includes('/') ? s.toLowerCase() : s; return s.includes('/')
? Promise.resolve(s.toLowerCase())
: Promise.resolve(s);
} }
export function normalizeOsVersion(version: string) { export function normalizeOsVersion(version: string) {
// Note that `version` may also be 'latest', 'recommended', 'default' // Note that `version` may also be 'latest', 'recommended', 'default'
if (/^v?\d+\.\d+\.\d+/.test(version)) { if (/^v?\d+\.\d+\.\d+/.test(version)) {
if (version[0] === 'v') { if (version.startsWith('v')) {
version = version.slice(1); version = version.slice(1);
} }
} }

View File

@ -25,7 +25,7 @@ export function capitanoizeOclifUsage(
.toLowerCase(); .toLowerCase();
} }
export async function getCommandsFromManifest() { export function getCommandsFromManifest() {
const manifest = require('../../oclif.manifest.json'); const manifest = require('../../oclif.manifest.json');
if (manifest.commands == null) { if (manifest.commands == null) {

View File

@ -119,7 +119,7 @@ export const checkLoggedInIf = async (doCheck: boolean) => {
* *
* @throws {NotAvailableInOfflineModeError} * @throws {NotAvailableInOfflineModeError}
*/ */
export const checkNotUsingOfflineMode = async () => { export const checkNotUsingOfflineMode = () => {
if (process.env.BALENARC_OFFLINE_MODE) { if (process.env.BALENARC_OFFLINE_MODE) {
throw new NotAvailableInOfflineModeError(stripIndent` throw new NotAvailableInOfflineModeError(stripIndent`
This command requires an internet connection, and cannot be used in offline mode. This command requires an internet connection, and cannot be used in offline mode.

View File

@ -390,13 +390,12 @@ async function createApplication(
try { try {
const userInfo = await sdk.auth.getUserInfo(); const userInfo = await sdk.auth.getUserInfo();
username = userInfo.username; username = userInfo.username;
} catch (err) { } catch {
throw new sdk.errors.BalenaNotLoggedIn(); throw new sdk.errors.BalenaNotLoggedIn();
} }
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor
const applicationName = await new Promise<string>(async (resolve, reject) => { const applicationName = await new Promise<string>(async (resolve, reject) => {
// eslint-disable-next-line no-constant-condition
while (true) { while (true) {
try { try {
const appName = await getCliForm().ask({ const appName = await getCliForm().ask({
@ -418,11 +417,13 @@ async function createApplication(
'You already have a fleet with that name; please choose another.', 'You already have a fleet with that name; please choose another.',
); );
continue; continue;
} catch (err) { } catch {
return resolve(appName); resolve(appName);
return;
} }
} catch (err) { } catch (err) {
return reject(err); reject(err as Error);
return;
} }
} }
}); });
@ -452,9 +453,7 @@ async function generateApplicationConfig(
const manifest = await sdk.models.config.getDeviceTypeManifestBySlug( const manifest = await sdk.models.config.getDeviceTypeManifestBySlug(
app.is_for__device_type[0].slug, app.is_for__device_type[0].slug,
); );
const opts = const opts = manifest.options?.filter((opt) => opt.name !== 'network');
manifest.options &&
manifest.options.filter((opt) => opt.name !== 'network');
const override = { const override = {
appUpdatePollInterval: options.appUpdatePollInterval, appUpdatePollInterval: options.appUpdatePollInterval,

View File

@ -114,7 +114,7 @@ async function installQemu(arch: string, qemuPath: string) {
stream.resume(); stream.resume();
} }
} catch (err) { } catch (err) {
reject(err); reject(err as Error);
} }
}); });
request(qemuUrl) request(qemuUrl)

View File

@ -246,7 +246,8 @@ function getBuilderMessageHandler(
console.error(`[debug] handling message: ${JSON.stringify(obj)}`); console.error(`[debug] handling message: ${JSON.stringify(obj)}`);
} }
if (obj.type != null && obj.type === 'metadata') { if (obj.type != null && obj.type === 'metadata') {
return handleBuilderMetadata(obj, build); handleBuilderMetadata(obj, build);
return;
} }
if (obj.message) { if (obj.message) {
readline.clearLine(process.stdout, 0); readline.clearLine(process.stdout, 0);
@ -423,10 +424,20 @@ async function getRemoteBuildStream(
stream = buildRequest.pipe(JSONStream.parse('*')) as NodeJS.ReadStream; stream = buildRequest.pipe(JSONStream.parse('*')) as NodeJS.ReadStream;
} }
stream = stream stream = stream
.once('error', () => uploadSpinner.stop()) .once('error', () => {
.once('close', () => uploadSpinner.stop()) uploadSpinner.stop();
.once('data', () => uploadSpinner.stop()) })
.once('end', () => uploadSpinner.stop()) .once('close', () => {
.once('finish', () => uploadSpinner.stop()); uploadSpinner.stop();
})
.once('data', () => {
uploadSpinner.stop();
})
.once('end', () => {
uploadSpinner.stop();
})
.once('finish', () => {
uploadSpinner.stop();
});
return [buildRequest, stream]; return [buildRequest, stream];
} }

View File

@ -73,7 +73,7 @@ export function sshArgsForRemoteCommand({
...['-o', 'LogLevel=ERROR'], ...['-o', 'LogLevel=ERROR'],
...['-o', 'StrictHostKeyChecking=no'], ...['-o', 'StrictHostKeyChecking=no'],
...['-o', 'UserKnownHostsFile=/dev/null'], ...['-o', 'UserKnownHostsFile=/dev/null'],
...(proxyCommand && proxyCommand.length ...(proxyCommand?.length
? ['-o', `ProxyCommand=${proxyCommand.join(' ')}`] ? ['-o', `ProxyCommand=${proxyCommand.join(' ')}`]
: []), : []),
`${username}@${hostname}`, `${username}@${hostname}`,
@ -155,9 +155,9 @@ export async function runRemoteCommand({
[exitCode, exitSignal] = await new Promise((resolve, reject) => { [exitCode, exitSignal] = await new Promise((resolve, reject) => {
const ps = spawn(program, args, { stdio }) const ps = spawn(program, args, { stdio })
.on('error', reject) .on('error', reject)
.on('close', (code, signal) => .on('close', (code, signal) => {
resolve([code ?? undefined, signal ?? undefined]), resolve([code ?? undefined, signal ?? undefined]);
); });
if (ps.stdin && stdin && typeof stdin !== 'string') { if (ps.stdin && stdin && typeof stdin !== 'string') {
stdin.pipe(ps.stdin); stdin.pipe(ps.stdin);
@ -272,7 +272,7 @@ export async function getLocalDeviceCmdStdout(
export const isRootUserGood = _.memoize(async (hostname: string, port) => { export const isRootUserGood = _.memoize(async (hostname: string, port) => {
try { try {
await runRemoteCommand({ cmd: 'exit 0', hostname, port, ...stdioIgnore }); await runRemoteCommand({ cmd: 'exit 0', hostname, port, ...stdioIgnore });
} catch (e) { } catch {
return false; return false;
} }
return true; return true;

View File

@ -29,7 +29,11 @@ export function buffer(
new Promise(function (resolve, reject) { new Promise(function (resolve, reject) {
const fstream = fs.createReadStream(bufferFile); const fstream = fs.createReadStream(bufferFile);
fstream.on('open', () => resolve(fstream)).on('error', reject); fstream
.on('open', () => {
resolve(fstream);
})
.on('error', reject);
}), }),
); );
} }

View File

@ -105,7 +105,7 @@ async function spawnAndPipe(
}); });
} }
async function windosuExec( function windosuExec(
escapedArgs: string[], escapedArgs: string[],
stderr?: NodeJS.WritableStream, stderr?: NodeJS.WritableStream,
): Promise<void> { ): Promise<void> {

View File

@ -42,9 +42,9 @@ export = (stream: NodeJS.WriteStream = process.stdout) => {
const showCursor = () => stream.write('\u001B[?25h'); const showCursor = () => stream.write('\u001B[?25h');
const cursorUp = (rows: number = 0) => stream.write(`\u001B[${rows}A`); const cursorUp = (rows = 0) => stream.write(`\u001B[${rows}A`);
const cursorDown = (rows: number = 0) => stream.write(`\u001B[${rows}B`); const cursorDown = (rows = 0) => stream.write(`\u001B[${rows}B`);
const write = (str: string) => stream.write(str); const write = (str: string) => stream.write(str);

View File

@ -61,11 +61,11 @@ export const tunnelConnectionToDevice = (
client.pipe(remote); client.pipe(remote);
remote.pipe(client); remote.pipe(client);
remote.on('error', (err) => { remote.on('error', (err) => {
console.error('Remote: ' + err); console.error(`Remote: ${err}`);
client.end(); client.end();
}); });
client.on('error', (err) => { client.on('error', (err) => {
console.error('Client: ' + err); console.error(`Client: ${err}`);
remote.end(); remote.end();
}); });
remote.on('close', () => { remote.on('close', () => {

View File

@ -97,24 +97,24 @@ export function parseAsInteger(input: string, paramName?: string) {
throw new ExpectedError(message); throw new ExpectedError(message);
} }
return Number(input); return Promise.resolve(Number(input));
} }
export function tryAsInteger(input: string): number | string { export async function tryAsInteger(input: string): Promise<number | string> {
try { try {
return parseAsInteger(input); return await parseAsInteger(input);
} catch { } catch {
return input; return input;
} }
} }
export async function parseAsLocalHostnameOrIp(input: string) { export function parseAsLocalHostnameOrIp(input: string) {
if (input && !validateLocalHostnameOrIp(input)) { if (input && !validateLocalHostnameOrIp(input)) {
throw new ExpectedError( throw new ExpectedError(
'The parameter must be a local hostname or IP address.', 'The parameter must be a local hostname or IP address.',
); );
} }
return input; return Promise.resolve(input);
} }
export function looksLikeFleetSlug(input: string) { export function looksLikeFleetSlug(input: string) {

View File

@ -31,7 +31,7 @@ chai.use(chaiAsPromised);
const { expect } = chai; const { expect } = chai;
async function getPage(name: string): Promise<string> { function getPage(name: string): string {
const pagePath = path.join( const pagePath = path.join(
__dirname, __dirname,
'..', '..',
@ -88,7 +88,7 @@ describe('Login server:', function () {
expect(body).to.equal(opt.expectedBody); expect(body).to.equal(opt.expectedBody);
resolve(); resolve();
} catch (err) { } catch (err) {
reject(err); reject(err as Error);
} }
}, },
); );
@ -143,7 +143,7 @@ describe('Login server:', function () {
it('should eventually be the token', async () => { it('should eventually be the token', async () => {
await testLogin({ await testLogin({
expectedBody: await getPage('success'), expectedBody: getPage('success'),
expectedStatusCode: 200, expectedStatusCode: 200,
expectedToken: tokens.johndoe.token, expectedToken: tokens.johndoe.token,
}); });
@ -162,7 +162,7 @@ describe('Login server:', function () {
it('should be rejected', async () => { it('should be rejected', async () => {
await testLogin({ await testLogin({
expectedBody: await getPage('error'), expectedBody: getPage('error'),
expectedStatusCode: 401, expectedStatusCode: 401,
expectedToken: tokens.johndoe.token, expectedToken: tokens.johndoe.token,
expectedErrorMsg: 'Invalid token', expectedErrorMsg: 'Invalid token',
@ -171,7 +171,7 @@ describe('Login server:', function () {
it('should be rejected if no token', async () => { it('should be rejected if no token', async () => {
await testLogin({ await testLogin({
expectedBody: await getPage('error'), expectedBody: getPage('error'),
expectedStatusCode: 401, expectedStatusCode: 401,
expectedToken: '', expectedToken: '',
expectedErrorMsg: 'No token', expectedErrorMsg: 'No token',
@ -180,7 +180,7 @@ describe('Login server:', function () {
it('should be rejected if token is malformed', async () => { it('should be rejected if token is malformed', async () => {
await testLogin({ await testLogin({
expectedBody: await getPage('error'), expectedBody: getPage('error'),
expectedStatusCode: 401, expectedStatusCode: 401,
expectedToken: 'asdf', expectedToken: 'asdf',
expectedErrorMsg: 'Invalid token', expectedErrorMsg: 'Invalid token',

View File

@ -267,15 +267,15 @@ describe('balena build', function () {
...fsMod, ...fsMod,
promises: { promises: {
...fsMod.promises, ...fsMod.promises,
access: async (p: string) => access: (p: string) =>
p === qemuBinPath ? undefined : fsMod.promises.access(p), p === qemuBinPath ? undefined : fsMod.promises.access(p),
stat: async (p: string) => stat: (p: string) =>
p === qemuBinPath ? { size: 1 } : fsMod.promises.stat(p), p === qemuBinPath ? { size: 1 } : fsMod.promises.stat(p),
}, },
}); });
mock(qemuModPath, { mock(qemuModPath, {
...qemuMod, ...qemuMod,
copyQemu: async () => '', copyQemu: () => '',
}); });
mock.reRequire('../../build/utils/qemu'); mock.reRequire('../../build/utils/qemu');
docker.expectGetInfo({ OperatingSystem: 'balenaOS 2.44.0+rev1' }); docker.expectGetInfo({ OperatingSystem: 'balenaOS 2.44.0+rev1' });

View File

@ -169,8 +169,12 @@ async function startMockSshServer(): Promise<[Server, number]> {
console.error(`[debug] mock ssh server: ${msg}`); console.error(`[debug] mock ssh server: ${msg}`);
} }
}; };
c.on('error', (err) => handler(err.message)); c.on('error', (err) => {
c.on('end', () => handler('client disconnected')); handler(err.message);
});
c.on('end', () => {
handler('client disconnected');
});
c.end(); c.end();
}); });
server.on('error', (err) => { server.on('error', (err) => {

View File

@ -462,7 +462,13 @@ describe('balena push', function () {
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
const projectPath = await new Promise<string>((resolve, reject) => { const projectPath = await new Promise<string>((resolve, reject) => {
const opts = { template: 'tmp-XXXXXX', unsafeCleanup: true }; const opts = { template: 'tmp-XXXXXX', unsafeCleanup: true };
tmp.dir(opts, (e, p) => (e ? reject(e) : resolve(p))); tmp.dir(opts, (e, p) => {
if (e) {
reject(e);
} else {
resolve(p);
}
});
}); });
console.error(`[debug] Temp project dir: ${projectPath}`); console.error(`[debug] Temp project dir: ${projectPath}`);
@ -475,7 +481,7 @@ describe('balena push', function () {
try { try {
server.listen(socketPath, resolve); server.listen(socketPath, resolve);
} catch (e) { } catch (e) {
reject(e); reject(e as Error);
} }
}); });
console.error(`[debug] Checking existence of socket at '${socketPath}'`); console.error(`[debug] Checking existence of socket at '${socketPath}'`);
@ -505,7 +511,13 @@ describe('balena push', function () {
// Terminate Unix Domain Socket server // Terminate Unix Domain Socket server
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
server.close((e) => (e ? reject(e) : resolve())); server.close((e) => {
if (e) {
reject(e);
} else {
resolve();
}
});
}); });
expect(await exists(socketPath), 'Socket existence').to.be.false; expect(await exists(socketPath), 'Socket existence').to.be.false;

View File

@ -26,7 +26,7 @@ describe('balena whoami', function () {
api = new BalenaAPIMock(); api = new BalenaAPIMock();
}); });
this.afterEach(async () => { this.afterEach(() => {
// Check all expected api calls have been made and clean up. // Check all expected api calls have been made and clean up.
api.done(); api.done();
}); });

View File

@ -26,7 +26,6 @@ import * as tar from 'tar-stream';
import { streamToBuffer } from 'tar-utils'; import { streamToBuffer } from 'tar-utils';
import { URL } from 'url'; import { URL } from 'url';
import { diff } from 'deep-object-diff'; import { diff } from 'deep-object-diff';
import { makeImageName } from '../build/utils/compose_ts'; import { makeImageName } from '../build/utils/compose_ts';
import { stripIndent } from '../build/utils/lazy'; import { stripIndent } from '../build/utils/lazy';
import type { BuilderMock } from './nock/builder-mock'; import type { BuilderMock } from './nock/builder-mock';
@ -77,13 +76,13 @@ export async function inspectTarStream(
type: header.type, type: header.type,
}; };
const expected = expectedFiles[header.name]; const expected = expectedFiles[header.name];
if (expected && expected.testStream) { if (expected?.testStream) {
await expected.testStream(header, stream, expected); await expected.testStream(header, stream, expected);
} else { } else {
await defaultTestStream(header, stream, expected, projectPath); await defaultTestStream(header, stream, expected, projectPath);
} }
} catch (err) { } catch (err) {
reject(err); reject(err as Error);
} }
next(); next();
}, },
@ -144,9 +143,8 @@ export async function expectStreamNoCRLF(
_header: tar.Headers, _header: tar.Headers,
stream: Readable, stream: Readable,
): Promise<void> { ): Promise<void> {
const chai = await import('chai');
const buf = await streamToBuffer(stream); const buf = await streamToBuffer(stream);
await chai.expect(buf.includes('\r\n')).to.be.false; expect(buf.includes('\r\n')).to.be.false;
} }
/** /**
@ -184,12 +182,13 @@ export async function testDockerBuildStream(o: {
o.dockerMock.expectPostBuild({ o.dockerMock.expectPostBuild({
...o, ...o,
checkURI: async (uri: string) => { checkURI: (uri: string) => {
const url = new URL(uri, 'http://test.net/'); const url = new URL(uri, 'http://test.net/');
const queryParams = Array.from(url.searchParams.entries()); const queryParams = Array.from(url.searchParams.entries());
expect(deepJsonParse(queryParams)).to.have.deep.members( expect(deepJsonParse(queryParams)).to.have.deep.members(
deepJsonParse(expectedQueryParams), deepJsonParse(expectedQueryParams),
); );
return Promise.resolve();
}, },
checkBuildRequestBody: (buildRequestBody: string) => checkBuildRequestBody: (buildRequestBody: string) =>
inspectTarStream(buildRequestBody, expectedFiles, projectPath), inspectTarStream(buildRequestBody, expectedFiles, projectPath),
@ -241,12 +240,13 @@ export async function testPushBuildStream(o: {
o.builderMock.expectPostBuild({ o.builderMock.expectPostBuild({
...o, ...o,
checkURI: async (uri: string) => { checkURI: (uri: string) => {
const url = new URL(uri, 'http://test.net/'); const url = new URL(uri, 'http://test.net/');
const queryParams = Array.from(url.searchParams.entries()); const queryParams = Array.from(url.searchParams.entries());
expect(deepJsonParse(queryParams)).to.have.deep.members( expect(deepJsonParse(queryParams)).to.have.deep.members(
deepJsonParse(expectedQueryParams), deepJsonParse(expectedQueryParams),
); );
return Promise.resolve();
}, },
checkBuildRequestBody: (buildRequestBody) => checkBuildRequestBody: (buildRequestBody) =>
inspectTarStream(buildRequestBody, o.expectedFiles, o.projectPath), inspectTarStream(buildRequestBody, o.expectedFiles, o.projectPath),

View File

@ -287,15 +287,14 @@ export function monochrome(text: string): string {
*/ */
export function fillTemplate( export function fillTemplate(
templateString: string, templateString: string,
templateVars: object, templateVars: Record<string, unknown>,
): string { ): string {
const escaped = templateString.replace(/\\/g, '\\\\').replace(/`/g, '\\`'); return templateString.replace(/\$\{(\w+)\}/g, (_, key) => {
const resolved = new Function( if (key in templateVars) {
...Object.keys(templateVars), return String(templateVars[key]);
`return \`${escaped}\`;`, }
).call(null, ...Object.values(templateVars)); throw new Error(`Missing template variable: ${key}`);
const unescaped = resolved.replace(/\\`/g, '`').replace(/\\\\/g, '\\'); });
return unescaped;
} }
/** /**

View File

@ -283,7 +283,7 @@ export class BalenaAPIMock extends NockMock {
this.optGet(/^\/v\d+\/service_environment_variable($|\?)/, opts).reply( 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 match = uri.match(/service_name%20eq%20%27(.+?)%27/);
const serviceName = (match && match[1]) || undefined; const serviceName = match?.[1] || undefined;
let varArray: any[]; let varArray: any[];
if (serviceName) { if (serviceName) {
const varObj = appServiceVarsByService[serviceName]; const varObj = appServiceVarsByService[serviceName];
@ -331,7 +331,7 @@ export class BalenaAPIMock extends NockMock {
opts, opts,
).reply(function (uri, _requestBody) { ).reply(function (uri, _requestBody) {
const match = uri.match(/service_name%20eq%20%27(.+?)%27/); const match = uri.match(/service_name%20eq%20%27(.+?)%27/);
const serviceName = (match && match[1]) || undefined; const serviceName = match?.[1] || undefined;
let varArray: any[]; let varArray: any[];
if (serviceName) { if (serviceName) {
const varObj = deviceServiceVarsByService[serviceName]; const varObj = deviceServiceVarsByService[serviceName];

View File

@ -37,7 +37,7 @@ export class NockMock {
constructor( constructor(
public basePathPattern: string | RegExp, public basePathPattern: string | RegExp,
public allowUnmocked: boolean = false, public allowUnmocked = false,
) { ) {
if (NockMock.instanceCount === 0) { if (NockMock.instanceCount === 0) {
if (!nock.isActive()) { if (!nock.isActive()) {

View File

@ -50,7 +50,7 @@ export async function exists(fPath: string) {
try { try {
await fs.stat(fPath); await fs.stat(fPath);
return true; return true;
} catch (e) { } catch {
return false; return false;
} }
} }

View File

@ -1,5 +1,5 @@
import * as stream from 'stream'; import * as stream from 'stream';
import { AssertionError, expect } from 'chai'; import { expect } from 'chai';
import { stub } from 'sinon'; import { stub } from 'sinon';
import * as tmp from 'tmp'; import * as tmp from 'tmp';
import { delay } from '../../utils'; import { delay } from '../../utils';
@ -62,7 +62,7 @@ describe('image-manager', function () {
return stream.on('end', function () { return stream.on('end', function () {
expect(result).to.equal('Cache image'); expect(result).to.equal('Cache image');
return done(); done();
}); });
}); });
}); });
@ -153,9 +153,7 @@ describe('image-manager', function () {
const contents = await fsAsync const contents = await fsAsync
.stat(this.image.name + '.inprogress') .stat(this.image.name + '.inprogress')
.then(function () { .then(function () {
throw new AssertionError( throw new Error('Image cache should be deleted on failure');
'Image cache should be deleted on failure',
);
}) })
.catch((err) => { .catch((err) => {
if (err.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
@ -174,7 +172,7 @@ describe('image-manager', function () {
}); });
}); });
describe('given a stream with the mime property', async function () { describe('given a stream with the mime property', function () {
beforeEach(function () { beforeEach(function () {
this.osDownloadStub = stub(balena.models.os, 'download'); this.osDownloadStub = stub(balena.models.os, 'download');
const message = 'Lorem ipsum dolor sit amet'; const message = 'Lorem ipsum dolor sit amet';
@ -422,7 +420,7 @@ describe('image-manager', function () {
.then(function (stream) { .then(function (stream) {
let result = ''; let result = '';
stream.on('data', (chunk) => (result += chunk)); stream.on('data', (chunk: string) => (result += chunk));
stream.on('end', function () { stream.on('end', function () {
expect(result).to.equal('Lorem ipsum dolor sit amet'); expect(result).to.equal('Lorem ipsum dolor sit amet');
@ -554,7 +552,9 @@ describe('image-manager', function () {
mockFs({}); mockFs({});
}); });
afterEach(() => mockFs.restore()); afterEach(() => {
mockFs.restore();
});
it('should keep the cache directory removed', async function () { it('should keep the cache directory removed', async function () {
const exists = await fsExistsAsync(this.cacheDirectory); const exists = await fsExistsAsync(this.cacheDirectory);

View File

@ -210,31 +210,31 @@ describe('parseAsInteger() function', () => {
); );
}); });
it('should parse integers to number type', () => { it('should parse integers to number type', async () => {
expect(v.parseAsInteger('100')).to.equal(100); expect(await v.parseAsInteger('100')).to.equal(100);
expect(v.parseAsInteger('100')).to.be.a('number'); expect(await v.parseAsInteger('100')).to.be.a('number');
expect(v.parseAsInteger('0')).to.equal(0); expect(await v.parseAsInteger('0')).to.equal(0);
expect(v.parseAsInteger('0')).to.be.a('number'); expect(await v.parseAsInteger('0')).to.be.a('number');
}); });
}); });
describe('tryAsInteger() function', () => { describe('tryAsInteger() function', () => {
it('should return string with non-numeric characters as string', () => { it('should return string with non-numeric characters as string', async () => {
expect(v.tryAsInteger('abc')).to.be.a('string'); expect(await v.tryAsInteger('abc')).to.be.a('string');
expect(v.tryAsInteger('1a')).to.be.a('string'); expect(await v.tryAsInteger('1a')).to.be.a('string');
expect(v.tryAsInteger('a1')).to.be.a('string'); expect(await v.tryAsInteger('a1')).to.be.a('string');
expect(v.tryAsInteger('a')).to.be.a('string'); expect(await v.tryAsInteger('a')).to.be.a('string');
expect(v.tryAsInteger('1.0')).to.be.a('string'); expect(await v.tryAsInteger('1.0')).to.be.a('string');
}); });
it('should return numerical strings with leading zeros as string', () => { it('should return numerical strings with leading zeros as string', async () => {
expect(v.tryAsInteger('01')).to.be.a('string'); expect(await v.tryAsInteger('01')).to.be.a('string');
expect(v.tryAsInteger('001')).to.be.a('string'); expect(await v.tryAsInteger('001')).to.be.a('string');
}); });
it('should return numerical strings without leading zeros as number', () => { it('should return numerical strings without leading zeros as number', async () => {
expect(v.tryAsInteger('100')).to.be.a('number'); expect(await v.tryAsInteger('100')).to.be.a('number');
expect(v.tryAsInteger('256')).to.be.a('number'); expect(await v.tryAsInteger('256')).to.be.a('number');
expect(v.tryAsInteger('0')).to.be.a('number'); expect(await v.tryAsInteger('0')).to.be.a('number');
}); });
}); });

View File

@ -9,6 +9,7 @@
"./src/**/*", "./src/**/*",
"./patches/*", "./patches/*",
"./tests/**/*", "./tests/**/*",
"./typings/**/*" "./typings/**/*",
".mocharc-standalone.js",
] ]
} }

View File

@ -32,7 +32,7 @@ declare module 'JSONStream' {
recurse: boolean; recurse: boolean;
} }
export function parse(pattern: any | any[]): NodeJS.ReadWriteStream; export function parse(pattern: any): NodeJS.ReadWriteStream;
type NewlineOnlyIndicator = false; type NewlineOnlyIndicator = false;

View File

@ -16,7 +16,7 @@
*/ */
declare module 'balena-device-init' { declare module 'balena-device-init' {
import { DeviceTypeJson } from 'balena-sdk'; import type { DeviceTypeJson } from 'balena-sdk';
interface OperationState { interface OperationState {
operation: operation:
@ -83,23 +83,23 @@ declare module 'balena-device-init' {
export function configure( export function configure(
image: string, image: string,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType, manifest: DeviceTypeJson.DeviceType,
config: object, config: object,
options?: object, options?: object,
): Promise<InitializeEmitter>; ): Promise<InitializeEmitter>;
export function initialize( export function initialize(
image: string, image: string,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType, manifest: DeviceTypeJson.DeviceType,
config: object, config: object,
): Promise<InitializeEmitter>; ): Promise<InitializeEmitter>;
export function getImageOsVersion( export function getImageOsVersion(
image: string, image: string,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType, manifest: DeviceTypeJson.DeviceType,
): Promise<string | null>; ): Promise<string | null>;
export function getImageManifest( export function getImageManifest(
image: string, image: string,
): Promise<BalenaSdk.DeviceTypeJson.DeviceType.DeviceType | null>; ): Promise<DeviceTypeJson.DeviceType | null>;
} }