mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-18 18:56:25 +00:00
Merge pull request #2296 from balena-io/npm-audit
chore: Update dependencies (balena-lint, oclif, "npm audit fix")
This commit is contained in:
commit
fa4e8e7b55
@ -86,7 +86,7 @@ function importOclifCommands(jsFilename: string): OclifCommand[] {
|
||||
|
||||
const command: OclifCommand =
|
||||
jsFilename === 'help'
|
||||
? ((new FakeHelpCommand() as unknown) as OclifCommand)
|
||||
? (new FakeHelpCommand() as unknown as OclifCommand)
|
||||
: (require(path.join(process.cwd(), jsFilename)).default as OclifCommand);
|
||||
|
||||
return [command];
|
||||
|
@ -62,9 +62,12 @@ export async function release() {
|
||||
|
||||
/** Return a cached Octokit instance, creating a new one as needed. */
|
||||
const getOctokit = _.once(function () {
|
||||
const Octokit = (require('@octokit/rest') as typeof import('@octokit/rest')).Octokit.plugin(
|
||||
(require('@octokit/plugin-throttling') as typeof import('@octokit/plugin-throttling'))
|
||||
.throttling,
|
||||
const Octokit = (
|
||||
require('@octokit/rest') as typeof import('@octokit/rest')
|
||||
).Octokit.plugin(
|
||||
(
|
||||
require('@octokit/plugin-throttling') as typeof import('@octokit/plugin-throttling')
|
||||
).throttling,
|
||||
);
|
||||
return new Octokit({
|
||||
auth: GITHUB_TOKEN,
|
||||
@ -110,7 +113,8 @@ function getPageNumbers(
|
||||
if (!response.headers.link) {
|
||||
return res;
|
||||
}
|
||||
const parse = require('parse-link-header') as typeof import('parse-link-header');
|
||||
const parse =
|
||||
require('parse-link-header') as typeof import('parse-link-header');
|
||||
const parsed = parse(response.headers.link);
|
||||
if (parsed == null) {
|
||||
throw new Error(`Failed to parse link header: '${response.headers.link}'`);
|
||||
@ -158,12 +162,14 @@ async function updateGitHubReleaseDescriptions(
|
||||
per_page: perPage,
|
||||
});
|
||||
let errCount = 0;
|
||||
type Release = import('@octokit/rest').RestEndpointMethodTypes['repos']['listReleases']['response']['data'][0];
|
||||
type Release =
|
||||
import('@octokit/rest').RestEndpointMethodTypes['repos']['listReleases']['response']['data'][0];
|
||||
for await (const response of octokit.paginate.iterator<Release>(options)) {
|
||||
const { page: thisPage, pages: totalPages, ordinal } = getPageNumbers(
|
||||
response,
|
||||
perPage,
|
||||
);
|
||||
const {
|
||||
page: thisPage,
|
||||
pages: totalPages,
|
||||
ordinal,
|
||||
} = getPageNumbers(response, perPage);
|
||||
let i = 0;
|
||||
for (const cliRelease of response.data) {
|
||||
const prefix = `[#${ordinal + i++} pg ${thisPage}/${totalPages}]`;
|
||||
|
@ -111,8 +111,6 @@ ${dockerignoreHelp}
|
||||
|
||||
await Command.checkLoggedInIf(!!options.application);
|
||||
|
||||
// compositions with many services trigger misleading warnings
|
||||
// @ts-ignore editing property that isn't typed but does exist
|
||||
(await import('events')).defaultMaxListeners = 1000;
|
||||
|
||||
const sdk = getBalenaSdk();
|
||||
|
@ -68,7 +68,7 @@ export default class ConfigInjectCmd extends Command {
|
||||
ConfigInjectCmd,
|
||||
);
|
||||
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device/OS drive'));
|
||||
|
@ -54,7 +54,7 @@ export default class ConfigReadCmd extends Command {
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReadCmd);
|
||||
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
|
@ -58,7 +58,7 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReconfigureCmd);
|
||||
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
|
@ -75,7 +75,7 @@ export default class ConfigWriteCmd extends Command {
|
||||
ConfigWriteCmd,
|
||||
);
|
||||
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
const { denyMount, safeUmount } = await import('../../utils/umount');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
@ -87,9 +87,10 @@ export default class ConfigWriteCmd extends Command {
|
||||
console.info(`Setting ${params.key} to ${params.value}`);
|
||||
ConfigWriteCmd.updateConfigJson(configJSON, params.key, params.value);
|
||||
|
||||
await safeUmount(drive);
|
||||
|
||||
await config.write(drive, options.type, configJSON);
|
||||
await denyMount(drive, async () => {
|
||||
await safeUmount(drive);
|
||||
await config.write(drive, options.type, configJSON);
|
||||
});
|
||||
|
||||
console.info('Done');
|
||||
}
|
||||
|
@ -155,8 +155,6 @@ ${dockerignoreHelp}
|
||||
DeployCmd,
|
||||
);
|
||||
|
||||
// compositions with many services trigger misleading warnings
|
||||
// @ts-ignore editing property that isn't typed but does exist
|
||||
(await import('events')).defaultMaxListeners = 1000;
|
||||
|
||||
const logger = await Command.getLogger();
|
||||
@ -190,15 +188,13 @@ ${dockerignoreHelp}
|
||||
options['registry-secrets'],
|
||||
);
|
||||
} else {
|
||||
const {
|
||||
dockerfilePath,
|
||||
registrySecrets,
|
||||
} = await validateProjectDirectory(sdk, {
|
||||
dockerfilePath: options.dockerfile,
|
||||
noParentCheck: options['noparent-check'] || false,
|
||||
projectPath: options.source || '.',
|
||||
registrySecretsPath: options['registry-secrets'],
|
||||
});
|
||||
const { dockerfilePath, registrySecrets } =
|
||||
await validateProjectDirectory(sdk, {
|
||||
dockerfilePath: options.dockerfile,
|
||||
noParentCheck: options['noparent-check'] || false,
|
||||
projectPath: options.source || '.',
|
||||
registrySecretsPath: options['registry-secrets'],
|
||||
});
|
||||
options.dockerfile = dockerfilePath;
|
||||
options['registry-secrets'] = registrySecrets;
|
||||
}
|
||||
|
@ -111,7 +111,8 @@ export default class DeviceCmd extends Command {
|
||||
|
||||
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
|
||||
|
||||
const belongsToApplication = device.belongs_to__application as Application[];
|
||||
const belongsToApplication =
|
||||
device.belongs_to__application as Application[];
|
||||
device.application_name = belongsToApplication?.[0]
|
||||
? belongsToApplication[0].app_name
|
||||
: 'N/a';
|
||||
|
@ -103,7 +103,9 @@ export default class DeviceInitCmd extends Command {
|
||||
const application = (await getApplication(
|
||||
balena,
|
||||
options['application'] ||
|
||||
(await (await import('../../utils/patterns')).selectApplication()).id,
|
||||
(
|
||||
await (await import('../../utils/patterns')).selectApplication()
|
||||
).id,
|
||||
{
|
||||
$expand: {
|
||||
is_for__device_type: {
|
||||
|
@ -98,15 +98,17 @@ export default class DeviceMoveCmd extends Command {
|
||||
const devices = await Promise.all(
|
||||
deviceIds.map(
|
||||
(uuid) =>
|
||||
balena.models.device.get(uuid, expandForAppName) as Promise<
|
||||
ExtendedDevice
|
||||
>,
|
||||
balena.models.device.get(
|
||||
uuid,
|
||||
expandForAppName,
|
||||
) as Promise<ExtendedDevice>,
|
||||
),
|
||||
);
|
||||
|
||||
// Map application name for each device
|
||||
for (const device of devices) {
|
||||
const belongsToApplication = device.belongs_to__application as Application[];
|
||||
const belongsToApplication =
|
||||
device.belongs_to__application as Application[];
|
||||
device.application_name = belongsToApplication?.[0]
|
||||
? belongsToApplication[0].app_name
|
||||
: 'N/a';
|
||||
@ -168,7 +170,6 @@ export default class DeviceMoveCmd extends Command {
|
||||
compatibleDeviceTypes.some(
|
||||
(dt) => dt.slug === app.is_for__device_type[0].slug,
|
||||
) &&
|
||||
// @ts-ignore using the extended device object prop
|
||||
devices.some((device) => device.application_name !== app.app_name),
|
||||
true,
|
||||
);
|
||||
|
@ -79,19 +79,15 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
// Get device info
|
||||
const {
|
||||
uuid,
|
||||
is_of__device_type,
|
||||
os_version,
|
||||
os_variant,
|
||||
} = (await sdk.models.device.get(params.uuid, {
|
||||
$select: ['uuid', 'os_version', 'os_variant'],
|
||||
$expand: {
|
||||
is_of__device_type: {
|
||||
$select: 'slug',
|
||||
const { uuid, is_of__device_type, os_version, os_variant } =
|
||||
(await sdk.models.device.get(params.uuid, {
|
||||
$select: ['uuid', 'os_version', 'os_variant'],
|
||||
$expand: {
|
||||
is_of__device_type: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
},
|
||||
})) as DeviceWithDeviceType;
|
||||
})) as DeviceWithDeviceType;
|
||||
|
||||
// Get current device OS version
|
||||
const currentOsVersion = sdk.models.device.getOsVersion({
|
||||
|
@ -96,7 +96,8 @@ export default class DevicesCmd extends Command {
|
||||
devices = devices.map(function (device) {
|
||||
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
|
||||
|
||||
const belongsToApplication = device.belongs_to__application as Application[];
|
||||
const belongsToApplication =
|
||||
device.belongs_to__application as Application[];
|
||||
device.application_name = belongsToApplication?.[0]?.app_name || null;
|
||||
|
||||
device.uuid = options.json ? device.uuid : device.uuid.slice(0, 7);
|
||||
|
@ -344,8 +344,10 @@ function fillInInfoFields(
|
||||
envVar.serviceName = (envVar.service as SDK.Service[])[0]?.service_name;
|
||||
} else if ('service_install' in envVar) {
|
||||
// envVar is of type DeviceServiceEnvironmentVariableInfo
|
||||
envVar.serviceName = ((envVar.service_install as SDK.ServiceInstall[])[0]
|
||||
?.installs__service as SDK.Service[])[0]?.service_name;
|
||||
envVar.serviceName = (
|
||||
(envVar.service_install as SDK.ServiceInstall[])[0]
|
||||
?.installs__service as SDK.Service[]
|
||||
)[0]?.service_name;
|
||||
}
|
||||
envVar.appName = appNameOrSlug;
|
||||
envVar.serviceName = envVar.serviceName || '*';
|
||||
|
@ -60,50 +60,36 @@ export default class LocalConfigureCmd extends Command {
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(LocalConfigureCmd);
|
||||
|
||||
const path = await import('path');
|
||||
const reconfix = await import('reconfix');
|
||||
const denymount = promisify(await import('denymount'));
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
const { denyMount, safeUmount } = await import('../../utils/umount');
|
||||
const Logger = await import('../../utils/logger');
|
||||
|
||||
const logger = Logger.getLogger();
|
||||
|
||||
const configurationSchema = await this.prepareConnectionFile(params.target);
|
||||
|
||||
await safeUmount(params.target);
|
||||
|
||||
const dmOpts: any = {};
|
||||
if (process.pkg) {
|
||||
// when running in a standalone pkg install, the 'denymount'
|
||||
// executable is placed on the same folder as process.execPath
|
||||
dmOpts.executablePath = path.join(
|
||||
path.dirname(process.execPath),
|
||||
'denymount',
|
||||
await denyMount(params.target, async () => {
|
||||
// TODO: safeUmount umounts drives like '/dev/sdc', but does not
|
||||
// umount image files like 'balena.img'
|
||||
await safeUmount(params.target);
|
||||
const config = await reconfix.readConfiguration(
|
||||
configurationSchema,
|
||||
params.target,
|
||||
);
|
||||
}
|
||||
|
||||
const dmHandler = (cb: () => void) =>
|
||||
reconfix
|
||||
.readConfiguration(configurationSchema, params.target)
|
||||
.then(async (config: any) => {
|
||||
logger.logDebug('Current config:');
|
||||
logger.logDebug(JSON.stringify(config));
|
||||
const answers = await this.getConfiguration(config);
|
||||
logger.logDebug('New config:');
|
||||
logger.logDebug(JSON.stringify(answers));
|
||||
|
||||
if (!answers.hostname) {
|
||||
await this.removeHostname(configurationSchema);
|
||||
}
|
||||
return await reconfix.writeConfiguration(
|
||||
configurationSchema,
|
||||
answers,
|
||||
params.target,
|
||||
);
|
||||
})
|
||||
.asCallback(cb);
|
||||
|
||||
await denymount(params.target, dmHandler, dmOpts);
|
||||
logger.logDebug('Current config:');
|
||||
logger.logDebug(JSON.stringify(config));
|
||||
const answers = await this.getConfiguration(config);
|
||||
logger.logDebug('New config:');
|
||||
logger.logDebug(JSON.stringify(answers));
|
||||
if (!answers.hostname) {
|
||||
await this.removeHostname(configurationSchema);
|
||||
}
|
||||
await reconfix.writeConfiguration(
|
||||
configurationSchema,
|
||||
answers,
|
||||
params.target,
|
||||
);
|
||||
});
|
||||
|
||||
console.log('Done!');
|
||||
}
|
||||
|
@ -83,8 +83,9 @@ export default class LocalFlashCmd extends Command {
|
||||
try {
|
||||
const info = await execAsync('cat /proc/version');
|
||||
distroVersion = info.stdout.toLowerCase();
|
||||
// tslint:disable-next-line: no-empty
|
||||
} catch {}
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
if (distroVersion.includes('microsoft')) {
|
||||
throw new ExpectedError(stripIndent`
|
||||
This command is known not to work on WSL. Please use a CLI release
|
||||
|
@ -74,9 +74,7 @@ export default class OsInitializeCmd extends Command {
|
||||
OsInitializeCmd,
|
||||
);
|
||||
|
||||
const { getManifest, safeUmount, sudo } = await import(
|
||||
'../../utils/helpers'
|
||||
);
|
||||
const { getManifest, sudo } = await import('../../utils/helpers');
|
||||
|
||||
console.info(`Initializing device ${INIT_WARNING_MESSAGE}`);
|
||||
|
||||
@ -96,6 +94,7 @@ export default class OsInitializeCmd extends Command {
|
||||
`Going to erase ${answers.drive}.`,
|
||||
true,
|
||||
);
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
await safeUmount(answers.drive);
|
||||
}
|
||||
|
||||
@ -108,6 +107,7 @@ export default class OsInitializeCmd extends Command {
|
||||
]);
|
||||
|
||||
if (answers.drive != null) {
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
await safeUmount(answers.drive);
|
||||
console.info(`You can safely remove ${answers.drive} now`);
|
||||
}
|
||||
|
@ -55,10 +55,8 @@ export default class OsVersionsCmd extends Command {
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(OsVersionsCmd);
|
||||
|
||||
const {
|
||||
versions: vs,
|
||||
recommended,
|
||||
} = await getBalenaSdk().models.os.getSupportedVersions(params.type);
|
||||
const { versions: vs, recommended } =
|
||||
await getBalenaSdk().models.os.getSupportedVersions(params.type);
|
||||
|
||||
vs.forEach((v) => {
|
||||
console.log(`v${v}` + (v === recommended ? ' (recommended)' : ''));
|
||||
|
@ -87,9 +87,8 @@ export default class ScanCmd extends Command {
|
||||
const ux = getCliUx();
|
||||
ux.action.start('Scanning for local balenaOS devices');
|
||||
|
||||
const localDevices: LocalBalenaOsDevice[] = await discover.discoverLocalBalenaOsDevices(
|
||||
discoverTimeout,
|
||||
);
|
||||
const localDevices: LocalBalenaOsDevice[] =
|
||||
await discover.discoverLocalBalenaOsDevices(discoverTimeout);
|
||||
const engineReachableDevices: boolean[] = await Promise.all(
|
||||
localDevices.map(async ({ address }: { address: string }) => {
|
||||
const docker = await dockerUtils.createClient({
|
||||
|
@ -136,7 +136,7 @@ export default class SshCmd extends Command {
|
||||
}
|
||||
|
||||
// Remote connection
|
||||
const { getProxyConfig, which } = await import('../utils/helpers');
|
||||
const { getProxyConfig } = await import('../utils/helpers');
|
||||
const { getOnlineTargetDeviceUuid } = await import('../utils/patterns');
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
@ -156,6 +156,7 @@ export default class SshCmd extends Command {
|
||||
|
||||
const deviceId = device.id;
|
||||
const supervisorVersion = device.supervisor_version;
|
||||
const { which } = await import('../utils/which');
|
||||
|
||||
const [whichProxytunnel, username, proxyUrl] = await Promise.all([
|
||||
useProxy ? which('proxytunnel', false) : undefined,
|
||||
@ -301,7 +302,7 @@ export default class SshCmd extends Command {
|
||||
// container
|
||||
const childProcess = await import('child_process');
|
||||
const { escapeRegExp } = await import('lodash');
|
||||
const { which } = await import('../utils/helpers');
|
||||
const { which } = await import('../utils/which');
|
||||
const { deviceContainerEngineBinary } = await import(
|
||||
'../utils/device/ssh'
|
||||
);
|
||||
|
10
lib/help.ts
10
lib/help.ts
@ -112,9 +112,8 @@ export default class BalenaHelp extends Help {
|
||||
typeof additionalCommands[0]?.usage === 'string'
|
||||
) {
|
||||
primaryCommands[0].usage = primaryCommands[0].usage.padEnd(usageLength);
|
||||
additionalCommands[0].usage = additionalCommands[0].usage.padEnd(
|
||||
usageLength,
|
||||
);
|
||||
additionalCommands[0].usage =
|
||||
additionalCommands[0].usage.padEnd(usageLength);
|
||||
}
|
||||
|
||||
// Output help
|
||||
@ -139,9 +138,8 @@ export default class BalenaHelp extends Help {
|
||||
console.log(' --help, -h');
|
||||
console.log(' --debug\n');
|
||||
|
||||
const {
|
||||
reachingOut,
|
||||
} = require('./utils/messages') as typeof import('./utils/messages');
|
||||
const { reachingOut } =
|
||||
require('./utils/messages') as typeof import('./utils/messages');
|
||||
console.log(reachingOut);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,8 @@
|
||||
export class CliSettings {
|
||||
public readonly settings: any;
|
||||
constructor() {
|
||||
this.settings = require('balena-settings-client') as typeof import('balena-settings-client');
|
||||
this.settings =
|
||||
require('balena-settings-client') as typeof import('balena-settings-client');
|
||||
}
|
||||
|
||||
public get<T>(name: string): T {
|
||||
|
@ -188,10 +188,8 @@ async function resolveOSVersion(deviceType: string, version: string) {
|
||||
return version;
|
||||
}
|
||||
|
||||
const {
|
||||
versions: vs,
|
||||
recommended,
|
||||
} = await getBalenaSdk().models.os.getSupportedVersions(deviceType);
|
||||
const { versions: vs, recommended } =
|
||||
await getBalenaSdk().models.os.getSupportedVersions(deviceType);
|
||||
|
||||
const choices = vs.map((v) => ({
|
||||
value: v,
|
||||
|
@ -42,15 +42,17 @@ import {
|
||||
import type { DeviceInfo } from './device/api';
|
||||
import { getBalenaSdk, getChalk, stripIndent } from './lazy';
|
||||
import Logger = require('./logger');
|
||||
import { exists } from './which';
|
||||
|
||||
/**
|
||||
* Given an array representing the raw `--release-tag` flag of the deploy and
|
||||
* push commands, parse it into separate arrays of release tag keys and values.
|
||||
* The returned keys and values arrays are guaranteed to be of the same length.
|
||||
*/
|
||||
export function parseReleaseTagKeysAndValues(
|
||||
releaseTags: string[],
|
||||
): { releaseTagKeys: string[]; releaseTagValues: string[] } {
|
||||
export function parseReleaseTagKeysAndValues(releaseTags: string[]): {
|
||||
releaseTagKeys: string[];
|
||||
releaseTagValues: string[];
|
||||
} {
|
||||
if (releaseTags.length === 0) {
|
||||
return { releaseTagKeys: [], releaseTagValues: [] };
|
||||
}
|
||||
@ -97,15 +99,6 @@ export async function applyReleaseTagKeysAndValues(
|
||||
);
|
||||
}
|
||||
|
||||
const exists = async (filename: string) => {
|
||||
try {
|
||||
await fs.access(filename);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const LOG_LENGTH_MAX = 512 * 1024; // 512KB
|
||||
const compositionFileNames = ['docker-compose.yml', 'docker-compose.yaml'];
|
||||
const hr =
|
||||
@ -285,16 +278,15 @@ export async function buildProject(opts: {
|
||||
|
||||
setTaskAttributes({ tasks, imageDescriptorsByServiceName, ...opts });
|
||||
|
||||
const transposeOptArray: Array<
|
||||
TransposeOptions | undefined
|
||||
> = await Promise.all(
|
||||
tasks.map((task) => {
|
||||
// Setup emulation if needed
|
||||
if (needsQemu && !task.external) {
|
||||
return qemuTransposeBuildStream({ task, ...opts });
|
||||
}
|
||||
}),
|
||||
);
|
||||
const transposeOptArray: Array<TransposeOptions | undefined> =
|
||||
await Promise.all(
|
||||
tasks.map((task) => {
|
||||
// Setup emulation if needed
|
||||
if (needsQemu && !task.external) {
|
||||
return qemuTransposeBuildStream({ task, ...opts });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
// transposeOptions may be undefined. That's OK.
|
||||
@ -498,9 +490,8 @@ async function setTaskProgressHooks({
|
||||
let rawStream;
|
||||
stream = createLogStream(stream);
|
||||
if (transposeOptions) {
|
||||
const buildThroughStream = transpose.getBuildThroughStream(
|
||||
transposeOptions,
|
||||
);
|
||||
const buildThroughStream =
|
||||
transpose.getBuildThroughStream(transposeOptions);
|
||||
rawStream = stream.pipe(buildThroughStream);
|
||||
} else {
|
||||
rawStream = stream;
|
||||
@ -766,10 +757,8 @@ async function newTarDirectory(
|
||||
const tar = await import('tar-stream');
|
||||
const pack = tar.pack();
|
||||
const serviceDirs = await getServiceDirsFromComposition(dir, composition);
|
||||
const {
|
||||
filteredFileList,
|
||||
dockerignoreFiles,
|
||||
} = await filterFilesWithDockerignore(dir, multiDockerignore, serviceDirs);
|
||||
const { filteredFileList, dockerignoreFiles } =
|
||||
await filterFilesWithDockerignore(dir, multiDockerignore, serviceDirs);
|
||||
printDockerignoreWarn(dockerignoreFiles, serviceDirs, multiDockerignore);
|
||||
for (const fileStats of filteredFileList) {
|
||||
pack.entry(
|
||||
@ -975,9 +964,10 @@ async function parseRegistrySecrets(
|
||||
}
|
||||
const raw = (await fs.readFile(secretsFilename)).toString();
|
||||
const multiBuild = await import('resin-multibuild');
|
||||
const registrySecrets = new multiBuild.RegistrySecretValidator().validateRegistrySecrets(
|
||||
isYaml ? require('js-yaml').load(raw) : JSON.parse(raw),
|
||||
);
|
||||
const registrySecrets =
|
||||
new multiBuild.RegistrySecretValidator().validateRegistrySecrets(
|
||||
isYaml ? require('js-yaml').load(raw) : JSON.parse(raw),
|
||||
);
|
||||
multiBuild.addCanonicalDockerHubEntry(registrySecrets);
|
||||
return registrySecrets;
|
||||
} catch (error) {
|
||||
@ -1279,17 +1269,20 @@ async function pushServiceImages(
|
||||
const { pushAndUpdateServiceImages } = await import('./compose');
|
||||
const releaseMod = await import('balena-release');
|
||||
logger.logInfo('Pushing images to registry...');
|
||||
await pushAndUpdateServiceImages(docker, token, taggedImages, async function (
|
||||
serviceImage,
|
||||
) {
|
||||
logger.logDebug(
|
||||
`Saving image ${serviceImage.is_stored_at__image_location}`,
|
||||
);
|
||||
if (skipLogUpload) {
|
||||
delete serviceImage.build_log;
|
||||
}
|
||||
await releaseMod.updateImage(pineClient, serviceImage.id, serviceImage);
|
||||
});
|
||||
await pushAndUpdateServiceImages(
|
||||
docker,
|
||||
token,
|
||||
taggedImages,
|
||||
async function (serviceImage) {
|
||||
logger.logDebug(
|
||||
`Saving image ${serviceImage.is_stored_at__image_location}`,
|
||||
);
|
||||
if (skipLogUpload) {
|
||||
delete serviceImage.build_log;
|
||||
}
|
||||
await releaseMod.updateImage(pineClient, serviceImage.id, serviceImage);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function deployProject(
|
||||
|
@ -216,7 +216,7 @@ export class DeviceAPI {
|
||||
const NetKeepalive = await import('net-keepalive');
|
||||
// Certain versions of typescript won't convert
|
||||
// this automatically
|
||||
const sock = (res.socket as any) as NodeJSSocketWithFileDescriptor;
|
||||
const sock = res.socket as any as NodeJSSocketWithFileDescriptor;
|
||||
// We send a tcp keepalive probe once every 5 seconds
|
||||
NetKeepalive.setKeepAliveInterval(sock, 5000);
|
||||
// After 5 failed probes, the connection is marked as
|
||||
@ -235,7 +235,7 @@ export class DeviceAPI {
|
||||
// A helper method for promisifying general (non-streaming) requests. Streaming
|
||||
// requests should use a seperate setup
|
||||
private static async promisifiedRequest<
|
||||
T extends Parameters<typeof request>[0]
|
||||
T extends Parameters<typeof request>[0],
|
||||
>(opts: T, logger?: Logger): Promise<any> {
|
||||
interface ObjectWithUrl {
|
||||
url?: string;
|
||||
|
@ -151,9 +151,8 @@ export class LivepushManager {
|
||||
// only happens when the dockerfile path is
|
||||
// specified differently - this should be patched
|
||||
// in resin-bundle-resolve
|
||||
this.dockerfilePaths[
|
||||
buildTask.serviceName
|
||||
] = this.getDockerfilePathFromTask(buildTask);
|
||||
this.dockerfilePaths[buildTask.serviceName] =
|
||||
this.getDockerfilePathFromTask(buildTask);
|
||||
} else {
|
||||
this.dockerfilePaths[buildTask.serviceName] = [
|
||||
buildTask.dockerfilePath,
|
||||
|
@ -90,9 +90,8 @@ export async function sudo(
|
||||
}
|
||||
|
||||
export function runCommand<T>(commandArgs: string[]): Promise<T> {
|
||||
const {
|
||||
isSubcommand,
|
||||
} = require('../preparser') as typeof import('../preparser');
|
||||
const { isSubcommand } =
|
||||
require('../preparser') as typeof import('../preparser');
|
||||
if (isSubcommand(commandArgs)) {
|
||||
commandArgs = [
|
||||
commandArgs[0] + ':' + commandArgs[1],
|
||||
@ -406,90 +405,6 @@ function windowsCmdExeEscapeArg(arg: string): string {
|
||||
return `"${arg.replace(/["]/g, '""')}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handling wrapper around the npm `which` package:
|
||||
* "Like the unix which utility. Finds the first instance of a specified
|
||||
* executable in the PATH environment variable. Does not cache the results,
|
||||
* so hash -r is not needed when the PATH changes."
|
||||
*
|
||||
* @param program Basename of a program, for example 'ssh'
|
||||
* @param rejectOnMissing If the program cannot be found, reject the promise
|
||||
* with an ExpectedError instead of fulfilling it with an empty string.
|
||||
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
|
||||
*/
|
||||
export async function which(
|
||||
program: string,
|
||||
rejectOnMissing = true,
|
||||
): Promise<string> {
|
||||
const whichMod = await import('which');
|
||||
let programPath: string;
|
||||
try {
|
||||
programPath = await whichMod(program);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (rejectOnMissing) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(
|
||||
`'${program}' program not found. Is it installed?`,
|
||||
);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return programPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call which(programName) and spawn() with the given arguments.
|
||||
*
|
||||
* If returnExitCodeOrSignal is true, the returned promise will resolve to
|
||||
* an array [code, signal] with the child process exit code number or exit
|
||||
* signal string respectively (as provided by the spawn close event).
|
||||
*
|
||||
* If returnExitCodeOrSignal is false, the returned promise will reject with
|
||||
* a custom error if the child process returns a non-zero exit code or a
|
||||
* non-empty signal string (as reported by the spawn close event).
|
||||
*
|
||||
* In either case and if spawn itself emits an error event or fails synchronously,
|
||||
* the returned promise will reject with a custom error that includes the error
|
||||
* message of spawn's error.
|
||||
*/
|
||||
export async function whichSpawn(
|
||||
programName: string,
|
||||
args: string[],
|
||||
options: import('child_process').SpawnOptions = { stdio: 'inherit' },
|
||||
returnExitCodeOrSignal = false,
|
||||
): Promise<[number | undefined, string | undefined]> {
|
||||
const { spawn } = await import('child_process');
|
||||
const program = await which(programName);
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] [${program}, ${args.join(', ')}]`);
|
||||
}
|
||||
let error: Error | undefined;
|
||||
let exitCode: number | undefined;
|
||||
let exitSignal: string | undefined;
|
||||
try {
|
||||
[exitCode, exitSignal] = await new Promise((resolve, reject) => {
|
||||
spawn(program, args, options)
|
||||
.on('error', reject)
|
||||
.on('close', (code, signal) => resolve([code, signal]));
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
if (error || (!returnExitCodeOrSignal && (exitCode || exitSignal))) {
|
||||
const msg = [
|
||||
`${programName} failed with exit code=${exitCode} signal=${exitSignal}:`,
|
||||
`[${program}, ${args.join(', ')}]`,
|
||||
...(error ? [`${error}`] : []),
|
||||
];
|
||||
throw new Error(msg.join('\n'));
|
||||
}
|
||||
return [exitCode, exitSignal];
|
||||
}
|
||||
|
||||
export interface ProxyConfig {
|
||||
host: string;
|
||||
port: string;
|
||||
@ -598,14 +513,13 @@ export function addSIGINTHandler(sigintHandler: () => void, once = true) {
|
||||
* @param theArgs Arguments to be passed to the task function
|
||||
*/
|
||||
export async function awaitInterruptibleTask<
|
||||
T extends (...args: any[]) => Promise<any>
|
||||
T extends (...args: any[]) => Promise<any>,
|
||||
>(task: T, ...theArgs: Parameters<T>): Promise<ReturnType<T>> {
|
||||
let sigintHandler: () => void = () => undefined;
|
||||
const sigintPromise = new Promise<T>((_resolve, reject) => {
|
||||
sigintHandler = () => {
|
||||
const {
|
||||
SIGINTError,
|
||||
} = require('../errors') as typeof import('../errors');
|
||||
const { SIGINTError } =
|
||||
require('../errors') as typeof import('../errors');
|
||||
reject(new SIGINTError('Task aborted on SIGINT signal'));
|
||||
};
|
||||
addSIGINTHandler(sigintHandler);
|
||||
@ -616,16 +530,3 @@ export async function awaitInterruptibleTask<
|
||||
process.removeListener('SIGINT', sigintHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if `drive` is mounted, and if so umount it. No-op on Windows. */
|
||||
export async function safeUmount(drive: string) {
|
||||
if (!drive) {
|
||||
return;
|
||||
}
|
||||
const { isMounted, umount } = await import('umount');
|
||||
const isMountedAsync = promisify(isMounted);
|
||||
if (await isMountedAsync(drive)) {
|
||||
const umountAsync = promisify(umount);
|
||||
await umountAsync(drive);
|
||||
}
|
||||
}
|
||||
|
@ -61,5 +61,6 @@ export const getCliForm = once(
|
||||
export const getCliUx = once(() => require('cli-ux').ux as typeof ux);
|
||||
|
||||
// Directly export stripIndent as we always use it immediately, but importing just `stripIndent` reduces startup time
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
export const stripIndent = require('common-tags/lib/stripIndent') as typeof StripIndent;
|
||||
export const stripIndent =
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
require('common-tags/lib/stripIndent') as typeof StripIndent;
|
||||
|
@ -268,13 +268,11 @@ export async function awaitDeviceOsUpdate(
|
||||
progressBar.update({ percentage: 0 });
|
||||
|
||||
const poll = async (): Promise<void> => {
|
||||
const [
|
||||
osUpdateStatus,
|
||||
{ overall_progress: osUpdateProgress },
|
||||
] = await Promise.all([
|
||||
balena.models.device.getOsUpdateStatus(uuid),
|
||||
balena.models.device.get(uuid, { $select: 'overall_progress' }),
|
||||
]);
|
||||
const [osUpdateStatus, { overall_progress: osUpdateProgress }] =
|
||||
await Promise.all([
|
||||
balena.models.device.getOsUpdateStatus(uuid),
|
||||
balena.models.device.get(uuid, { $select: 'overall_progress' }),
|
||||
]);
|
||||
if (osUpdateStatus.status === 'done') {
|
||||
console.info(
|
||||
`The device ${deviceName} has been updated to v${targetOsVersion} and will restart shortly!`,
|
||||
|
@ -36,7 +36,7 @@ export async function exec(
|
||||
cmd: string,
|
||||
stdout?: NodeJS.WritableStream,
|
||||
): Promise<void> {
|
||||
const { which } = await import('./helpers');
|
||||
const { which } = await import('./which');
|
||||
const program = await which('ssh');
|
||||
const args = [
|
||||
'-n',
|
||||
@ -132,7 +132,7 @@ export async function spawnSshAndThrowOnError(
|
||||
args: string[],
|
||||
options?: import('child_process').SpawnOptions,
|
||||
) {
|
||||
const { whichSpawn } = await import('./helpers');
|
||||
const { whichSpawn } = await import('./which');
|
||||
const [exitCode, exitSignal] = await whichSpawn(
|
||||
'ssh',
|
||||
args,
|
||||
|
153
lib/utils/umount.ts
Normal file
153
lib/utils/umount.ts
Normal file
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This module was inspired by the npm `umount` package:
|
||||
* https://www.npmjs.com/package/umount
|
||||
* With some important changes:
|
||||
* - Fix "Command Injection" security advisory 1512
|
||||
* https://www.npmjs.com/advisories/1512
|
||||
* - Port from CoffeeScript to TypeScript
|
||||
* - Convert callbacks to async/await
|
||||
*/
|
||||
|
||||
import * as child_process from 'child_process';
|
||||
import * as path from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execFile = promisify(child_process.execFile);
|
||||
|
||||
/**
|
||||
* Unmount a device on Linux or macOS. No-op on Windows.
|
||||
* @param device Device path, e.g. '/dev/disk2'
|
||||
*/
|
||||
export async function umount(device: string): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
return;
|
||||
}
|
||||
const { sanitizePath, whichBin } = await import('./which');
|
||||
// sanitize user's input (regular expression attacks ?)
|
||||
device = sanitizePath(device);
|
||||
const cmd: string[] = [];
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
cmd.push('/usr/sbin/diskutil', 'unmountDisk', 'force', device);
|
||||
} else {
|
||||
// Linux
|
||||
const glob = promisify(await import('glob'));
|
||||
// '?*' expands a base device path like '/dev/sdb' to an array of paths
|
||||
// like '/dev/sdb1', '/dev/sdb2', ..., '/dev/sdb11', ... (partitions)
|
||||
// that exist for balenaOS images and are needed as arguments to 'umount'
|
||||
// on Linux (otherwise, umount produces an error "/dev/sdb: not mounted")
|
||||
const devices = await glob(`${device}?*`, { nodir: true, nonull: true });
|
||||
cmd.push(await whichBin('umount'), ...devices);
|
||||
}
|
||||
if (cmd.length > 1) {
|
||||
let stderr = '';
|
||||
try {
|
||||
const proc = await execFile(cmd[0], cmd.slice(1));
|
||||
stderr = proc.stderr;
|
||||
} catch (err) {
|
||||
const msg = [
|
||||
'',
|
||||
`Error executing "${cmd.join(' ')}"`,
|
||||
stderr || '',
|
||||
err.message || '',
|
||||
];
|
||||
if (process.platform === 'linux') {
|
||||
// ignore errors like: "umount: /dev/sdb4: not mounted."
|
||||
if (process.env.DEBUG) {
|
||||
console.error(msg.join('\n[debug] '));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(msg.join('\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a device is mounted on Linux or macOS. Always true on Windows.
|
||||
* @param device Device path, e.g. '/dev/disk2'
|
||||
*/
|
||||
export async function isMounted(device: string): Promise<boolean> {
|
||||
if (process.platform === 'win32') {
|
||||
return true;
|
||||
}
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
const { whichBin } = await import('./which');
|
||||
const mountCmd = await whichBin('mount');
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
try {
|
||||
const proc = await execFile(mountCmd);
|
||||
stdout = proc.stdout;
|
||||
stderr = proc.stderr;
|
||||
} catch (err) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(
|
||||
`Error executing "${mountCmd}":\n${stderr}\n${err.message}`,
|
||||
);
|
||||
}
|
||||
const result = (stdout || '')
|
||||
.split('\n')
|
||||
.some((line) => line.startsWith(device));
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Check if `drive` is mounted and, if so, umount it. No-op on Windows. */
|
||||
export async function safeUmount(drive: string) {
|
||||
if (!drive) {
|
||||
return;
|
||||
}
|
||||
if (await isMounted(drive)) {
|
||||
await umount(drive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the `denymount` package. See:
|
||||
* https://github.com/balena-io-modules/denymount
|
||||
*/
|
||||
export async function denyMount(
|
||||
target: string,
|
||||
handler: () => any,
|
||||
opts: { autoMountOnSuccess?: boolean; executablePath?: string } = {},
|
||||
) {
|
||||
const denymount = promisify(await import('denymount'));
|
||||
if (process.pkg) {
|
||||
// when running in a standalone pkg install, the 'denymount'
|
||||
// executable is placed on the same folder as process.execPath
|
||||
opts.executablePath ||= path.join(
|
||||
path.dirname(process.execPath),
|
||||
'denymount',
|
||||
);
|
||||
}
|
||||
const dmHandler = async (cb: (err?: Error) => void) => {
|
||||
let err: Error | undefined;
|
||||
try {
|
||||
await handler();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
cb(err);
|
||||
};
|
||||
await denymount(target, dmHandler, opts);
|
||||
}
|
146
lib/utils/which.ts
Normal file
146
lib/utils/which.ts
Normal file
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { promises as fs, constants } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export const { F_OK, R_OK, W_OK, X_OK } = constants;
|
||||
|
||||
export async function exists(filename: string, mode = F_OK) {
|
||||
try {
|
||||
await fs.access(filename, mode);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace sequences of untowardly characters like /[<>:"/\\|?*\u0000-\u001F]/g
|
||||
* and '.' or '..' with an underscore, plus other rules enforced by the filenamify
|
||||
* package. See https://github.com/sindresorhus/filenamify/
|
||||
*/
|
||||
export function sanitizePath(filepath: string) {
|
||||
const filenamify = require('filenamify') as typeof import('filenamify');
|
||||
// normalize also converts forward slash to backslash on Windows
|
||||
return path
|
||||
.normalize(filepath)
|
||||
.split(path.sep)
|
||||
.map((f) => filenamify(f, { replacement: '_' }))
|
||||
.join(path.sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a program name like 'mount', search for it in a pre-defined set of
|
||||
* folders ('/usr/bin', '/bin', '/usr/sbin', '/sbin') and return the full path if found.
|
||||
*
|
||||
* For executables, in some scenarios, this can be more secure than allowing
|
||||
* any folder in the PATH. Only relevant on Linux or macOS.
|
||||
*/
|
||||
export async function whichBin(programName: string): Promise<string> {
|
||||
for (const dir of ['/usr/bin', '/bin', '/usr/sbin', '/sbin']) {
|
||||
const candidate = path.join(dir, programName);
|
||||
if (await exists(candidate, X_OK)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Error handling wrapper around the npm `which` package:
|
||||
* "Like the unix which utility. Finds the first instance of a specified
|
||||
* executable in the PATH environment variable. Does not cache the results,
|
||||
* so hash -r is not needed when the PATH changes."
|
||||
*
|
||||
* @param program Basename of a program, for example 'ssh'
|
||||
* @param rejectOnMissing If the program cannot be found, reject the promise
|
||||
* with an ExpectedError instead of fulfilling it with an empty string.
|
||||
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
|
||||
*/
|
||||
export async function which(
|
||||
program: string,
|
||||
rejectOnMissing = true,
|
||||
): Promise<string> {
|
||||
const whichMod = await import('which');
|
||||
let programPath: string;
|
||||
try {
|
||||
programPath = await whichMod(program);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (rejectOnMissing) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(
|
||||
`'${program}' program not found. Is it installed?`,
|
||||
);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return programPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call which(programName) and spawn() with the given arguments.
|
||||
*
|
||||
* If returnExitCodeOrSignal is true, the returned promise will resolve to
|
||||
* an array [code, signal] with the child process exit code number or exit
|
||||
* signal string respectively (as provided by the spawn close event).
|
||||
*
|
||||
* If returnExitCodeOrSignal is false, the returned promise will reject with
|
||||
* a custom error if the child process returns a non-zero exit code or a
|
||||
* non-empty signal string (as reported by the spawn close event).
|
||||
*
|
||||
* In either case and if spawn itself emits an error event or fails synchronously,
|
||||
* the returned promise will reject with a custom error that includes the error
|
||||
* message of spawn's error.
|
||||
*/
|
||||
export async function whichSpawn(
|
||||
programName: string,
|
||||
args: string[],
|
||||
options: import('child_process').SpawnOptions = { stdio: 'inherit' },
|
||||
returnExitCodeOrSignal = false,
|
||||
): Promise<[number | undefined, string | undefined]> {
|
||||
const { spawn } = await import('child_process');
|
||||
const program = await which(programName);
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] [${program}, ${args.join(', ')}]`);
|
||||
}
|
||||
let error: Error | undefined;
|
||||
let exitCode: number | undefined;
|
||||
let exitSignal: string | undefined;
|
||||
try {
|
||||
[exitCode, exitSignal] = await new Promise((resolve, reject) => {
|
||||
spawn(program, args, options)
|
||||
.on('error', reject)
|
||||
.on('close', (code, signal) => resolve([code, signal]));
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
if (error || (!returnExitCodeOrSignal && (exitCode || exitSignal))) {
|
||||
const msg = [
|
||||
`${programName} failed with exit code=${exitCode} signal=${exitSignal}:`,
|
||||
`[${program}, ${args.join(', ')}]`,
|
||||
...(error ? [`${error}`] : []),
|
||||
];
|
||||
throw new Error(msg.join('\n'));
|
||||
}
|
||||
return [exitCode, exitSignal];
|
||||
}
|
1935
npm-shrinkwrap.json
generated
1935
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@ -69,7 +69,7 @@
|
||||
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
|
||||
"ci": "npm run test && npm run catch-uncommitted",
|
||||
"watch": "gulp watch",
|
||||
"lint": "balena-lint -e ts -e js --typescript --fix automation/ completion/ lib/ typings/ tests/ bin/balena bin/balena-dev gulpfile.js .mocharc.js .mocharc-standalone.js",
|
||||
"lint": "balena-lint -e ts -e js --fix automation/ completion/ lib/ typings/ tests/ bin/balena bin/balena-dev gulpfile.js .mocharc.js .mocharc-standalone.js",
|
||||
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
||||
"prepare": "echo {} > bin/.fast-boot.json",
|
||||
"prepublishOnly": "npm run build"
|
||||
@ -111,7 +111,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^5.2.0",
|
||||
"@balena/lint": "^6.1.1",
|
||||
"@oclif/config": "^1.17.0",
|
||||
"@oclif/dev-cli": "^1.26.0",
|
||||
"@oclif/parser": "^3.8.5",
|
||||
@ -231,7 +231,9 @@
|
||||
"fast-boot2": "^1.1.0",
|
||||
"fast-levenshtein": "^3.0.0",
|
||||
"file-disk": "^8.0.1",
|
||||
"filenamify": "^4.3.0",
|
||||
"get-stdin": "^8.0.0",
|
||||
"glob": "^7.1.7",
|
||||
"global-agent": "^2.1.12",
|
||||
"global-tunnel-ng": "^2.1.1",
|
||||
"humanize": "0.0.9",
|
||||
@ -242,7 +244,7 @@
|
||||
"js-yaml": "^4.0.0",
|
||||
"klaw": "^3.0.0",
|
||||
"livepush": "^3.5.0",
|
||||
"lodash": "^4.17.20",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^3.0.4",
|
||||
"mixpanel": "^0.10.3",
|
||||
"moment": "^2.27.0",
|
||||
@ -250,7 +252,7 @@
|
||||
"ndjson": "^2.0.0",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"node-unzip-2": "^0.2.8",
|
||||
"oclif": "^1.16.1",
|
||||
"oclif": "^1.18.1",
|
||||
"open": "^7.1.0",
|
||||
"partitioninfo": "^6.0.2",
|
||||
"patch-package": "^6.4.7",
|
||||
@ -276,7 +278,6 @@
|
||||
"through2": "^2.0.3",
|
||||
"tmp": "^0.2.1",
|
||||
"typed-error": "^3.2.1",
|
||||
"umount": "^1.1.6",
|
||||
"update-notifier": "^4.1.0",
|
||||
"which": "^2.0.2",
|
||||
"window-size": "^1.1.0"
|
||||
|
@ -223,7 +223,7 @@ describe('balena deploy', function () {
|
||||
});
|
||||
} finally {
|
||||
await switchSentry(sentryStatus);
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
process.exit.restore();
|
||||
}
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ describe('balena ssh', function () {
|
||||
if (hasSshExecutable) {
|
||||
[sshServer, sshServerPort] = await startMockSshServer();
|
||||
}
|
||||
const modPath = '../../build/utils/helpers';
|
||||
const modPath = '../../build/utils/which';
|
||||
const mod = await import(modPath);
|
||||
mock(modPath, {
|
||||
...mod,
|
||||
@ -130,7 +130,7 @@ describe('balena ssh', function () {
|
||||
|
||||
/** Check whether the 'ssh' tool (executable) exists in the PATH */
|
||||
async function checkSsh(): Promise<boolean> {
|
||||
const { which } = await import('../../build/utils/helpers');
|
||||
const { which } = await import('../../build/utils/which');
|
||||
const sshPath = await which('ssh', false);
|
||||
if ((sshPath || '').includes('\\Windows\\System32\\OpenSSH\\ssh')) {
|
||||
// don't use Windows' built-in ssh tool for these test cases
|
||||
|
@ -100,9 +100,8 @@ export async function inspectTarStream(
|
||||
try {
|
||||
expect($expected).to.deep.equal(found);
|
||||
} catch (e) {
|
||||
const {
|
||||
diff,
|
||||
} = require('deep-object-diff') as typeof import('deep-object-diff');
|
||||
const { diff } =
|
||||
require('deep-object-diff') as typeof import('deep-object-diff');
|
||||
const diffStr = JSON.stringify(
|
||||
diff($expected, found),
|
||||
(_k, v) => (v === undefined ? 'undefined' : v),
|
||||
@ -216,7 +215,7 @@ export async function testDockerBuildStream(o: {
|
||||
}
|
||||
if (o.expectedExitCode != null) {
|
||||
if (process.env.BALENA_CLI_TEST_TYPE !== 'standalone') {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
sinon.assert.calledWith(process.exit);
|
||||
}
|
||||
expect(o.expectedExitCode).to.equal(exitCode);
|
||||
|
@ -48,7 +48,7 @@ describe('handleError() function', () => {
|
||||
'printExpectedErrorMessage',
|
||||
);
|
||||
captureException = sinon.stub();
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
sandbox.stub(ErrorsModule, 'getSentry').resolves({ captureException });
|
||||
processExit = sandbox.stub(process, 'exit');
|
||||
|
||||
|
@ -39,9 +39,8 @@ interface TestOutput {
|
||||
* @param testOutput
|
||||
*/
|
||||
function filterCliOutputForTests(testOutput: TestOutput): TestOutput {
|
||||
const {
|
||||
matchesNodeEngineVersionWarn,
|
||||
} = require('../automation/utils') as typeof import('../automation/utils');
|
||||
const { matchesNodeEngineVersionWarn } =
|
||||
require('../automation/utils') as typeof import('../automation/utils');
|
||||
return {
|
||||
exitCode: testOutput.exitCode,
|
||||
err: testOutput.err.filter(
|
||||
|
@ -130,9 +130,8 @@ export class NockMock {
|
||||
}
|
||||
|
||||
protected handleUnexpectedRequest(req: any) {
|
||||
const {
|
||||
interceptorServerPort,
|
||||
} = require('./proxy-server') as typeof import('./proxy-server');
|
||||
const { interceptorServerPort } =
|
||||
require('./proxy-server') as typeof import('./proxy-server');
|
||||
const o = req.options || {};
|
||||
const u = o.uri || {};
|
||||
const method = req.method;
|
||||
|
@ -86,16 +86,19 @@ async function createProxyServer(): Promise<[number, number]> {
|
||||
const interceptorPort = await createInterceptorServer();
|
||||
|
||||
const proxy = httpProxy.createProxyServer();
|
||||
proxy.on('error', function (
|
||||
err: Error,
|
||||
_req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
) {
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
const msg = `Proxy server error: ${err}`;
|
||||
console.error(msg);
|
||||
res.end(msg);
|
||||
});
|
||||
proxy.on(
|
||||
'error',
|
||||
function (
|
||||
err: Error,
|
||||
_req: http.IncomingMessage,
|
||||
res: http.ServerResponse,
|
||||
) {
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
const msg = `Proxy server error: ${err}`;
|
||||
console.error(msg);
|
||||
res.end(msg);
|
||||
},
|
||||
);
|
||||
|
||||
const server = http.createServer(function (
|
||||
req: http.IncomingMessage,
|
||||
@ -150,10 +153,9 @@ async function createInterceptorServer(): Promise<number> {
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] Interceptor forwarding for ${proxiedFor}`);
|
||||
}
|
||||
// tslint:disable-next-line:prefer-const
|
||||
let { protocol, hostname, port, path: urlPath, hash } = url.parse(
|
||||
proxiedFor,
|
||||
);
|
||||
const parsed = url.parse(proxiedFor);
|
||||
const { hash, hostname, path: urlPath } = parsed;
|
||||
let { port, protocol } = parsed;
|
||||
protocol = (protocol || 'http:').toLowerCase();
|
||||
port = port || (protocol === 'https:' ? '443' : '80');
|
||||
const reqOpts = {
|
||||
|
@ -46,7 +46,8 @@ class MockLivepushManager extends LivepushManager {
|
||||
api: {} as import('../../../lib/utils/device/api').DeviceAPI,
|
||||
logger: {} as import('../../../lib/utils/logger'),
|
||||
buildLogs: {},
|
||||
deployOpts: {} as import('../../../lib/utils/device/deploy').DeviceDeployOptions,
|
||||
deployOpts:
|
||||
{} as import('../../../lib/utils/device/deploy').DeviceDeployOptions,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,6 @@ describe('detectEncoding() function', function () {
|
||||
'node_modules/.bin/mocha',
|
||||
'node_modules/.bin/rimraf',
|
||||
'node_modules/.bin/gulp',
|
||||
'node_modules/.bin/coffeelint',
|
||||
'node_modules/.bin/tsc',
|
||||
'node_modules/.bin/balena-lint',
|
||||
'node_modules/.bin/balena-preload',
|
||||
|
27
typings/umount/index.d.ts
vendored
27
typings/umount/index.d.ts
vendored
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'umount' {
|
||||
export const umount: (
|
||||
device: string,
|
||||
callback: (err?: Error, stdout?: any, stderr?: any) => void,
|
||||
) => void;
|
||||
export const isMounted: (
|
||||
device: string,
|
||||
callback: (err: Error | null, isMounted?: boolean) => void,
|
||||
) => void;
|
||||
}
|
Loading…
Reference in New Issue
Block a user