mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-31 08:25:36 +00:00
Merge pull request #2312 from balena-io/remove-exitWithExpectedError
build, deploy: Extend CTRL-C coverage on Windows (PowerShell, cmd.exe)
This commit is contained in:
commit
8db36ccec9
@ -101,7 +101,7 @@ async function printMarkdown() {
|
||||
console.log(await renderMarkdown());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,17 +41,25 @@ function checkNpmVersion() {
|
||||
// the reason is that it would unnecessarily prevent end users from
|
||||
// using npm v6.4.1 that ships with Node 8. (It is OK for the
|
||||
// shrinkwrap file to get damaged if it is not going to be reused.)
|
||||
console.error(`\
|
||||
-------------------------------------------------------------------------------
|
||||
throw new Error(`\
|
||||
-----------------------------------------------------------------------------
|
||||
Error: npm version '${npmVersion}' detected. Please upgrade to npm v${requiredVersion} or later
|
||||
because of a bug that causes the 'npm-shrinkwrap.json' file to be damaged.
|
||||
At this point, however, your 'npm-shrinkwrap.json' file has already been
|
||||
damaged. Please revert it to the master branch state with a command such as:
|
||||
"git checkout master -- npm-shrinkwrap.json"
|
||||
Then re-run "npm install" using npm version ${requiredVersion} or later.
|
||||
-------------------------------------------------------------------------------`);
|
||||
process.exit(1);
|
||||
-----------------------------------------------------------------------------`);
|
||||
}
|
||||
}
|
||||
|
||||
checkNpmVersion();
|
||||
function main() {
|
||||
try {
|
||||
checkNpmVersion();
|
||||
} catch (e) {
|
||||
console.error(e.message || e);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
@ -54,9 +54,7 @@ export async function release() {
|
||||
try {
|
||||
await createGitHubRelease();
|
||||
} catch (err) {
|
||||
console.error('Release failed');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
throw new Error(`Error creating GitHub release:\n${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,6 @@ process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes(
|
||||
? ''
|
||||
: '1';
|
||||
|
||||
function exitWithError(error: Error | string): never {
|
||||
console.error(`Error: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trivial command-line parser. Check whether the command-line argument is one
|
||||
* of the following strings, then call the appropriate functions:
|
||||
@ -49,12 +44,12 @@ function exitWithError(error: Error | string): never {
|
||||
*
|
||||
* @param args Arguments to parse (default is process.argv.slice(2))
|
||||
*/
|
||||
export async function run(args?: string[]) {
|
||||
async function parse(args?: string[]) {
|
||||
args = args || process.argv.slice(2);
|
||||
console.log(`automation/run.ts process.argv=[${process.argv}]\n`);
|
||||
console.log(`automation/run.ts args=[${args}]`);
|
||||
console.error(`[debug] automation/run.ts process.argv=[${process.argv}]`);
|
||||
console.error(`[debug] automation/run.ts args=[${args}]`);
|
||||
if (_.isEmpty(args)) {
|
||||
return exitWithError('missing command-line arguments');
|
||||
throw new Error('missing command-line arguments');
|
||||
}
|
||||
const commands: { [cmd: string]: () => void | Promise<void> } = {
|
||||
'build:installer': buildOclifInstaller,
|
||||
@ -66,7 +61,7 @@ export async function run(args?: string[]) {
|
||||
};
|
||||
for (const arg of args) {
|
||||
if (!commands.hasOwnProperty(arg)) {
|
||||
return exitWithError(`command unknown: ${arg}`);
|
||||
throw new Error(`command unknown: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,9 +85,22 @@ export async function run(args?: string[]) {
|
||||
const cmdFunc = commands[arg];
|
||||
await cmdFunc();
|
||||
} catch (err) {
|
||||
return exitWithError(`"${arg}": ${err}`);
|
||||
if (typeof err === 'object') {
|
||||
err.message = `"${arg}": ${err.message}`;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** See jsdoc for parse() function above */
|
||||
export async function run(args?: string[]) {
|
||||
try {
|
||||
await parse(args);
|
||||
} catch (e) {
|
||||
console.error(e.message ? `Error: ${e.message}` : e);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
@ -11,8 +11,7 @@ const validateChangeType = (maybeChangeType: string = 'minor') => {
|
||||
case 'major':
|
||||
return maybeChangeType;
|
||||
default:
|
||||
console.error(`Invalid change type: '${maybeChangeType}'`);
|
||||
return process.exit(1);
|
||||
throw new Error(`Invalid change type: '${maybeChangeType}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,24 +64,17 @@ const getUpstreams = async () => {
|
||||
return upstream;
|
||||
};
|
||||
|
||||
const printUsage = (upstreams: Upstream[], upstreamName: string) => {
|
||||
console.error(
|
||||
`
|
||||
const getUsage = (upstreams: Upstream[], upstreamName: string) => `
|
||||
Usage: npm run update ${upstreamName} $version [$changeType=minor]
|
||||
|
||||
Upstream names: ${upstreams.map(({ repo }) => repo).join(', ')}
|
||||
`,
|
||||
);
|
||||
return process.exit(1);
|
||||
};
|
||||
`;
|
||||
|
||||
// TODO: Drop the wrapper function once we move to TS 3.8,
|
||||
// which will support top level await.
|
||||
async function main() {
|
||||
async function $main() {
|
||||
const upstreams = await getUpstreams();
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
return printUsage(upstreams, '$upstreamName');
|
||||
throw new Error(getUsage(upstreams, '$upstreamName'));
|
||||
}
|
||||
|
||||
const upstreamName = process.argv[2];
|
||||
@ -90,16 +82,15 @@ async function main() {
|
||||
const upstream = upstreams.find((v) => v.repo === upstreamName);
|
||||
|
||||
if (!upstream) {
|
||||
console.error(
|
||||
throw new Error(
|
||||
`Invalid upstream name '${upstreamName}', valid options: ${upstreams
|
||||
.map(({ repo }) => repo)
|
||||
.join(', ')}`,
|
||||
);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
if (process.argv.length < 4) {
|
||||
printUsage(upstreams, upstreamName);
|
||||
throw new Error(getUsage(upstreams, upstreamName));
|
||||
}
|
||||
|
||||
const packageName = upstream.module || upstream.repo;
|
||||
@ -108,8 +99,7 @@ async function main() {
|
||||
await run(`npm install ${packageName}@${process.argv[3]}`);
|
||||
const newVersion = await getVersion(packageName);
|
||||
if (newVersion === oldVersion) {
|
||||
console.error(`Already on version '${newVersion}'`);
|
||||
return process.exit(1);
|
||||
throw new Error(`Already on version '${newVersion}'`);
|
||||
}
|
||||
|
||||
console.log(`Updated ${upstreamName} from ${oldVersion} to ${newVersion}`);
|
||||
@ -137,4 +127,13 @@ async function main() {
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await $main();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
2
lib/commands/env/rm.ts
vendored
2
lib/commands/env/rm.ts
vendored
@ -91,8 +91,6 @@ export default class EnvRmCmd extends Command {
|
||||
await confirm(
|
||||
opt.yes || false,
|
||||
'Are you sure you want to delete the environment variable?',
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
@ -20,12 +20,7 @@ import type { BlockDevice } from 'etcher-sdk/build/source-destination';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import {
|
||||
getChalk,
|
||||
getCliForm,
|
||||
getVisuals,
|
||||
stripIndent,
|
||||
} from '../../utils/lazy';
|
||||
import { getChalk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
yes: boolean;
|
||||
@ -93,24 +88,15 @@ export default class LocalFlashCmd extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
const { sourceDestination, multiWrite } = await import('etcher-sdk');
|
||||
|
||||
const drive = await this.getDrive(options);
|
||||
|
||||
const yes =
|
||||
options.yes ||
|
||||
(await getCliForm().ask({
|
||||
message: 'This will erase the selected drive. Are you sure?',
|
||||
type: 'confirm',
|
||||
name: 'yes',
|
||||
default: false,
|
||||
}));
|
||||
|
||||
if (!yes) {
|
||||
console.log(getChalk().red.bold('Aborted image flash'));
|
||||
process.exit(0);
|
||||
}
|
||||
const { confirm } = await import('../../utils/patterns');
|
||||
await confirm(
|
||||
options.yes,
|
||||
'This will erase the selected drive. Are you sure?',
|
||||
);
|
||||
|
||||
const { sourceDestination, multiWrite } = await import('etcher-sdk');
|
||||
const file = new sourceDestination.File({
|
||||
path: params.image,
|
||||
});
|
||||
|
@ -92,7 +92,6 @@ export default class OsInitializeCmd extends Command {
|
||||
options.yes,
|
||||
`This will erase ${answers.drive}. Are you sure?`,
|
||||
`Going to erase ${answers.drive}.`,
|
||||
true,
|
||||
);
|
||||
const { safeUmount } = await import('../../utils/umount');
|
||||
await safeUmount(answers.drive);
|
||||
|
@ -286,24 +286,3 @@ export const printErrorMessage = function (message: string) {
|
||||
export const printExpectedErrorMessage = function (message: string) {
|
||||
console.error(`${message}\n`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Print a friendly error message and exit the CLI with an error code, BYPASSING
|
||||
* error reporting through Sentry.io's platform (raven.Raven.captureException).
|
||||
* Note that lib/errors.ts provides top-level error handling code to catch any
|
||||
* otherwise uncaught errors, AND to report them through Sentry.io. But many
|
||||
* "expected" errors (say, a JSON parsing error in a file provided by the user)
|
||||
* don't warrant reporting through Sentry.io. For such mundane errors, catch
|
||||
* them and call this function.
|
||||
*
|
||||
* DEPRECATED: Use `throw new ExpectedError(<message>)` instead.
|
||||
* If a specific process exit code x must be set, use process.exitCode = x
|
||||
*/
|
||||
export function exitWithExpectedError(message: string | Error): never {
|
||||
if (message instanceof Error) {
|
||||
({ message } = message);
|
||||
}
|
||||
|
||||
printErrorMessage(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
@ -447,7 +447,6 @@ var pushProgressRenderer = function (tty, prefix) {
|
||||
export class BuildProgressUI {
|
||||
constructor(tty, descriptors) {
|
||||
this._handleEvent = this._handleEvent.bind(this);
|
||||
this._handleInterrupt = this._handleInterrupt.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
this.end = this.end.bind(this);
|
||||
this._display = this._display.bind(this);
|
||||
@ -499,14 +498,7 @@ export class BuildProgressUI {
|
||||
this._serviceToDataMap[service] = event;
|
||||
}
|
||||
|
||||
_handleInterrupt() {
|
||||
this._cancelled = true;
|
||||
this.end();
|
||||
return process.exit(130); // 128 + SIGINT
|
||||
}
|
||||
|
||||
start() {
|
||||
process.on('SIGINT', this._handleInterrupt);
|
||||
this._tty.hideCursor();
|
||||
this._services.forEach((service) => {
|
||||
this.streams[service].write({ status: 'Preparing...' });
|
||||
@ -520,7 +512,6 @@ export class BuildProgressUI {
|
||||
return;
|
||||
}
|
||||
this._ended = true;
|
||||
process.removeListener('SIGINT', this._handleInterrupt);
|
||||
this._runloop?.end();
|
||||
this._runloop = null;
|
||||
|
||||
|
@ -237,7 +237,7 @@ interface Renderer {
|
||||
streams: Dictionary<NodeJS.ReadWriteStream>;
|
||||
}
|
||||
|
||||
export async function buildProject(opts: {
|
||||
export interface BuildProjectOpts {
|
||||
docker: Dockerode;
|
||||
logger: Logger;
|
||||
projectPath: string;
|
||||
@ -252,82 +252,99 @@ export async function buildProject(opts: {
|
||||
dockerfilePath?: string;
|
||||
nogitignore: boolean;
|
||||
multiDockerignore: boolean;
|
||||
}): Promise<BuiltImage[]> {
|
||||
const { logger, projectName } = opts;
|
||||
logger.logInfo(`Building for ${opts.arch}/${opts.deviceType}`);
|
||||
}
|
||||
|
||||
let buildSummaryByService: Dictionary<string> | undefined;
|
||||
export async function buildProject(
|
||||
opts: BuildProjectOpts,
|
||||
): Promise<BuiltImage[]> {
|
||||
await checkBuildSecretsRequirements(opts.docker, opts.projectPath);
|
||||
const compose = await import('resin-compose-parse');
|
||||
const imageDescriptors = compose.parse(opts.composition);
|
||||
const imageDescriptorsByServiceName = _.keyBy(
|
||||
imageDescriptors,
|
||||
'serviceName',
|
||||
);
|
||||
const renderer = await startRenderer({ imageDescriptors, ...opts });
|
||||
let buildSummaryByService: Dictionary<string> | undefined;
|
||||
try {
|
||||
await checkBuildSecretsRequirements(opts.docker, opts.projectPath);
|
||||
|
||||
const needsQemu = await installQemuIfNeeded({ ...opts, imageDescriptors });
|
||||
|
||||
const tarStream = await tarDirectory(opts.projectPath, opts);
|
||||
|
||||
const tasks: BuildTaskPlus[] = await makeBuildTasks(
|
||||
opts.composition,
|
||||
tarStream,
|
||||
const { awaitInterruptibleTask } = await import('./helpers');
|
||||
const [images, summaryMsgByService] = await awaitInterruptibleTask(
|
||||
$buildProject,
|
||||
imageDescriptors,
|
||||
renderer,
|
||||
opts,
|
||||
logger,
|
||||
projectName,
|
||||
);
|
||||
|
||||
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 });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
// transposeOptions may be undefined. That's OK.
|
||||
transposeOptArray.map((transposeOptions, index) =>
|
||||
setTaskProgressHooks({
|
||||
task: tasks[index],
|
||||
renderer,
|
||||
transposeOptions,
|
||||
...opts,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
logger.logDebug('Prepared tasks; building...');
|
||||
|
||||
const { BALENA_ENGINE_TMP_PATH } = await import('../config');
|
||||
const builder = await import('resin-multibuild');
|
||||
|
||||
const builtImages = await builder.performBuilds(
|
||||
tasks,
|
||||
opts.docker,
|
||||
BALENA_ENGINE_TMP_PATH,
|
||||
);
|
||||
|
||||
const [images, summaryMsgByService] = await inspectBuiltImages({
|
||||
builtImages,
|
||||
imageDescriptorsByServiceName,
|
||||
tasks,
|
||||
...opts,
|
||||
});
|
||||
buildSummaryByService = summaryMsgByService;
|
||||
|
||||
return images;
|
||||
} finally {
|
||||
renderer.end(buildSummaryByService);
|
||||
}
|
||||
}
|
||||
|
||||
async function $buildProject(
|
||||
imageDescriptors: ImageDescriptor[],
|
||||
renderer: Renderer,
|
||||
opts: BuildProjectOpts,
|
||||
): Promise<[BuiltImage[], Dictionary<string>]> {
|
||||
const { logger, projectName } = opts;
|
||||
logger.logInfo(`Building for ${opts.arch}/${opts.deviceType}`);
|
||||
|
||||
const needsQemu = await installQemuIfNeeded({ ...opts, imageDescriptors });
|
||||
|
||||
const tarStream = await tarDirectory(opts.projectPath, opts);
|
||||
|
||||
const tasks: BuildTaskPlus[] = await makeBuildTasks(
|
||||
opts.composition,
|
||||
tarStream,
|
||||
opts,
|
||||
logger,
|
||||
projectName,
|
||||
);
|
||||
|
||||
const imageDescriptorsByServiceName = _.keyBy(
|
||||
imageDescriptors,
|
||||
'serviceName',
|
||||
);
|
||||
|
||||
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 });
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
// transposeOptions may be undefined. That's OK.
|
||||
transposeOptArray.map((transposeOptions, index) =>
|
||||
setTaskProgressHooks({
|
||||
task: tasks[index],
|
||||
renderer,
|
||||
transposeOptions,
|
||||
...opts,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
logger.logDebug('Prepared tasks; building...');
|
||||
|
||||
const { BALENA_ENGINE_TMP_PATH } = await import('../config');
|
||||
const builder = await import('resin-multibuild');
|
||||
|
||||
const builtImages = await builder.performBuilds(
|
||||
tasks,
|
||||
opts.docker,
|
||||
BALENA_ENGINE_TMP_PATH,
|
||||
);
|
||||
|
||||
return await inspectBuiltImages({
|
||||
builtImages,
|
||||
imageDescriptorsByServiceName,
|
||||
tasks,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
async function startRenderer({
|
||||
imageDescriptors,
|
||||
inlineLogs,
|
||||
@ -1344,20 +1361,25 @@ export async function deployProject(
|
||||
logger.logDebug('Tagging images...');
|
||||
const taggedImages = await tagServiceImages(docker, images, serviceImages);
|
||||
try {
|
||||
const token = await getTokenForPreviousRepos(
|
||||
logger,
|
||||
appId,
|
||||
apiEndpoint,
|
||||
taggedImages,
|
||||
);
|
||||
await pushServiceImages(
|
||||
docker,
|
||||
logger,
|
||||
pineClient,
|
||||
taggedImages,
|
||||
token,
|
||||
skipLogUpload,
|
||||
);
|
||||
const { awaitInterruptibleTask } = await import('./helpers');
|
||||
// awaitInterruptibleTask throws SIGINTError on CTRL-C,
|
||||
// causing the release status to be set to 'failed'
|
||||
await awaitInterruptibleTask(async () => {
|
||||
const token = await getTokenForPreviousRepos(
|
||||
logger,
|
||||
appId,
|
||||
apiEndpoint,
|
||||
taggedImages,
|
||||
);
|
||||
await pushServiceImages(
|
||||
docker,
|
||||
logger,
|
||||
pineClient,
|
||||
taggedImages,
|
||||
token,
|
||||
skipLogUpload,
|
||||
);
|
||||
});
|
||||
release.status = 'success';
|
||||
} catch (err) {
|
||||
release.status = 'failed';
|
||||
|
@ -16,18 +16,12 @@ limitations under the License.
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import _ = require('lodash');
|
||||
|
||||
import {
|
||||
exitWithExpectedError,
|
||||
instanceOf,
|
||||
NotLoggedInError,
|
||||
ExpectedError,
|
||||
} from '../errors';
|
||||
import { instanceOf, NotLoggedInError, ExpectedError } from '../errors';
|
||||
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
|
||||
import validation = require('./validation');
|
||||
import { delay } from './helpers';
|
||||
import { isV13 } from './version';
|
||||
import type { Application, Device, Organization } from 'balena-sdk';
|
||||
import { getApplication } from './sdk';
|
||||
|
||||
export function authenticate(options: {}): Promise<void> {
|
||||
const balena = getBalenaSdk();
|
||||
@ -135,18 +129,16 @@ export function selectDeviceType() {
|
||||
|
||||
/**
|
||||
* Display interactive confirmation prompt.
|
||||
* If the user declines, then either an error will be thrown,
|
||||
* or `exitWithExpectedError` will be called (if exitIfDeclined true).
|
||||
* Throw ExpectedError if the user declines.
|
||||
* @param yesOption - automatically confirm if true
|
||||
* @param message - message to display with prompt
|
||||
* @param yesMessage - message to display if automatically confirming
|
||||
* @param exitIfDeclined - exitWithExpectedError when decline if true
|
||||
*/
|
||||
export async function confirm(
|
||||
yesOption: boolean,
|
||||
message: string,
|
||||
yesMessage?: string,
|
||||
exitIfDeclined = false,
|
||||
defaultValue = false,
|
||||
) {
|
||||
if (yesOption) {
|
||||
if (yesMessage) {
|
||||
@ -162,16 +154,11 @@ export async function confirm(
|
||||
const confirmed = await getCliForm().ask<boolean>({
|
||||
message,
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
default: defaultValue,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
const err = new ExpectedError('Aborted');
|
||||
// TODO remove this deprecated function (exitWithExpectedError)
|
||||
if (exitIfDeclined) {
|
||||
exitWithExpectedError(err);
|
||||
}
|
||||
throw err;
|
||||
throw new ExpectedError('Aborted');
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,11 +268,9 @@ export async function awaitDeviceOsUpdate(
|
||||
}
|
||||
|
||||
if (osUpdateStatus.error) {
|
||||
console.error(
|
||||
`Failed to complete Host OS update on device ${deviceName}!`,
|
||||
throw new ExpectedError(
|
||||
`Failed to complete Host OS update on device ${deviceName}\n${osUpdateStatus.error}`,
|
||||
);
|
||||
exitWithExpectedError(osUpdateStatus.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (osUpdateProgress !== null) {
|
||||
@ -379,6 +364,7 @@ export async function getOnlineTargetDeviceUuid(
|
||||
let app: Application;
|
||||
try {
|
||||
logger.logDebug(`Fetching fleet ${applicationOrDevice}`);
|
||||
const { getApplication } = await import('./sdk');
|
||||
app = await getApplication(sdk, applicationOrDevice);
|
||||
} catch (err) {
|
||||
const { BalenaApplicationNotFound } = await import('balena-errors');
|
||||
|
@ -19,6 +19,7 @@ import type * as BalenaSdk from 'balena-sdk';
|
||||
import { ExpectedError, printErrorMessage } from '../errors';
|
||||
import { getVisuals, stripIndent, getCliForm } from './lazy';
|
||||
import Logger = require('./logger');
|
||||
import { confirm } from './patterns';
|
||||
import { exec, execBuffered, getDeviceOsRelease } from './ssh';
|
||||
|
||||
const MIN_BALENAOS_VERSION = 'v2.14.0';
|
||||
@ -211,7 +212,7 @@ async function getOrSelectApplication(
|
||||
.value();
|
||||
|
||||
if (!appName) {
|
||||
return createOrSelectAppOrExit(sdk, compatibleDeviceTypes, deviceType);
|
||||
return createOrSelectApp(sdk, compatibleDeviceTypes, deviceType);
|
||||
}
|
||||
|
||||
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
||||
@ -239,17 +240,14 @@ async function getOrSelectApplication(
|
||||
)) as ApplicationWithDeviceType[];
|
||||
|
||||
if (applications.length === 0) {
|
||||
const shouldCreateApp = await getCliForm().ask({
|
||||
message:
|
||||
`No fleet found with name "${appName}".\n` +
|
||||
await confirm(
|
||||
false,
|
||||
`No fleet found with name "${appName}".\n` +
|
||||
'Would you like to create it now?',
|
||||
type: 'confirm',
|
||||
default: true,
|
||||
});
|
||||
if (shouldCreateApp) {
|
||||
return createApplication(sdk, deviceType, name);
|
||||
}
|
||||
process.exit(1);
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
return await createApplication(sdk, deviceType, name);
|
||||
}
|
||||
|
||||
// We've found at least one fleet with the given name.
|
||||
@ -269,10 +267,7 @@ async function getOrSelectApplication(
|
||||
return selectAppFromList(applications);
|
||||
}
|
||||
|
||||
// TODO: revisit this function's purpose. It was refactored out of
|
||||
// `getOrSelectApplication` above in order to satisfy some resin-lint v3
|
||||
// rules, but it looks like there's a fair amount of duplicate logic.
|
||||
async function createOrSelectAppOrExit(
|
||||
async function createOrSelectApp(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
compatibleDeviceTypes: string[],
|
||||
deviceType: string,
|
||||
@ -291,17 +286,14 @@ async function createOrSelectAppOrExit(
|
||||
})) as ApplicationWithDeviceType[];
|
||||
|
||||
if (applications.length === 0) {
|
||||
const shouldCreateApp = await getCliForm().ask({
|
||||
message:
|
||||
'You have no fleets this device can join.\n' +
|
||||
await confirm(
|
||||
false,
|
||||
'You have no fleets this device can join.\n' +
|
||||
'Would you like to create one now?',
|
||||
type: 'confirm',
|
||||
default: true,
|
||||
});
|
||||
if (shouldCreateApp) {
|
||||
return createApplication(sdk, deviceType);
|
||||
}
|
||||
process.exit(1);
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
return await createApplication(sdk, deviceType);
|
||||
}
|
||||
|
||||
return selectAppFromList(applications);
|
||||
|
Loading…
x
Reference in New Issue
Block a user