mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-18 21:27:51 +00:00
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:
parent
36077cacda
commit
c0e7ae9c91
@ -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)'),
|
||||||
|
@ -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();
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
32
eslint.config.js
Normal 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
2145
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -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;
|
||||||
|
@ -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.`,
|
||||||
|
@ -76,6 +76,6 @@ export default class DeviceRegisterCmd extends Command {
|
|||||||
options.deviceType,
|
options.deviceType,
|
||||||
);
|
);
|
||||||
|
|
||||||
return result && result.uuid;
|
return result.uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
2
src/commands/env/rename.ts
vendored
2
src/commands/env/rename.ts
vendored
@ -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,
|
||||||
|
2
src/commands/env/rm.ts
vendored
2
src/commands/env/rm.ts
vendored
@ -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'),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
);
|
);
|
||||||
|
@ -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 || '')
|
||||||
|
@ -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>;
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -69,8 +69,7 @@ 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,
|
||||||
};
|
};
|
||||||
|
@ -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');
|
||||||
}
|
}
|
||||||
|
@ -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 ...')
|
||||||
|
@ -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);
|
||||||
|
@ -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');
|
||||||
|
@ -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', () => {
|
||||||
|
@ -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);
|
||||||
|
@ -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 });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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 '';
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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', () => {
|
||||||
|
@ -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) {
|
||||||
|
@ -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',
|
||||||
|
@ -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' });
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -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),
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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];
|
||||||
|
@ -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()) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"./src/**/*",
|
"./src/**/*",
|
||||||
"./patches/*",
|
"./patches/*",
|
||||||
"./tests/**/*",
|
"./tests/**/*",
|
||||||
"./typings/**/*"
|
"./typings/**/*",
|
||||||
|
".mocharc-standalone.js",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
2
typings/JSONStream/index.d.ts
vendored
2
typings/JSONStream/index.d.ts
vendored
@ -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;
|
||||||
|
|
||||||
|
10
typings/balena-device-init/index.d.ts
vendored
10
typings/balena-device-init/index.d.ts
vendored
@ -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>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user