diff --git a/lib/commands/device/move.ts b/lib/commands/device/move.ts index 46b9aa22..5c533f2a 100644 --- a/lib/commands/device/move.ts +++ b/lib/commands/device/move.ts @@ -30,13 +30,6 @@ import { ExpectedError } from '../../errors'; import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { applicationIdInfo } from '../../utils/messages'; -type ExtendedDevice = PineTypedResult< - Device, - typeof import('../../utils/helpers').expandForAppNameAndCpuArch -> & { - application_name?: string; -}; - interface FlagsDef { fleet?: string; help: void; @@ -82,6 +75,33 @@ export default class DeviceMoveCmd extends Command { public static authenticated = true; + private async getDevices(balena: BalenaSDK, deviceUuids: string[]) { + const deviceOptions = { + $select: 'belongs_to__application', + $expand: { + is_of__device_type: { + $select: 'is_of__cpu_architecture', + $expand: { + is_of__cpu_architecture: { + $select: 'slug', + }, + }, + }, + }, + } satisfies PineOptions; + + // TODO: Refacor once `device.get()` accepts an array of uuids` + const devices = await Promise.all( + deviceUuids.map( + (uuid) => + balena.models.device.get(uuid, deviceOptions) as Promise< + PineTypedResult + >, + ), + ); + return devices; + } + public async run() { const { args: params, flags: options } = this.parse( DeviceMoveCmd, @@ -89,36 +109,17 @@ export default class DeviceMoveCmd extends Command { const balena = getBalenaSdk(); - const { expandForAppNameAndCpuArch } = await import('../../utils/helpers'); - // Split uuids string into array of uuids const deviceUuids = params.uuid.split(','); - // Get devices - const devices = await Promise.all( - deviceUuids.map( - (uuid) => - balena.models.device.get( - uuid, - expandForAppNameAndCpuArch, - ) as Promise, - ), - ); - - // Map application name for each device - for (const device of devices) { - const belongsToApplication = device.belongs_to__application; - device.application_name = belongsToApplication?.[0] - ? belongsToApplication[0].app_name - : 'N/a'; - } + const devices = await this.getDevices(balena, deviceUuids); // Disambiguate application const { getApplication } = await import('../../utils/sdk'); // Get destination application const application = options.fleet - ? await getApplication(balena, options.fleet) + ? await getApplication(balena, options.fleet, { $select: ['id', 'slug'] }) : await this.interactivelySelectApplication(balena, devices); // Move each device @@ -135,7 +136,7 @@ export default class DeviceMoveCmd extends Command { async interactivelySelectApplication( balena: BalenaSDK, - devices: ExtendedDevice[], + devices: Awaited>, ) { const { getExpandedProp } = await import('../../utils/pine'); // deduplicate the slugs @@ -181,7 +182,9 @@ export default class DeviceMoveCmd extends Command { const application = await patterns.selectApplication( (app) => compatibleDeviceTypeSlugs.has(app.is_for__device_type[0].slug) && - devices.some((device) => device.application_name !== app.app_name), + devices.some( + (device) => device.belongs_to__application.__id !== app.id, + ), true, ); return application; diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 0d480806..5c3f0361 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -437,20 +437,6 @@ export const expandForAppName = { }, } satisfies BalenaSdk.PineOptions; -export const expandForAppNameAndCpuArch = { - $expand: { - ...expandForAppName.$expand, - is_of__device_type: { - $select: 'slug', - $expand: { - is_of__cpu_architecture: { - $select: 'slug', - }, - }, - }, - }, -} satisfies BalenaSdk.PineOptions; - /** * Use the `readline` library on Windows to install SIGINT handlers. * This appears to be necessary on MSYS / Git for Windows, and also useful diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index cb7fc35f..409d8276 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -14,7 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type { BalenaSDK, Device, Organization } from 'balena-sdk'; +import type { + Application, + BalenaSDK, + Device, + Organization, + PineOptions, + PineTypedResult, +} from 'balena-sdk'; import _ = require('lodash'); import { instanceOf, NotLoggedInError, ExpectedError } from '../errors'; @@ -157,18 +164,28 @@ export async function confirm( } } +const selectApplicationPineOptions = { + $select: ['id', 'slug', 'app_name'], + $expand: { + is_for__device_type: { + $select: 'slug', + }, + }, +} satisfies PineOptions; + +type SelectApplicationResult = PineTypedResult< + Application, + typeof selectApplicationPineOptions +>; + export async function selectApplication( - filter?: (app: ApplicationWithDeviceType) => boolean, + filter?: (app: SelectApplicationResult) => boolean, errorOnEmptySelection = false, ) { const balena = getBalenaSdk(); - const apps = (await balena.models.application.getAllDirectlyAccessible({ - $expand: { - is_for__device_type: { - $select: 'slug', - }, - }, - })) as ApplicationWithDeviceType[]; + const apps = (await balena.models.application.getAllDirectlyAccessible( + selectApplicationPineOptions, + )) as SelectApplicationResult[]; if (!apps.length) { throw new ExpectedError('No fleets found');