mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-18 02:39:49 +00:00
Merge pull request #2013 from balena-io/async-await-oclif
Convert oclif actions to async/await
This commit is contained in:
commit
ac0ce8f702
@ -196,7 +196,6 @@ ${dockerignoreHelp}
|
||||
buildOpts: any; // arguments to forward to docker build command
|
||||
},
|
||||
) {
|
||||
const Bluebird = await import('bluebird');
|
||||
const _ = await import('lodash');
|
||||
const doodles = await import('resin-doodles');
|
||||
const sdk = getBalenaSdk();
|
||||
@ -206,149 +205,139 @@ ${dockerignoreHelp}
|
||||
|
||||
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
||||
|
||||
return loadProject(logger, composeOpts, opts.image)
|
||||
.then(function (project) {
|
||||
if (
|
||||
project.descriptors.length > 1 &&
|
||||
!appType?.supports_multicontainer
|
||||
) {
|
||||
throw new ExpectedError(
|
||||
'Target application does not support multiple containers. Aborting!',
|
||||
);
|
||||
}
|
||||
try {
|
||||
const project = await loadProject(logger, composeOpts, opts.image);
|
||||
if (project.descriptors.length > 1 && !appType?.supports_multicontainer) {
|
||||
throw new ExpectedError(
|
||||
'Target application does not support multiple containers. Aborting!',
|
||||
);
|
||||
}
|
||||
|
||||
// find which services use images that already exist locally
|
||||
return (
|
||||
Bluebird.map(project.descriptors, function (d: any) {
|
||||
// unconditionally build (or pull) if explicitly requested
|
||||
if (opts.shouldPerformBuild) {
|
||||
return d;
|
||||
}
|
||||
return docker
|
||||
// find which services use images that already exist locally
|
||||
let servicesToSkip = await Promise.all(
|
||||
project.descriptors.map(async function (d: any) {
|
||||
// unconditionally build (or pull) if explicitly requested
|
||||
if (opts.shouldPerformBuild) {
|
||||
return d;
|
||||
}
|
||||
try {
|
||||
await docker
|
||||
.getImage(
|
||||
(typeof d.image === 'string' ? d.image : d.image.tag) || '',
|
||||
)
|
||||
.inspect()
|
||||
.then(() => {
|
||||
return d.serviceName;
|
||||
})
|
||||
.catch(() => {
|
||||
// Ignore
|
||||
});
|
||||
})
|
||||
.filter((d) => !!d)
|
||||
.then(function (servicesToSkip: any[]) {
|
||||
// multibuild takes in a composition and always attempts to
|
||||
// build or pull all services. we workaround that here by
|
||||
// passing a modified composition.
|
||||
const compositionToBuild = _.cloneDeep(project.composition);
|
||||
compositionToBuild.services = _.omit(
|
||||
compositionToBuild.services,
|
||||
servicesToSkip,
|
||||
);
|
||||
if (_.size(compositionToBuild.services) === 0) {
|
||||
logger.logInfo(
|
||||
'Everything is up to date (use --build to force a rebuild)',
|
||||
);
|
||||
return {};
|
||||
}
|
||||
return compose
|
||||
.buildProject(
|
||||
docker,
|
||||
logger,
|
||||
project.path,
|
||||
project.name,
|
||||
compositionToBuild,
|
||||
opts.app.arch,
|
||||
(opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
|
||||
opts.buildEmulated,
|
||||
opts.buildOpts,
|
||||
composeOpts.inlineLogs,
|
||||
composeOpts.convertEol,
|
||||
composeOpts.dockerfilePath,
|
||||
composeOpts.nogitignore,
|
||||
composeOpts.multiDockerignore,
|
||||
)
|
||||
.then((builtImages) => _.keyBy(builtImages, 'serviceName'));
|
||||
})
|
||||
.then((builtImages: any) =>
|
||||
project.descriptors.map(
|
||||
(d) =>
|
||||
builtImages[d.serviceName] ?? {
|
||||
serviceName: d.serviceName,
|
||||
name: typeof d.image === 'string' ? d.image : d.image.tag,
|
||||
logs: 'Build skipped; image for service already exists.',
|
||||
props: {},
|
||||
},
|
||||
),
|
||||
)
|
||||
// @ts-ignore slightly different return types of partial vs non-partial release
|
||||
.then(function (images) {
|
||||
if (appType?.is_legacy) {
|
||||
const { deployLegacy } = require('../utils/deploy-legacy');
|
||||
.inspect();
|
||||
|
||||
const msg = getChalk().yellow(
|
||||
'Target application requires legacy deploy method.',
|
||||
);
|
||||
logger.logWarn(msg);
|
||||
return d.serviceName;
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}),
|
||||
);
|
||||
servicesToSkip = servicesToSkip.filter((d) => !!d);
|
||||
|
||||
return Promise.all([
|
||||
sdk.auth.getToken(),
|
||||
sdk.auth.whoami(),
|
||||
sdk.settings.get('balenaUrl'),
|
||||
{
|
||||
// opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
|
||||
appName: opts.appName,
|
||||
imageName: images[0].name,
|
||||
buildLogs: images[0].logs,
|
||||
shouldUploadLogs: opts.shouldUploadLogs,
|
||||
},
|
||||
])
|
||||
.then(([token, username, url, options]) => {
|
||||
return deployLegacy(
|
||||
docker,
|
||||
logger,
|
||||
token,
|
||||
username,
|
||||
url,
|
||||
options,
|
||||
);
|
||||
})
|
||||
.then((releaseId) =>
|
||||
sdk.models.release.get(releaseId, { $select: ['commit'] }),
|
||||
);
|
||||
}
|
||||
return Promise.all([
|
||||
sdk.auth.getUserId(),
|
||||
sdk.auth.getToken(),
|
||||
sdk.settings.get('apiUrl'),
|
||||
]).then(([userId, auth, apiEndpoint]) =>
|
||||
$deployProject(
|
||||
docker,
|
||||
logger,
|
||||
project.composition,
|
||||
images,
|
||||
opts.app.id,
|
||||
userId,
|
||||
`Bearer ${auth}`,
|
||||
apiEndpoint,
|
||||
!opts.shouldUploadLogs,
|
||||
),
|
||||
);
|
||||
})
|
||||
// multibuild takes in a composition and always attempts to
|
||||
// build or pull all services. we workaround that here by
|
||||
// passing a modified composition.
|
||||
const compositionToBuild = _.cloneDeep(project.composition);
|
||||
compositionToBuild.services = _.omit(
|
||||
compositionToBuild.services,
|
||||
servicesToSkip,
|
||||
);
|
||||
if (_.size(compositionToBuild.services) === 0) {
|
||||
logger.logInfo(
|
||||
'Everything is up to date (use --build to force a rebuild)',
|
||||
);
|
||||
})
|
||||
.then(function (release: any) {
|
||||
logger.outputDeferredMessages();
|
||||
logger.logSuccess('Deploy succeeded!');
|
||||
logger.logSuccess(`Release: ${release.commit}`);
|
||||
console.log();
|
||||
console.log(doodles.getDoodle()); // Show charlie
|
||||
console.log();
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.logError('Deploy failed');
|
||||
throw err;
|
||||
});
|
||||
return {};
|
||||
}
|
||||
const builtImages = await compose.buildProject(
|
||||
docker,
|
||||
logger,
|
||||
project.path,
|
||||
project.name,
|
||||
compositionToBuild,
|
||||
opts.app.arch,
|
||||
(opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
|
||||
opts.buildEmulated,
|
||||
opts.buildOpts,
|
||||
composeOpts.inlineLogs,
|
||||
composeOpts.convertEol,
|
||||
composeOpts.dockerfilePath,
|
||||
composeOpts.nogitignore,
|
||||
composeOpts.multiDockerignore,
|
||||
);
|
||||
const builtImagesByService = _.keyBy(builtImages, 'serviceName');
|
||||
|
||||
const images = project.descriptors.map(
|
||||
(d) =>
|
||||
builtImagesByService[d.serviceName] ?? {
|
||||
serviceName: d.serviceName,
|
||||
name: typeof d.image === 'string' ? d.image : d.image.tag,
|
||||
logs: 'Build skipped; image for service already exists.',
|
||||
props: {},
|
||||
},
|
||||
);
|
||||
|
||||
let release;
|
||||
if (appType?.is_legacy) {
|
||||
const { deployLegacy } = require('../utils/deploy-legacy');
|
||||
|
||||
const msg = getChalk().yellow(
|
||||
'Target application requires legacy deploy method.',
|
||||
);
|
||||
logger.logWarn(msg);
|
||||
|
||||
const [token, username, url, options] = await Promise.all([
|
||||
sdk.auth.getToken(),
|
||||
sdk.auth.whoami(),
|
||||
sdk.settings.get('balenaUrl'),
|
||||
{
|
||||
// opts.appName may be prefixed by 'owner/', unlike opts.app.app_name
|
||||
appName: opts.appName,
|
||||
imageName: images[0].name,
|
||||
buildLogs: images[0].logs,
|
||||
shouldUploadLogs: opts.shouldUploadLogs,
|
||||
},
|
||||
]);
|
||||
const releaseId = await deployLegacy(
|
||||
docker,
|
||||
logger,
|
||||
token,
|
||||
username,
|
||||
url,
|
||||
options,
|
||||
);
|
||||
|
||||
release = await sdk.models.release.get(releaseId, {
|
||||
$select: ['commit'],
|
||||
});
|
||||
} else {
|
||||
const [userId, auth, apiEndpoint] = await Promise.all([
|
||||
sdk.auth.getUserId(),
|
||||
sdk.auth.getToken(),
|
||||
sdk.settings.get('apiUrl'),
|
||||
]);
|
||||
release = await $deployProject(
|
||||
docker,
|
||||
logger,
|
||||
project.composition,
|
||||
images,
|
||||
opts.app.id,
|
||||
userId,
|
||||
`Bearer ${auth}`,
|
||||
apiEndpoint,
|
||||
!opts.shouldUploadLogs,
|
||||
);
|
||||
}
|
||||
|
||||
logger.outputDeferredMessages();
|
||||
logger.logSuccess('Deploy succeeded!');
|
||||
logger.logSuccess(`Release: ${release.commit}`);
|
||||
console.log();
|
||||
console.log(doodles.getDoodle()); // Show charlie
|
||||
console.log();
|
||||
} catch (err) {
|
||||
logger.logError('Deploy failed');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,26 +76,23 @@ export default class DevicesSupportedCmd extends Command {
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
|
||||
let deviceTypes: Array<Partial<
|
||||
SDK.DeviceTypeJson.DeviceType
|
||||
>> = await getBalenaSdk()
|
||||
.models.config.getDeviceTypes()
|
||||
.then((dts) =>
|
||||
dts.map((d) => {
|
||||
if (d.aliases && d.aliases.length) {
|
||||
// remove aliases that are equal to the slug
|
||||
d.aliases = d.aliases.filter((alias: string) => alias !== d.slug);
|
||||
if (!options.json) {
|
||||
// stringify the aliases array with commas and spaces
|
||||
d.aliases = [d.aliases.join(', ')];
|
||||
}
|
||||
} else {
|
||||
// ensure it is always an array (for the benefit of JSON output)
|
||||
d.aliases = [];
|
||||
const dts = await getBalenaSdk().models.config.getDeviceTypes();
|
||||
let deviceTypes: Array<Partial<SDK.DeviceTypeJson.DeviceType>> = dts.map(
|
||||
(d) => {
|
||||
if (d.aliases && d.aliases.length) {
|
||||
// remove aliases that are equal to the slug
|
||||
d.aliases = d.aliases.filter((alias: string) => alias !== d.slug);
|
||||
if (!options.json) {
|
||||
// stringify the aliases array with commas and spaces
|
||||
d.aliases = [d.aliases.join(', ')];
|
||||
}
|
||||
return d;
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// ensure it is always an array (for the benefit of JSON output)
|
||||
d.aliases = [];
|
||||
}
|
||||
return d;
|
||||
},
|
||||
);
|
||||
if (!options.discontinued) {
|
||||
deviceTypes = deviceTypes.filter((dt) => dt.state !== 'DISCONTINUED');
|
||||
}
|
||||
|
@ -89,20 +89,17 @@ export default class LocalConfigureCmd extends Command {
|
||||
const dmHandler = (cb: () => void) =>
|
||||
reconfix
|
||||
.readConfiguration(configurationSchema, params.target)
|
||||
.tap((config: any) => {
|
||||
.then(async (config: any) => {
|
||||
logger.logDebug('Current config:');
|
||||
logger.logDebug(JSON.stringify(config));
|
||||
})
|
||||
.then((config: any) => this.getConfiguration(config))
|
||||
.tap((config: any) => {
|
||||
const answers = await this.getConfiguration(config);
|
||||
logger.logDebug('New config:');
|
||||
logger.logDebug(JSON.stringify(config));
|
||||
})
|
||||
.then(async (answers: any) => {
|
||||
logger.logDebug(JSON.stringify(answers));
|
||||
|
||||
if (!answers.hostname) {
|
||||
await this.removeHostname(configurationSchema);
|
||||
}
|
||||
return reconfix.writeConfiguration(
|
||||
return await reconfix.writeConfiguration(
|
||||
configurationSchema,
|
||||
answers,
|
||||
params.target,
|
||||
@ -220,9 +217,8 @@ export default class LocalConfigureCmd extends Command {
|
||||
persistentLogging: data.persistentLogging || false,
|
||||
});
|
||||
|
||||
return inquirer
|
||||
.prompt(this.inquirerOptions(data))
|
||||
.then((answers: any) => _.merge(data, answers));
|
||||
const answers = await inquirer.prompt(this.inquirerOptions(data));
|
||||
return _.merge(data, answers);
|
||||
};
|
||||
|
||||
// Taken from https://goo.gl/kr1kCt
|
||||
@ -259,62 +255,50 @@ export default class LocalConfigureCmd extends Command {
|
||||
const _ = await import('lodash');
|
||||
const imagefs = await import('resin-image-fs');
|
||||
|
||||
return imagefs
|
||||
.listDirectory({
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: this.CONNECTIONS_FOLDER,
|
||||
})
|
||||
.then((files: string[]) => {
|
||||
// The required file already exists
|
||||
if (_.includes(files, 'resin-wifi')) {
|
||||
return null;
|
||||
}
|
||||
const files = await imagefs.listDirectory({
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: this.CONNECTIONS_FOLDER,
|
||||
});
|
||||
|
||||
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
||||
if (_.includes(files, 'resin-sample.ignore')) {
|
||||
return imagefs
|
||||
.copy(
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
||||
},
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
)
|
||||
.thenReturn(null);
|
||||
}
|
||||
|
||||
// Legacy mode, to be removed later
|
||||
// We return the file name override from this branch
|
||||
// When it is removed the following cleanup should be done:
|
||||
// * delete all the null returns from this method
|
||||
// * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
||||
// * drop the final `then` from this method
|
||||
// * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
||||
if (_.includes(files, 'resin-sample')) {
|
||||
return 'resin-sample';
|
||||
}
|
||||
|
||||
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||
return imagefs
|
||||
.writeFile(
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
this.CONNECTION_FILE,
|
||||
)
|
||||
.thenReturn(null);
|
||||
})
|
||||
.then((connectionFileName) =>
|
||||
this.getConfigurationSchema(connectionFileName || undefined),
|
||||
let connectionFileName;
|
||||
if (_.includes(files, 'resin-wifi')) {
|
||||
// The required file already exists, nothing to do
|
||||
} else if (_.includes(files, 'resin-sample.ignore')) {
|
||||
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
||||
await imagefs.copy(
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
||||
},
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
);
|
||||
} else if (_.includes(files, 'resin-sample')) {
|
||||
// Legacy mode, to be removed later
|
||||
// We return the file name override from this branch
|
||||
// When it is removed the following cleanup should be done:
|
||||
// * delete all the null returns from this method
|
||||
// * turn `getConfigurationSchema` back into the constant, with the connection filename always being `resin-wifi`
|
||||
// * drop the final `then` from this method
|
||||
// * adapt the code in the main listener to not receive the config from this method, and use that constant instead
|
||||
connectionFileName = 'resin-sample';
|
||||
} else {
|
||||
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||
await imagefs.writeFile(
|
||||
{
|
||||
image: target,
|
||||
partition: this.BOOT_PARTITION,
|
||||
path: `${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
},
|
||||
this.CONNECTION_FILE,
|
||||
);
|
||||
}
|
||||
return await this.getConfigurationSchema(connectionFileName);
|
||||
}
|
||||
|
||||
async removeHostname(schema: any) {
|
||||
|
@ -302,86 +302,72 @@ Can be repeated to add multiple certificates.\
|
||||
|
||||
allDeviceTypes: DeviceTypeJson.DeviceType[];
|
||||
async getDeviceTypes() {
|
||||
if (this.allDeviceTypes !== undefined) {
|
||||
return this.allDeviceTypes;
|
||||
if (this.allDeviceTypes === undefined) {
|
||||
const balena = getBalenaSdk();
|
||||
const deviceTypes = await balena.models.config.getDeviceTypes();
|
||||
this.allDeviceTypes = _.sortBy(deviceTypes, 'name');
|
||||
}
|
||||
const balena = getBalenaSdk();
|
||||
return balena.models.config
|
||||
.getDeviceTypes()
|
||||
.then((deviceTypes) => _.sortBy(deviceTypes, 'name'))
|
||||
.then((deviceTypes) => {
|
||||
this.allDeviceTypes = deviceTypes;
|
||||
return deviceTypes;
|
||||
});
|
||||
return this.allDeviceTypes;
|
||||
}
|
||||
|
||||
isCurrentCommit(commit: string) {
|
||||
return commit === 'latest' || commit === 'current';
|
||||
}
|
||||
|
||||
getDeviceTypesWithSameArch(deviceTypeSlug: string) {
|
||||
return this.getDeviceTypes().then((deviceTypes) => {
|
||||
const deviceType = _.find(deviceTypes, { slug: deviceTypeSlug });
|
||||
if (!deviceType) {
|
||||
throw new Error(
|
||||
`Device type "${deviceTypeSlug}" not found in API query`,
|
||||
);
|
||||
}
|
||||
return _(deviceTypes)
|
||||
.filter({ arch: deviceType.arch })
|
||||
.map('slug')
|
||||
.value();
|
||||
});
|
||||
async getDeviceTypesWithSameArch(deviceTypeSlug: string) {
|
||||
const deviceTypes = await this.getDeviceTypes();
|
||||
const deviceType = _.find(deviceTypes, { slug: deviceTypeSlug });
|
||||
if (!deviceType) {
|
||||
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
|
||||
}
|
||||
return _(deviceTypes).filter({ arch: deviceType.arch }).map('slug').value();
|
||||
}
|
||||
|
||||
getApplicationsWithSuccessfulBuilds(deviceTypeSlug: string) {
|
||||
async getApplicationsWithSuccessfulBuilds(deviceTypeSlug: string) {
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
return this.getDeviceTypesWithSameArch(deviceTypeSlug).then(
|
||||
(deviceTypes) => {
|
||||
// TODO: remove the explicit types once https://github.com/balena-io/balena-sdk/pull/889 gets merged
|
||||
return balena.pine.get<
|
||||
Application,
|
||||
Array<
|
||||
ApplicationWithDeviceType & {
|
||||
should_be_running__release: [Release?];
|
||||
}
|
||||
>
|
||||
>({
|
||||
resource: 'my_application',
|
||||
options: {
|
||||
$filter: {
|
||||
is_for__device_type: {
|
||||
$any: {
|
||||
$alias: 'dt',
|
||||
$expr: {
|
||||
dt: {
|
||||
slug: { $in: deviceTypes },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
owns__release: {
|
||||
$any: {
|
||||
$alias: 'r',
|
||||
$expr: {
|
||||
r: {
|
||||
status: 'success',
|
||||
},
|
||||
},
|
||||
const deviceTypes = await this.getDeviceTypesWithSameArch(deviceTypeSlug);
|
||||
// TODO: remove the explicit types once https://github.com/balena-io/balena-sdk/pull/889 gets merged
|
||||
return balena.pine.get<
|
||||
Application,
|
||||
Array<
|
||||
ApplicationWithDeviceType & {
|
||||
should_be_running__release: [Release?];
|
||||
}
|
||||
>
|
||||
>({
|
||||
resource: 'my_application',
|
||||
options: {
|
||||
$filter: {
|
||||
is_for__device_type: {
|
||||
$any: {
|
||||
$alias: 'dt',
|
||||
$expr: {
|
||||
dt: {
|
||||
slug: { $in: deviceTypes },
|
||||
},
|
||||
},
|
||||
},
|
||||
$expand: this.applicationExpandOptions,
|
||||
$select: ['id', 'app_name', 'should_track_latest_release'],
|
||||
$orderby: 'app_name asc',
|
||||
},
|
||||
});
|
||||
owns__release: {
|
||||
$any: {
|
||||
$alias: 'r',
|
||||
$expr: {
|
||||
r: {
|
||||
status: 'success',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
$expand: this.applicationExpandOptions,
|
||||
$select: ['id', 'app_name', 'should_track_latest_release'],
|
||||
$orderby: 'app_name asc',
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
selectApplication(deviceTypeSlug: string) {
|
||||
async selectApplication(deviceTypeSlug: string) {
|
||||
const visuals = getVisuals();
|
||||
|
||||
const applicationInfoSpinner = new visuals.Spinner(
|
||||
@ -389,24 +375,23 @@ Can be repeated to add multiple certificates.\
|
||||
);
|
||||
applicationInfoSpinner.start();
|
||||
|
||||
return this.getApplicationsWithSuccessfulBuilds(deviceTypeSlug).then(
|
||||
(applications) => {
|
||||
applicationInfoSpinner.stop();
|
||||
if (applications.length === 0) {
|
||||
throw new ExpectedError(
|
||||
`You have no apps with successful releases for a '${deviceTypeSlug}' device type.`,
|
||||
);
|
||||
}
|
||||
return getCliForm().ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: applications.map((app) => ({
|
||||
name: app.app_name,
|
||||
value: app,
|
||||
})),
|
||||
});
|
||||
},
|
||||
const applications = await this.getApplicationsWithSuccessfulBuilds(
|
||||
deviceTypeSlug,
|
||||
);
|
||||
applicationInfoSpinner.stop();
|
||||
if (applications.length === 0) {
|
||||
throw new ExpectedError(
|
||||
`You have no apps with successful releases for a '${deviceTypeSlug}' device type.`,
|
||||
);
|
||||
}
|
||||
return getCliForm().ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: applications.map((app) => ({
|
||||
name: app.app_name,
|
||||
value: app,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
selectApplicationCommit(releases: Release[]) {
|
||||
@ -462,23 +447,20 @@ preloaded device to the selected release.
|
||||
|
||||
Would you like to disable automatic updates for this application now?\
|
||||
`;
|
||||
return getCliForm()
|
||||
.ask({
|
||||
message,
|
||||
type: 'confirm',
|
||||
})
|
||||
.then(function (update) {
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
return balena.pine.patch({
|
||||
resource: 'application',
|
||||
id: application.id,
|
||||
body: {
|
||||
should_track_latest_release: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
const update = await getCliForm().ask({
|
||||
message,
|
||||
type: 'confirm',
|
||||
});
|
||||
if (!update) {
|
||||
return;
|
||||
}
|
||||
return await balena.pine.patch({
|
||||
resource: 'application',
|
||||
id: application.id,
|
||||
body: {
|
||||
should_track_latest_release: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getAppWithReleases(balenaSdk: BalenaSDK, appId: string | number) {
|
||||
|
Loading…
Reference in New Issue
Block a user