Enforce and improve lazy loading of resin-cli-form

Change-type: patch
This commit is contained in:
Pagan Gazzard 2020-07-08 18:03:10 +01:00
parent 0bd27bd7ac
commit eea8c83bff
14 changed files with 60 additions and 79 deletions

View File

@ -19,7 +19,7 @@ import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import type { Device } from 'balena-sdk';
import { ExpectedError } from '../../errors';
@ -78,7 +78,6 @@ export default class DeviceOsUpdateCmd extends Command {
const sdk = getBalenaSdk();
const patterns = await import('../../utils/patterns');
const form = await import('resin-cli-form');
// Get device info
const {
@ -121,7 +120,7 @@ export default class DeviceOsUpdateCmd extends Command {
);
}
} else {
targetOsVersion = await form.ask({
targetOsVersion = await getCliForm().ask({
message: 'Target OS version',
type: 'list',
choices: hupVersionInfo.versions.map((version) => ({

View File

@ -19,7 +19,7 @@ import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
interface FlagsDef {
@ -69,11 +69,10 @@ export default class DeviceRenameCmd extends Command {
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceRenameCmd);
const balena = getBalenaSdk();
const form = await import('resin-cli-form');
const newName =
params.newName ||
(await form.ask({
(await getCliForm().ask({
message: 'How do you want to name this device?',
type: 'input',
})) ||

View File

@ -18,7 +18,7 @@
import { flags } from '@oclif/command';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { getBalenaSdk, stripIndent, getCliForm } from '../utils/lazy';
import { ExpectedError } from '../errors';
interface FlagsDef {
@ -147,8 +147,7 @@ ${messages.reachingOut}`);
// Token
if (loginOptions.token) {
if (!token) {
const form = await import('resin-cli-form');
token = await form.ask({
token = await getCliForm().ask({
message: 'Session token or API key from the preferences page',
name: 'token',
type: 'input',

View File

@ -23,7 +23,7 @@ import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { CommandHelp } from '../../utils/oclif-utils';
const BOOT_PARTITION = 1;
@ -396,7 +396,6 @@ async function askQuestionsForDeviceType(
options: FlagsDef,
configJson?: import('../../utils/config').ImgConfig,
): Promise<Answers> {
const form = await import('resin-cli-form');
const helpers = await import('../../utils/helpers');
const answerSources: any[] = [camelifyConfigOptions(options)];
const defaultAnswers: Partial<Answers> = {};
@ -436,7 +435,7 @@ async function askQuestionsForDeviceType(
extraOpts = { override: defaultAnswers };
}
return form.run(questions, extraOpts);
return getCliForm().run(questions, extraOpts);
}
/**

View File

@ -17,7 +17,7 @@ limitations under the License.
import * as commandOptions from './command-options';
import { normalizeUuidProp } from '../utils/normalization';
import { getBalenaSdk, getVisuals } from '../utils/lazy';
import { getBalenaSdk, getVisuals, getCliForm } from '../utils/lazy';
import * as _ from 'lodash';
const getUmountAsync = async () => {
@ -303,7 +303,6 @@ Examples:
normalizeUuidProp(options, 'device');
const Bluebird = require('bluebird');
const balena = getBalenaSdk();
const form = require('resin-cli-form');
const prettyjson = require('prettyjson');
const {
@ -378,7 +377,7 @@ See the help page for examples:
formOptions, // Pass params as an override: if there is any param with exactly the same name as a
) =>
// required option, that value is used (and the corresponding question is not asked)
form.run(formOptions, { override: options }),
getCliForm().run(formOptions, { override: options }),
)
.then(function (answers) {
answers.version = options.version;

View File

@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as dockerUtils from '../../utils/docker';
import { exitWithExpectedError } from '../../errors';
import { getChalk } from '../../utils/lazy';
import { getChalk, getCliForm } from '../../utils/lazy';
export const dockerPort = 2375;
export const dockerTimeout = 2000;
@ -26,7 +26,6 @@ export const selectContainerFromDevice = Bluebird.method(function (
if (filterSupervisor == null) {
filterSupervisor = false;
}
const form = require('resin-cli-form');
const docker = dockerUtils.createClient({
host: deviceIp,
port: dockerPort,
@ -45,7 +44,7 @@ export const selectContainerFromDevice = Bluebird.method(function (
exitWithExpectedError(`No containers found in ${deviceIp}`);
}
return form.ask({
return getCliForm().ask({
message: 'Select a container',
type: 'list',
choices: _.map(containers, function (container) {

View File

@ -17,7 +17,12 @@ limitations under the License.
import type { CommandDefinition } from 'capitano';
import type * as SDK from 'etcher-sdk';
import { getChalk, getVisuals, stripIndent } from '../../utils/lazy';
import {
getChalk,
getVisuals,
stripIndent,
getCliForm,
} from '../../utils/lazy';
import { ExpectedError } from '../../errors';
async function getDrive(options: {
@ -71,14 +76,13 @@ export const flash: CommandDefinition<
},
],
async action(params, options) {
const form = await import('resin-cli-form');
const { sourceDestination, multiWrite } = await import('etcher-sdk');
const drive = await getDrive(options);
const yes =
options.yes ||
(await form.ask({
(await getCliForm().ask({
message: 'This will erase the selected drive. Are you sure?',
type: 'confirm',
name: 'yes',

View File

@ -17,7 +17,7 @@ limitations under the License.
import * as commandOptions from './command-options';
import * as _ from 'lodash';
import { getBalenaSdk, getVisuals } from '../utils/lazy';
import { getBalenaSdk, getVisuals, getCliForm } from '../utils/lazy';
const formatVersion = function (v, isRecommended) {
let result = `v${v}`;
@ -35,7 +35,6 @@ const resolveVersion = function (deviceType, version) {
return Promise.resolve(version);
}
const form = require('resin-cli-form');
const balena = getBalenaSdk();
return balena.models.os
@ -46,7 +45,7 @@ const resolveVersion = function (deviceType, version) {
name: formatVersion(v, v === recommended),
}));
return form.ask({
return getCliForm().ask({
message: 'Select the OS version:',
type: 'list',
choices,
@ -185,7 +184,6 @@ const buildConfigForDeviceType = function (deviceType, advanced) {
if (advanced == null) {
advanced = false;
}
const form = require('resin-cli-form');
const helpers = require('../utils/helpers');
let override;
@ -201,7 +199,7 @@ const buildConfigForDeviceType = function (deviceType, advanced) {
}
}
return form.run(questions, { override });
return getCliForm().run(questions, { override });
};
const $buildConfig = function (image, deviceTypeSlug, advanced) {
@ -287,7 +285,6 @@ Examples:
action(params, options) {
const Bluebird = require('bluebird');
const umountAsync = Bluebird.promisify(require('umount').umount);
const form = require('resin-cli-form');
const patterns = require('../utils/patterns');
const helpers = require('../utils/helpers');
@ -298,7 +295,7 @@ ${INIT_WARNING_MESSAGE}\
`);
return Bluebird.resolve(helpers.getManifest(params.image, options.type))
.then((manifest) =>
form.run(manifest.initialization?.options, {
getCliForm().run(manifest.initialization?.options, {
override: {
drive: options.drive,
},

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import * as _ from 'lodash';
import { getBalenaSdk, getVisuals } from '../utils/lazy';
import { getBalenaSdk, getVisuals, getCliForm } from '../utils/lazy';
import * as dockerUtils from '../utils/docker';
const isCurrent = (commit) => commit === 'latest' || commit === 'current';
@ -83,7 +83,6 @@ const getApplicationsWithSuccessfulBuilds = function (deviceType) {
const selectApplication = function (deviceType) {
const visuals = getVisuals();
const form = require('resin-cli-form');
const { exitWithExpectedError } = require('../errors');
const applicationInfoSpinner = new visuals.Spinner(
@ -100,7 +99,7 @@ const selectApplication = function (deviceType) {
`You have no apps with successful releases for a '${deviceType}' device type.`,
);
}
return form.ask({
return getCliForm().ask({
message: 'Select an application',
type: 'list',
choices: applications.map((app) => ({
@ -112,7 +111,6 @@ const selectApplication = function (deviceType) {
};
const selectApplicationCommit = function (releases) {
const form = require('resin-cli-form');
const { exitWithExpectedError } = require('../errors');
if (releases.length === 0) {
@ -125,7 +123,7 @@ const selectApplicationCommit = function (releases) {
value: release.commit,
})),
);
return form.ask({
return getCliForm().ask({
message: 'Select a release',
type: 'list',
default: 'current',
@ -140,7 +138,6 @@ const offerToDisableAutomaticUpdates = function (
) {
const Bluebird = require('bluebird');
const balena = getBalenaSdk();
const form = require('resin-cli-form');
if (
isCurrent(commit) ||
@ -162,7 +159,7 @@ see https://balena.io/docs/reference/api/resources/device/#set-device-to-release
Alternatively you can pass the --pin-device-to-release flag to pin only this device to the selected release.\
`;
return form
return getCliForm()
.ask({
message,
type: 'confirm',

View File

@ -19,6 +19,7 @@ limitations under the License.
import type * as BalenaSdk from 'balena-sdk';
import type { Chalk } from 'chalk';
import type * as visuals from 'resin-cli-visuals';
import type * as CliForm from 'resin-cli-form';
import type { stripIndent as StripIndent } from 'common-tags';
// Equivalent of _.once but avoiding the need to import lodash for lazy deps
@ -52,6 +53,10 @@ export const getVisuals = once(
export const getChalk = once(() => require('chalk') as Chalk);
export const getCliForm = once(
() => require('resin-cli-form') as typeof CliForm,
);
// 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;

View File

@ -16,7 +16,6 @@ limitations under the License.
import { BalenaApplicationNotFound } from 'balena-errors';
import type * as BalenaSdk from 'balena-sdk';
import _ = require('lodash');
import _form = require('resin-cli-form');
import {
exitWithExpectedError,
@ -24,15 +23,13 @@ import {
NotLoggedInError,
ExpectedError,
} from '../errors';
import { getBalenaSdk, getVisuals, stripIndent } from './lazy';
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
import validation = require('./validation');
import { delay } from './helpers';
const getForm = _.once((): typeof _form => require('resin-cli-form'));
export function authenticate(options: {}): Promise<void> {
const balena = getBalenaSdk();
return getForm()
return getCliForm()
.run(
[
{
@ -56,7 +53,7 @@ export function authenticate(options: {}): Promise<void> {
return;
}
return getForm()
return getCliForm()
.ask({
message: 'Two factor auth challenge:',
name: 'code',
@ -91,7 +88,7 @@ export async function checkLoggedIn(): Promise<void> {
}
export function askLoginType() {
return getForm().ask<'web' | 'credentials' | 'token' | 'register'>({
return getCliForm().ask<'web' | 'credentials' | 'token' | 'register'>({
message: 'How would you like to login?',
name: 'loginType',
type: 'list',
@ -123,7 +120,7 @@ export function selectDeviceType() {
deviceTypes = _.sortBy(deviceTypes, 'name').filter(
(dt) => dt.state !== 'DISCONTINUED',
);
return getForm().ask({
return getCliForm().ask({
message: 'Device Type',
type: 'list',
choices: _.map(deviceTypes, ({ slug: value, name }) => ({
@ -147,7 +144,7 @@ export async function confirm(
return;
}
const confirmed = await getForm().ask<boolean>({
const confirmed = await getCliForm().ask<boolean>({
message,
type: 'confirm',
default: false,
@ -177,7 +174,7 @@ export function selectApplication(
})
.filter(filter || _.constant(true))
.then((applications) => {
return getForm().ask({
return getCliForm().ask({
message: 'Select an application',
type: 'list',
choices: _.map(applications, (application) => ({
@ -212,7 +209,7 @@ export function selectOrCreateApplication() {
value: null,
});
return getForm().ask({
return getCliForm().ask({
message: 'Select an application',
type: 'list',
choices: appOptions,
@ -224,7 +221,7 @@ export function selectOrCreateApplication() {
return application;
}
return getForm().ask({
return getCliForm().ask({
message: 'Choose a Name for your new application',
type: 'input',
validate: validation.validateApplicationName,
@ -324,7 +321,7 @@ export function inferOrSelectDevice(preferredUuid: string) {
? preferredUuid
: onlineDevices[0].uuid;
return getForm().ask({
return getCliForm().ask({
message: 'Select a device',
type: 'list',
default: defaultUuid,
@ -381,7 +378,7 @@ export async function getOnlineTargetUuid(
throw new ExpectedError('No accessible devices are online');
}
return await getForm().ask({
return await getCliForm().ask({
message: 'Select a device',
type: 'list',
default: devices[0].uuid,
@ -416,7 +413,7 @@ export function selectFromList<T>(
message: string,
choices: Array<T & { name: string }>,
): Promise<T> {
return getForm().ask<T>({
return getCliForm().ask<T>({
message,
type: 'list',
choices: _.map(choices, (s) => ({

View File

@ -17,7 +17,7 @@
import type * as BalenaSdk from 'balena-sdk';
import { ExpectedError, printErrorMessage } from '../errors';
import { getVisuals, stripIndent } from './lazy';
import { getVisuals, stripIndent, getCliForm } from './lazy';
import Logger = require('./logger');
import { exec, execBuffered, getDeviceOsRelease } from './ssh';
@ -206,7 +206,6 @@ async function getOrSelectApplication(
appName?: string,
): Promise<BalenaSdk.Application> {
const _ = await import('lodash');
const form = await import('resin-cli-form');
const allDeviceTypes = await sdk.models.config.getDeviceTypes();
const deviceTypeManifest = _.find(allDeviceTypes, { slug: deviceType });
@ -227,12 +226,7 @@ async function getOrSelectApplication(
.value();
if (!appName) {
return createOrSelectAppOrExit(
form,
sdk,
compatibleDeviceTypes,
deviceType,
);
return createOrSelectAppOrExit(sdk, compatibleDeviceTypes, deviceType);
}
const options: BalenaSdk.PineOptionsFor<BalenaSdk.Application> = {};
@ -254,7 +248,7 @@ async function getOrSelectApplication(
const applications = await sdk.models.application.getAll(options);
if (applications.length === 0) {
const shouldCreateApp = await form.ask({
const shouldCreateApp = await getCliForm().ask({
message:
`No application found with name "${appName}".\n` +
'Would you like to create it now?',
@ -288,7 +282,6 @@ async function getOrSelectApplication(
// `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(
form: any,
sdk: BalenaSdk.BalenaSDK,
compatibleDeviceTypes: string[],
deviceType: string,
@ -301,7 +294,7 @@ async function createOrSelectAppOrExit(
const applications = await sdk.models.application.getAll(options);
if (applications.length === 0) {
const shouldCreateApp = await form.ask({
const shouldCreateApp = await getCliForm().ask({
message:
'You have no applications this device can join.\n' +
'Would you like to create one now?',
@ -322,7 +315,6 @@ async function createApplication(
deviceType: string,
name?: string,
): Promise<BalenaSdk.Application> {
const form = await import('resin-cli-form');
const validation = await import('./validation');
let username = await sdk.auth.whoami();
@ -334,7 +326,7 @@ async function createApplication(
const applicationName = await new Promise<string>(async (resolve, reject) => {
while (true) {
try {
const appName = await form.ask({
const appName = await getCliForm().ask({
message: 'Enter a name for your new application:',
type: 'input',
default: name,
@ -376,7 +368,6 @@ async function generateApplicationConfig(
app: BalenaSdk.Application,
options: { version: string },
) {
const form = await import('resin-cli-form');
const { generateApplicationConfig: configGen } = await import('./config');
const manifest = await sdk.models.device.getManifestBySlug(app.device_type);
@ -384,7 +375,7 @@ async function generateApplicationConfig(
manifest.options &&
manifest.options.filter((opt) => opt.name !== 'network');
const values = {
...(opts ? await form.run(opts) : {}),
...(opts ? await getCliForm().run(opts) : {}),
...options,
};

View File

@ -2,6 +2,6 @@
"extends": "./node_modules/@balena/lint/config/tslint-prettier.json",
"rules": {
"ignoreDefinitionFiles": false,
"import-blacklist": [true, "resin-cli-visuals", "chalk", "common-tags"]
"import-blacklist": [true, "resin-cli-visuals", "chalk", "common-tags", "resin-cli-form"]
}
}

View File

@ -18,13 +18,13 @@
declare module 'resin-cli-form' {
import Bluebird = require('bluebird');
type TypeOrPromiseLike<T> = T | PromiseLike<T>;
export type TypeOrPromiseLike<T> = T | PromiseLike<T>;
type Validate = (
export type Validate = (
input: any,
) => TypeOrPromiseLike<boolean | string | undefined>;
interface AskOptions<T> {
export interface AskOptions<T> {
message: string;
type?: string;
name?: string;
@ -36,20 +36,16 @@ declare module 'resin-cli-form' {
validate?: Validate;
}
interface RunQuestion {
export interface RunQuestion {
message: string;
name: string;
type?: string;
validate?: Validate;
}
const form: {
ask: <T = string>(options: AskOptions<T>) => Bluebird<T>;
run: <T = any>(
questions?: RunQuestion[],
extraOptions?: { override?: object },
) => Bluebird<T>;
};
export = form;
export const ask: <T = string>(options: AskOptions<T>) => Bluebird<T>;
export const run: <T = any>(
questions?: RunQuestion[],
extraOptions?: { override?: object },
) => Bluebird<T>;
}