Update balena-sdk to 14.x

Update balena-sdk from 13.6.0 to 14.8.0

Change-type: patch
This commit is contained in:
Pagan Gazzard
2020-07-31 15:35:20 +01:00
committed by Balena CI
parent bf22d9eaa8
commit e1c42405a1
36 changed files with 390 additions and 434 deletions

View File

@ -86,6 +86,7 @@ export default class AppCreateCmd extends Command {
application = await balena.models.application.create({ application = await balena.models.application.create({
name: params.name, name: params.name,
deviceType, deviceType,
organization: (await balena.auth.whoami())!,
}); });
} catch (err) { } catch (err) {
// BalenaRequestError: Request error: Unique key constraint violated // BalenaRequestError: Request error: Unique key constraint violated
@ -97,7 +98,7 @@ export default class AppCreateCmd extends Command {
throw err; throw err;
} }
console.info( console.info(
`Application created: ${application.slug} (${application.device_type}, id ${application.id})`, `Application created: ${application.slug} (${deviceType}, id ${application.id})`,
); );
} }
} }

View File

@ -20,6 +20,7 @@ import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation'; import { tryAsInteger } from '../../utils/validation';
import { Release } from 'balena-sdk';
interface FlagsDef { interface FlagsDef {
help: void; help: void;
@ -57,10 +58,22 @@ export default class AppCmd extends Command {
public async run() { public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd); const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd);
const application = await getBalenaSdk().models.application.get( const application = (await getBalenaSdk().models.application.get(
tryAsInteger(params.name), tryAsInteger(params.name),
); {
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
},
)) as ApplicationWithDeviceType & {
should_be_running__release: [Release?];
};
// @ts-expect-error
application.device_type = application.is_for__device_type[0].slug;
// @ts-expect-error
application.commit = application.should_be_running__release[0]?.commit;
console.log( console.log(
getVisuals().table.vertical(application, [ getVisuals().table.vertical(application, [
`$${application.app_name}$`, `$${application.app_name}$`,

View File

@ -16,13 +16,12 @@
*/ */
import { flags } from '@oclif/command'; import { flags } from '@oclif/command';
import type { Application } from 'balena-sdk';
import Command from '../command'; import Command from '../command';
import * as cf from '../utils/common-flags'; import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import { isV12 } from '../utils/version'; import { isV12 } from '../utils/version';
interface ExtendedApplication extends Application { interface ExtendedApplication extends ApplicationWithDeviceType {
device_count?: number; device_count?: number;
online_devices?: number; online_devices?: number;
} }
@ -64,12 +63,13 @@ export default class AppsCmd extends Command {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
// Get applications // Get applications
const applications: ExtendedApplication[] = await balena.models.application.getAll( const applications = (await balena.models.application.getAll({
{ $select: ['id', 'app_name', 'slug'],
$select: ['id', 'app_name', 'slug', 'device_type'], $expand: {
$expand: { owns__device: { $select: 'is_online' } }, is_for__device_type: { $select: 'slug' },
owns__device: { $select: 'is_online' },
}, },
); })) as ExtendedApplication[];
const _ = await import('lodash'); const _ = await import('lodash');
// Add extended properties // Add extended properties
@ -78,6 +78,8 @@ export default class AppsCmd extends Command {
application.online_devices = _.sumBy(application.owns__device, (d) => application.online_devices = _.sumBy(application.owns__device, (d) =>
d.is_online === true ? 1 : 0, d.is_online === true ? 1 : 0,
); );
// @ts-expect-error
application.device_type = application.is_for__device_type[0].slug;
}); });
// Display // Display

View File

@ -189,7 +189,7 @@ ${dockerignoreHelp}
const { getAppWithArch } = await import('../utils/helpers'); const { getAppWithArch } = await import('../utils/helpers');
const app = await getAppWithArch(opts.application); const app = await getAppWithArch(opts.application);
opts.arch = app.arch; opts.arch = app.arch;
opts.deviceType = app.device_type; opts.deviceType = app.is_for__device_type[0].slug;
return app; return app;
} }
} }

View File

@ -19,7 +19,7 @@ import { flags } from '@oclif/command';
import Command from '../../command'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import type { Device, Application, PineDeferred } from 'balena-sdk'; import type { Application, PineDeferred } from 'balena-sdk';
interface FlagsDef { interface FlagsDef {
version: string; // OS version version: string; // OS version
@ -130,27 +130,34 @@ export default class ConfigGenerateCmd extends Command {
await this.validateOptions(options); await this.validateOptions(options);
let deviceType = options.deviceType;
// Get device | application // Get device | application
let resource; let resource;
if (options.device != null) { if (options.device != null) {
const { tryAsInteger } = await import('../../utils/validation'); const { tryAsInteger } = await import('../../utils/validation');
resource = (await balena.models.device.get( resource = (await balena.models.device.get(tryAsInteger(options.device), {
tryAsInteger(options.device), $expand: {
)) as Device & { belongs_to__application: PineDeferred }; is_of__device_type: { $select: 'slug' },
},
})) as DeviceWithDeviceType & { belongs_to__application: PineDeferred };
deviceType = deviceType || resource.is_of__device_type[0].slug;
} else { } else {
resource = await balena.models.application.get(options.application!); resource = (await balena.models.application.get(options.application!, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
deviceType = deviceType || resource.is_for__device_type[0].slug;
} }
const deviceType = options.deviceType || resource.device_type;
const deviceManifest = await balena.models.device.getManifestBySlug( const deviceManifest = await balena.models.device.getManifestBySlug(
deviceType, deviceType!,
); );
// Check compatibility if application and deviceType provided // Check compatibility if application and deviceType provided
if (options.application && options.deviceType) { if (options.application && options.deviceType) {
const appDeviceManifest = await balena.models.device.getManifestBySlug( const appDeviceManifest = await balena.models.device.getManifestBySlug(
resource.device_type, deviceType!,
); );
const helpers = await import('../../utils/helpers'); const helpers = await import('../../utils/helpers');

View File

@ -22,11 +22,12 @@ import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers'; import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation'; import { tryAsInteger } from '../../utils/validation';
import type { Application, Device } from 'balena-sdk'; import type { Application, Release } from 'balena-sdk';
interface ExtendedDevice extends Device { interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string; dashboard_url?: string;
application_name?: string; application_name?: string;
device_type?: string;
commit?: string; commit?: string;
last_seen?: string; last_seen?: string;
} }
@ -70,25 +71,23 @@ export default class DeviceCmd extends Command {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
const device: ExtendedDevice = await balena.models.device.get(params.uuid, { const device = (await balena.models.device.get(params.uuid, {
$select: [ $select: [
'device_name', 'device_name',
'id', 'id',
'device_type',
'overall_status', 'overall_status',
'is_online', 'is_online',
'ip_address', 'ip_address',
'mac_address', 'mac_address',
'last_connectivity_event', 'last_connectivity_event',
'uuid', 'uuid',
'is_on__commit',
'supervisor_version', 'supervisor_version',
'is_web_accessible', 'is_web_accessible',
'note', 'note',
'os_version', 'os_version',
], ],
...expandForAppName, ...expandForAppName,
}); })) as ExtendedDevice;
device.status = device.overall_status; device.status = device.overall_status;
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid); device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
@ -98,8 +97,9 @@ export default class DeviceCmd extends Command {
? belongsToApplication[0].app_name ? belongsToApplication[0].app_name
: 'N/a'; : 'N/a';
device.commit = device.is_on__commit; device.device_type = device.is_of__device_type[0].slug;
device.last_seen = device.last_connectivity_event; device.commit = (device.is_running__release as Release[])[0].commit;
device.last_seen = device.last_connectivity_event ?? undefined;
console.log( console.log(
getVisuals().table.vertical(device, [ getVisuals().table.vertical(device, [

View File

@ -95,10 +95,17 @@ export default class DeviceInitCmd extends Command {
delete options.app; delete options.app;
// Get application and // Get application and
const application = await balena.models.application.get( const application = (await balena.models.application.get(
options['application'] || options['application'] ||
(await (await import('../../utils/patterns')).selectApplication()), (await (await import('../../utils/patterns')).selectApplication()),
); {
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
},
)) as ApplicationWithDeviceType;
// Register new device // Register new device
const deviceUuid = balena.models.device.generateUniqueKey(); const deviceUuid = balena.models.device.generateUniqueKey();
@ -111,13 +118,14 @@ export default class DeviceInitCmd extends Command {
try { try {
logger.logDebug(`Downloading OS image...`); logger.logDebug(`Downloading OS image...`);
const osVersion = options['os-version'] || 'default'; const osVersion = options['os-version'] || 'default';
await downloadOSImage(application.device_type, tmpPath, osVersion); const deviceType = application.is_for__device_type[0].slug;
await downloadOSImage(deviceType, tmpPath, osVersion);
logger.logDebug(`Configuring OS image...`); logger.logDebug(`Configuring OS image...`);
await this.configureOsImage(tmpPath, device.uuid, options); await this.configureOsImage(tmpPath, device.uuid, options);
logger.logDebug(`Writing OS image...`); logger.logDebug(`Writing OS image...`);
await this.writeOsImage(tmpPath, application.device_type, options); await this.writeOsImage(tmpPath, deviceType, options);
} catch (e) { } catch (e) {
// Remove device in failed cases // Remove device in failed cases
try { try {

View File

@ -17,7 +17,7 @@
import { flags } from '@oclif/command'; import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args'; import type { IArg } from '@oclif/parser/lib/args';
import type { Application, Device } from 'balena-sdk'; import type { Application } from 'balena-sdk';
import Command from '../../command'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers'; import { expandForAppName } from '../../utils/helpers';
@ -25,7 +25,7 @@ import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation'; import { tryAsInteger } from '../../utils/validation';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
interface ExtendedDevice extends Device { interface ExtendedDevice extends DeviceWithDeviceType {
application_name?: string; application_name?: string;
} }
@ -85,10 +85,15 @@ export default class DeviceMoveCmd extends Command {
options.application = options.application || options.app; options.application = options.application || options.app;
delete options.app; delete options.app;
const devices: ExtendedDevice[] = await Promise.all( const devices = await Promise.all(
params.uuid params.uuid
.split(',') .split(',')
.map((uuid) => balena.models.device.get(uuid, expandForAppName)), .map(
(uuid) =>
balena.models.device.get(uuid, expandForAppName) as Promise<
ExtendedDevice
>,
),
); );
for (const device of devices) { for (const device of devices) {
@ -106,7 +111,9 @@ export default class DeviceMoveCmd extends Command {
const [deviceDeviceTypes, deviceTypes] = await Promise.all([ const [deviceDeviceTypes, deviceTypes] = await Promise.all([
Promise.all( Promise.all(
devices.map((device) => devices.map((device) =>
balena.models.device.getManifestBySlug(device.device_type), balena.models.device.getManifestBySlug(
device.is_of__device_type[0].slug,
),
), ),
), ),
balena.models.config.getDeviceTypes(), balena.models.config.getDeviceTypes(),
@ -127,8 +134,10 @@ export default class DeviceMoveCmd extends Command {
const patterns = await import('../../utils/patterns'); const patterns = await import('../../utils/patterns');
try { try {
application = await patterns.selectApplication( application = await patterns.selectApplication(
(app: Application) => (app) =>
compatibleDeviceTypes.some((dt) => dt.slug === app.device_type) && compatibleDeviceTypes.some(
(dt) => dt.slug === app.is_for__device_type[0].slug,
) &&
// @ts-ignore using the extended device object prop // @ts-ignore using the extended device object prop
devices.some((device) => device.application_name !== app.app_name), devices.some((device) => device.application_name !== app.app_name),
true, true,

View File

@ -81,12 +81,17 @@ export default class DeviceOsUpdateCmd extends Command {
// Get device info // Get device info
const { const {
uuid, uuid,
device_type, is_of__device_type,
os_version, os_version,
os_variant, os_variant,
} = await sdk.models.device.get(params.uuid, { } = (await sdk.models.device.get(params.uuid, {
$select: ['uuid', 'device_type', 'os_version', 'os_variant'], $select: ['uuid', 'os_version', 'os_variant'],
}); $expand: {
is_of__device_type: {
$select: 'slug',
},
},
})) as DeviceWithDeviceType;
// Get current device OS version // Get current device OS version
const currentOsVersion = sdk.models.device.getOsVersion({ const currentOsVersion = sdk.models.device.getOsVersion({
@ -101,7 +106,7 @@ export default class DeviceOsUpdateCmd extends Command {
// Get supported OS update versions // Get supported OS update versions
const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions( const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions(
device_type, is_of__device_type[0].slug,
currentOsVersion, currentOsVersion,
); );
if (hupVersionInfo.versions.length === 0) { if (hupVersionInfo.versions.length === 0) {

View File

@ -21,9 +21,9 @@ import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers'; import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation'; import { tryAsInteger } from '../../utils/validation';
import type { Device, Application } from 'balena-sdk'; import type { Application } from 'balena-sdk';
interface ExtendedDevice extends Device { interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string; dashboard_url?: string;
application_name?: string; application_name?: string;
} }
@ -80,15 +80,17 @@ export default class DevicesCmd extends Command {
options.application = options.application || options.app; options.application = options.application || options.app;
delete options.app; delete options.app;
let devices: ExtendedDevice[]; let devices;
if (options.application != null) { if (options.application != null) {
devices = await balena.models.device.getAllByApplication( devices = (await balena.models.device.getAllByApplication(
tryAsInteger(options.application), tryAsInteger(options.application),
expandForAppName, expandForAppName,
); )) as ExtendedDevice[];
} else { } else {
devices = await balena.models.device.getAll(expandForAppName); devices = (await balena.models.device.getAll(
expandForAppName,
)) as ExtendedDevice[];
} }
devices = devices.map(function (device) { devices = devices.map(function (device) {
@ -100,6 +102,9 @@ export default class DevicesCmd extends Command {
: 'N/a'; : 'N/a';
device.uuid = device.uuid.slice(0, 7); device.uuid = device.uuid.slice(0, 7);
// @ts-ignore
device.device_type = device.is_of__device_type[0].slug;
return device; return device;
}); });

View File

@ -76,7 +76,9 @@ export default class DevicesSupportedCmd extends Command {
public async run() { public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd); const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
let deviceTypes: Array<Partial<SDK.DeviceType>> = await getBalenaSdk() let deviceTypes: Array<Partial<
SDK.DeviceTypeJson.DeviceType
>> = await getBalenaSdk()
.models.config.getDeviceTypes() .models.config.getDeviceTypes()
.map((d) => { .map((d) => {
if (d.aliases && d.aliases.length) { if (d.aliases && d.aliases.length) {
@ -100,7 +102,7 @@ export default class DevicesSupportedCmd extends Command {
: ['slug', 'aliases', 'arch', 'name']; : ['slug', 'aliases', 'arch', 'name'];
deviceTypes = _.sortBy( deviceTypes = _.sortBy(
deviceTypes.map((d) => { deviceTypes.map((d) => {
const picked = _.pick<Partial<SDK.DeviceType>>(d, fields); const picked = _.pick(d, fields);
// 'BETA' renamed to 'NEW' // 'BETA' renamed to 'NEW'
picked.state = picked.state === 'BETA' ? 'NEW' : picked.state; picked.state = picked.state === 'BETA' ? 'NEW' : picked.state;
return picked; return picked;

View File

@ -317,7 +317,7 @@ async function getAppVars(
appVars.push(...vars); appVars.push(...vars);
} }
if (!options.config && (options.service || options.all)) { if (!options.config && (options.service || options.all)) {
const pineOpts: SDK.PineOptionsFor<SDK.ServiceEnvironmentVariable> = { const pineOpts: SDK.PineOptions<SDK.ServiceEnvironmentVariable> = {
$expand: { $expand: {
service: {}, service: {},
}, },
@ -359,7 +359,7 @@ async function getDeviceVars(
deviceVars.push(...deviceConfigVars); deviceVars.push(...deviceConfigVars);
} else { } else {
if (options.service || options.all) { if (options.service || options.all) {
const pineOpts: SDK.PineOptionsFor<SDK.DeviceServiceEnvironmentVariable> = { const pineOpts: SDK.PineOptions<SDK.DeviceServiceEnvironmentVariable> = {
$expand: { $expand: {
service_install: { service_install: {
$expand: 'installs__service', $expand: 'installs__service',

View File

@ -20,7 +20,7 @@ import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getCliForm, stripIndent } from '../../utils/lazy'; import { getCliForm, stripIndent } from '../../utils/lazy';
import * as _ from 'lodash'; import * as _ from 'lodash';
import type { DeviceType } from 'balena-sdk'; import type { DeviceTypeJson } from 'balena-sdk';
interface FlagsDef { interface FlagsDef {
advanced: boolean; advanced: boolean;
@ -104,7 +104,7 @@ export default class OsBuildConfigCmd extends Command {
} }
async buildConfigForDeviceType( async buildConfigForDeviceType(
deviceTypeManifest: DeviceType, deviceTypeManifest: DeviceTypeJson.DeviceType,
advanced: boolean, advanced: boolean,
) { ) {
if (advanced == null) { if (advanced == null) {

View File

@ -51,10 +51,6 @@ interface ArgsDef {
image: string; image: string;
} }
interface DeferredDevice extends BalenaSdk.Device {
belongs_to__application: BalenaSdk.PineDeferred;
}
interface Answers { interface Answers {
appUpdatePollInterval: number; // in minutes appUpdatePollInterval: number; // in minutes
deviceType: string; // e.g. "raspberrypi3" deviceType: string; // e.g. "raspberrypi3"
@ -192,18 +188,29 @@ export default class OsConfigureCmd extends Command {
'../../utils/config' '../../utils/config'
); );
const helpers = await import('../../utils/helpers'); const helpers = await import('../../utils/helpers');
let app: BalenaSdk.Application | undefined; let app: ApplicationWithDeviceType | undefined;
let device: BalenaSdk.Device | undefined; let device;
let deviceTypeSlug: string; let deviceTypeSlug: string;
const balena = getBalenaSdk(); const balena = getBalenaSdk();
if (options.device) { if (options.device) {
device = await balena.models['device'].get(options.device); device = (await balena.models.device.get(options.device, {
deviceTypeSlug = device.device_type; $expand: {
is_of__device_type: { $select: 'slug' },
},
})) as DeviceWithDeviceType & {
belongs_to__application: BalenaSdk.PineDeferred;
};
deviceTypeSlug = device.is_of__device_type[0].slug;
} else { } else {
app = await balena.models['application'].get(options.application!); app = (await balena.models.application.get(options.application!, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
await checkDeviceTypeCompatibility(balena, options, app); await checkDeviceTypeCompatibility(balena, options, app);
deviceTypeSlug = options['device-type'] || app.device_type; deviceTypeSlug =
options['device-type'] || app.is_for__device_type[0].slug;
} }
const deviceTypeManifest = await helpers.getManifest( const deviceTypeManifest = await helpers.getManifest(
@ -232,7 +239,7 @@ export default class OsConfigureCmd extends Command {
if (_.isEmpty(configJson)) { if (_.isEmpty(configJson)) {
if (device) { if (device) {
configJson = await generateDeviceConfig( configJson = await generateDeviceConfig(
device as DeferredDevice, device,
options['device-api-key'], options['device-api-key'],
answers, answers,
); );
@ -335,7 +342,7 @@ async function validateOptions(options: FlagsDef) {
*/ */
async function getOsVersionFromImage( async function getOsVersionFromImage(
imagePath: string, imagePath: string,
deviceTypeManifest: BalenaSdk.DeviceType, deviceTypeManifest: BalenaSdk.DeviceTypeJson.DeviceType,
devInit: typeof import('balena-device-init'), devInit: typeof import('balena-device-init'),
): Promise<string> { ): Promise<string> {
const osVersion = await devInit.getImageOsVersion( const osVersion = await devInit.getImageOsVersion(
@ -361,11 +368,11 @@ async function getOsVersionFromImage(
async function checkDeviceTypeCompatibility( async function checkDeviceTypeCompatibility(
sdk: BalenaSdk.BalenaSDK, sdk: BalenaSdk.BalenaSDK,
options: FlagsDef, options: FlagsDef,
app: BalenaSdk.Application, app: ApplicationWithDeviceType,
) { ) {
if (options['device-type']) { if (options['device-type']) {
const [appDeviceType, optionDeviceType] = await Promise.all([ const [appDeviceType, optionDeviceType] = await Promise.all([
sdk.models.device.getManifestBySlug(app.device_type), sdk.models.device.getManifestBySlug(app.is_for__device_type[0].slug),
sdk.models.device.getManifestBySlug(options['device-type']), sdk.models.device.getManifestBySlug(options['device-type']),
]); ]);
const helpers = await import('../../utils/helpers'); const helpers = await import('../../utils/helpers');
@ -392,7 +399,7 @@ async function checkDeviceTypeCompatibility(
* The questions are extracted from the given deviceType "manifest". * The questions are extracted from the given deviceType "manifest".
*/ */
async function askQuestionsForDeviceType( async function askQuestionsForDeviceType(
deviceType: BalenaSdk.DeviceType, deviceType: BalenaSdk.DeviceTypeJson.DeviceType,
options: FlagsDef, options: FlagsDef,
configJson?: import('../../utils/config').ImgConfig, configJson?: import('../../utils/config').ImgConfig,
): Promise<Answers> { ): Promise<Answers> {
@ -460,14 +467,17 @@ async function askQuestionsForDeviceType(
* [ 'network', 'wifiSsid', 'wifiKey', 'appUpdatePollInterval' ] * [ 'network', 'wifiSsid', 'wifiKey', 'appUpdatePollInterval' ]
*/ */
function getQuestionNames( function getQuestionNames(
deviceType: BalenaSdk.DeviceType, deviceType: BalenaSdk.DeviceTypeJson.DeviceType,
): Array<keyof Answers> { ): Array<keyof Answers> {
const questionNames: string[] = _.chain(deviceType.options) const questionNames: string[] = _.chain(deviceType.options)
.flatMap( .flatMap(
(group: BalenaSdk.DeviceTypeOptions) => (group: BalenaSdk.DeviceTypeJson.DeviceTypeOptions) =>
(group.isGroup && group.options) || [], (group.isGroup && group.options) || [],
) )
.map((groupOption: BalenaSdk.DeviceTypeOptionsGroup) => groupOption.name) .map(
(groupOption: BalenaSdk.DeviceTypeJson.DeviceTypeOptionsGroup) =>
groupOption.name,
)
.filter() .filter()
.value(); .value();
return questionNames as Array<keyof Answers>; return questionNames as Array<keyof Answers>;

View File

@ -20,7 +20,7 @@ import Command from '../command';
import * as cf from '../utils/common-flags'; import * as cf from '../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../utils/lazy'; import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages'; import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
import type { BalenaSDK } from 'balena-sdk'; import type { BalenaSDK, Application, Organization } from 'balena-sdk';
import { ExpectedError, instanceOf } from '../errors'; import { ExpectedError, instanceOf } from '../errors';
enum BuildTarget { enum BuildTarget {
@ -362,17 +362,21 @@ export default class PushCmd extends Command {
async getAppOwner(sdk: BalenaSDK, appName: string) { async getAppOwner(sdk: BalenaSDK, appName: string) {
const _ = await import('lodash'); const _ = await import('lodash');
const applications = await sdk.models.application.getAll({ const applications = (await sdk.models.application.getAll({
$expand: { $expand: {
user: { organization: {
$select: ['username'], $select: ['handle'],
}, },
}, },
$filter: { $filter: {
$eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()], $eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()],
}, },
$select: ['id'], $select: ['id'],
}); })) as Array<
Application & {
organization: [Organization];
}
>;
if (applications == null || applications.length === 0) { if (applications == null || applications.length === 0) {
throw new ExpectedError( throw new ExpectedError(
@ -385,7 +389,7 @@ export default class PushCmd extends Command {
} }
if (applications.length === 1) { if (applications.length === 1) {
return _.get(applications, '[0].user[0].username'); return applications[0].organization[0].handle;
} }
// If we got more than one application with the same name it means that the // If we got more than one application with the same name it means that the
@ -393,7 +397,7 @@ export default class PushCmd extends Command {
// present a list to the user which shows the fully qualified application // present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select // name (user/appname) and allows them to select
const entries = _.map(applications, (app) => { const entries = _.map(applications, (app) => {
const username = _.get(app, 'user[0].username'); const username = app.organization[0].handle;
return { return {
name: `${username}/${appName}`, name: `${username}/${appName}`,
extra: username, extra: username,

View File

@ -94,7 +94,7 @@ const deployProject = function (docker, logger, composeOpts, opts) {
project.name, project.name,
compositionToBuild, compositionToBuild,
opts.app.arch, opts.app.arch,
opts.app.device_type, opts.app.is_for__device_type[0].slug,
opts.buildEmulated, opts.buildEmulated,
opts.buildOpts, opts.buildOpts,
composeOpts.inlineLogs, composeOpts.inlineLogs,

View File

@ -39,6 +39,9 @@ const applicationExpandOptions = {
status: 'success', status: 'success',
}, },
}, },
should_be_running__release: {
$select: 'commit',
},
}; };
let allDeviceTypes; let allDeviceTypes;
@ -69,11 +72,18 @@ const getApplicationsWithSuccessfulBuilds = function (deviceType) {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
return getDeviceTypesWithSameArch(deviceType).then((deviceTypes) => { return getDeviceTypesWithSameArch(deviceType).then((deviceTypes) => {
/** @type {import('balena-sdk').PineOptionsFor<import('balena-sdk').Application>} */ /** @type {import('balena-sdk').PineOptions<ApplicationWithDeviceType & { should_be_running__release: [import('balena-sdk').Release?] }>} */
const options = { const options = {
$filter: { $filter: {
device_type: { is_for__device_type: {
$in: deviceTypes, $any: {
$alias: 'dt',
$expr: {
dt: {
slug: { $in: deviceTypes },
},
},
},
}, },
owns__release: { owns__release: {
$any: { $any: {
@ -87,13 +97,7 @@ const getApplicationsWithSuccessfulBuilds = function (deviceType) {
}, },
}, },
$expand: applicationExpandOptions, $expand: applicationExpandOptions,
$select: [ $select: ['id', 'app_name', 'should_track_latest_release'],
'id',
'app_name',
'device_type',
'commit',
'should_track_latest_release',
],
$orderby: 'app_name asc', $orderby: 'app_name asc',
}; };
return balena.pine.get({ return balena.pine.get({
@ -209,9 +213,10 @@ Would you like to disable automatic updates for this application now?\
/** /**
* @param {import('balena-sdk').BalenaSDK} balenaSdk * @param {import('balena-sdk').BalenaSDK} balenaSdk
* @param {string | number} appId * @param {string | number} appId
* @returns {Promise<import('balena-sdk').Application>} * @returns {Promise<import('balena-sdk').Application & { should_be_running__release: [import('balena-sdk').Release?] }>}
*/ */
async function getAppWithReleases(balenaSdk, appId) { async function getAppWithReleases(balenaSdk, appId) {
// @ts-ignore
return balenaSdk.models.application.get(appId, { return balenaSdk.models.application.get(appId, {
$expand: applicationExpandOptions, $expand: applicationExpandOptions,
}); });
@ -229,10 +234,12 @@ async function prepareAndPreload(preloader, balenaSdk, options) {
/** @type {string} commit hash or the strings 'latest' or 'current' */ /** @type {string} commit hash or the strings 'latest' or 'current' */
let commit; let commit;
const appCommit = application.should_be_running__release[0]?.commit;
// Use the commit given as --commit or show an interactive commit selection menu // Use the commit given as --commit or show an interactive commit selection menu
if (options.commit) { if (options.commit) {
if (isCurrent(options.commit)) { if (isCurrent(options.commit)) {
if (!application.commit) { if (!appCommit) {
throw new Error( throw new Error(
`Unexpected empty commit hash for app ID "${application.id}"`, `Unexpected empty commit hash for app ID "${application.id}"`,
); );
@ -257,7 +264,7 @@ async function prepareAndPreload(preloader, balenaSdk, options) {
await preloader.setAppIdAndCommit( await preloader.setAppIdAndCommit(
application.id, application.id,
isCurrent(commit) ? application.commit : commit, isCurrent(commit) ? appCommit : commit,
); );
// Propose to disable automatic app updates if the commit is not the current release // Propose to disable automatic app updates if the commit is not the current release

View File

@ -92,7 +92,7 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize(
$expand: selectAppFields $expand: selectAppFields
? { belongs_to__application: { $select: selectAppFields } } ? { belongs_to__application: { $select: selectAppFields } }
: 'belongs_to__application', : 'belongs_to__application',
} as SDK.PineOptionsFor<SDK.Device>; } as SDK.PineOptions<SDK.Device>;
if (selectDeviceFields) { if (selectDeviceFields) {
pineOpts.$select = selectDeviceFields as any; pineOpts.$select = selectDeviceFields as any;
} }

View File

@ -102,7 +102,7 @@ export async function generateApplicationConfig(
} }
export function generateDeviceConfig( export function generateDeviceConfig(
device: BalenaSdk.Device & { device: DeviceWithDeviceType & {
belongs_to__application: BalenaSdk.PineDeferred; belongs_to__application: BalenaSdk.PineDeferred;
}, },
deviceApiKey: string | true | undefined, deviceApiKey: string | true | undefined,
@ -113,7 +113,7 @@ export function generateDeviceConfig(
.then(async (application) => { .then(async (application) => {
const baseConfigOpts = { const baseConfigOpts = {
...options, ...options,
deviceType: device.device_type, deviceType: device.is_of__device_type[0].slug,
}; };
const config = await generateBaseConfig(application, baseConfigOpts); const config = await generateBaseConfig(application, baseConfigOpts);

View File

@ -21,7 +21,7 @@ import * as _ from 'lodash';
import * as os from 'os'; import * as os from 'os';
import type * as ShellEscape from 'shell-escape'; import type * as ShellEscape from 'shell-escape';
import type { Device, PineOptionsFor } from 'balena-sdk'; import type { Device, PineOptions } from 'balena-sdk';
import { ExpectedError } from '../errors'; import { ExpectedError } from '../errors';
import { getBalenaSdk, getChalk, getVisuals } from './lazy'; import { getBalenaSdk, getChalk, getVisuals } from './lazy';
import { promisify } from 'util'; import { promisify } from 'util';
@ -121,7 +121,7 @@ export function runCommand<T>(commandArgs: string[]): Promise<T> {
export async function getManifest( export async function getManifest(
image: string, image: string,
deviceType: string, deviceType: string,
): Promise<BalenaSdk.DeviceType> { ): Promise<BalenaSdk.DeviceTypeJson.DeviceType> {
const init = await import('balena-device-init'); const init = await import('balena-device-init');
const manifest = await init.getImageManifest(image); const manifest = await init.getImageManifest(image);
if (manifest != null) { if (manifest != null) {
@ -131,8 +131,8 @@ export async function getManifest(
} }
export const areDeviceTypesCompatible = ( export const areDeviceTypesCompatible = (
appDeviceType: BalenaSdk.DeviceType, appDeviceType: BalenaSdk.DeviceTypeJson.DeviceType,
osDeviceType: BalenaSdk.DeviceType, osDeviceType: BalenaSdk.DeviceTypeJson.DeviceType,
) => ) =>
getBalenaSdk().models.os.isArchitectureCompatibleWith( getBalenaSdk().models.os.isArchitectureCompatibleWith(
osDeviceType.arch, osDeviceType.arch,
@ -166,13 +166,13 @@ export async function osProgressHandler(step: InitializeEmitter) {
export function getAppWithArch( export function getAppWithArch(
applicationName: string, applicationName: string,
): Promise<BalenaSdk.Application & { arch: string }> { ): Promise<ApplicationWithDeviceType & { arch: string }> {
return Promise.all([ return Promise.all([
getApplication(applicationName), getApplication(applicationName),
getBalenaSdk().models.config.getDeviceTypes(), getBalenaSdk().models.config.getDeviceTypes(),
]).then(function ([app, deviceTypes]) { ]).then(function ([app, deviceTypes]) {
const config = _.find<BalenaSdk.DeviceType>(deviceTypes, { const config = _.find<BalenaSdk.DeviceTypeJson.DeviceType>(deviceTypes, {
slug: app.device_type, slug: app.is_for__device_type[0].slug,
}); });
if (!config) { if (!config) {
@ -183,16 +183,21 @@ export function getAppWithArch(
}); });
} }
function getApplication(applicationName: string) { function getApplication(
applicationName: string,
): Promise<ApplicationWithDeviceType> {
// Check for an app of the form `user/application`, and send // Check for an app of the form `user/application`, and send
// that off to a special handler (before importing any modules) // that off to a special handler (before importing any modules)
const match = applicationName.split('/'); const match = applicationName.split('/');
const extraOptions: BalenaSdk.PineOptionsFor<BalenaSdk.Application> = { const extraOptions: BalenaSdk.PineOptions<BalenaSdk.Application> = {
$expand: { $expand: {
application_type: { application_type: {
$select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'], $select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'],
}, },
is_for__device_type: {
$select: 'slug',
},
}, },
}; };
@ -487,6 +492,10 @@ export function getProxyConfig(): ProxyConfig | undefined {
} }
} }
export const expandForAppName: PineOptionsFor<Device> = { export const expandForAppName: PineOptions<Device> = {
$expand: { belongs_to__application: { $select: 'app_name' } }, $expand: {
belongs_to__application: { $select: 'app_name' },
is_of__device_type: { $select: 'slug' },
is_running__release: { $select: 'commit' },
},
}; };

View File

@ -168,7 +168,7 @@ export async function confirm(
} }
export function selectApplication( export function selectApplication(
filter?: (app: BalenaSdk.Application) => boolean, filter?: (app: ApplicationWithDeviceType) => boolean,
errorOnEmptySelection = false, errorOnEmptySelection = false,
) { ) {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
@ -179,7 +179,7 @@ export function selectApplication(
throw new ExpectedError("You don't have any applications"); throw new ExpectedError("You don't have any applications");
} }
return balena.models.application.getAll(); return balena.models.application.getAll() as Promise<ApplicationWithDeviceType[]>;
}) })
.filter(filter || _.constant(true)) .filter(filter || _.constant(true))
.then((applications) => { .then((applications) => {
@ -190,7 +190,7 @@ export function selectApplication(
message: 'Select an application', message: 'Select an application',
type: 'list', type: 'list',
choices: _.map(applications, (application) => ({ choices: _.map(applications, (application) => ({
name: `${application.app_name} (${application.device_type})`, name: `${application.app_name} (${application.is_for__device_type[0].slug})`,
value: application.app_name, value: application.app_name,
})), })),
}); });
@ -207,14 +207,20 @@ export function selectOrCreateApplication() {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
return balena.models.application.getAll().then((applications) => { return (balena.models.application.getAll({
const appOptions = _.map< $expand: {
BalenaSdk.Application, is_for__device_type: {
{ name: string; value: string | null } $select: 'slug',
>(applications, (application) => ({ },
name: `${application.app_name} (${application.device_type})`, },
}) as Promise<ApplicationWithDeviceType[]>).then((applications) => {
const appOptions: Array<{ name: string; value: string | null }> = _.map(
applications,
(application) => ({
name: `${application.app_name} (${application.is_for__device_type[0].slug})`,
value: application.app_name, value: application.app_name,
})); }),
);
appOptions.unshift({ appOptions.unshift({
name: 'Create a new application', name: 'Create a new application',

View File

@ -40,10 +40,12 @@ export async function join(
logger.logDebug('Determining application...'); logger.logDebug('Determining application...');
const app = await getOrSelectApplication(sdk, deviceType, appName); const app = await getOrSelectApplication(sdk, deviceType, appName);
logger.logDebug(`Using application: ${app.app_name} (${app.device_type})`); logger.logDebug(
if (app.device_type !== deviceType) { `Using application: ${app.app_name} (${app.is_for__device_type[0].slug})`,
);
if (app.is_for__device_type[0].slug !== deviceType) {
logger.logDebug(`Forcing device type to: ${deviceType}`); logger.logDebug(`Forcing device type to: ${deviceType}`);
app.device_type = deviceType; app.is_for__device_type[0].slug = deviceType;
} }
logger.logDebug('Determining device OS version...'); logger.logDebug('Determining device OS version...');
@ -186,7 +188,9 @@ async function getOrSelectLocalDevice(deviceIp?: string): Promise<string> {
return ip; return ip;
} }
async function selectAppFromList(applications: BalenaSdk.Application[]) { async function selectAppFromList(
applications: ApplicationWithDeviceType[],
): Promise<ApplicationWithDeviceType> {
const _ = await import('lodash'); const _ = await import('lodash');
const { selectFromList } = await import('../utils/patterns'); const { selectFromList } = await import('../utils/patterns');
@ -204,7 +208,7 @@ async function getOrSelectApplication(
sdk: BalenaSdk.BalenaSDK, sdk: BalenaSdk.BalenaSDK,
deviceType: string, deviceType: string,
appName?: string, appName?: string,
): Promise<BalenaSdk.Application> { ): Promise<ApplicationWithDeviceType> {
const _ = await import('lodash'); const _ = await import('lodash');
const allDeviceTypes = await sdk.models.config.getDeviceTypes(); const allDeviceTypes = await sdk.models.config.getDeviceTypes();
@ -229,7 +233,11 @@ async function getOrSelectApplication(
return createOrSelectAppOrExit(sdk, compatibleDeviceTypes, deviceType); return createOrSelectAppOrExit(sdk, compatibleDeviceTypes, deviceType);
} }
const options: BalenaSdk.PineOptionsFor<BalenaSdk.Application> = {}; const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
$expand: {
is_for__device_type: { $select: 'slug' },
},
};
// Check for an app of the form `user/application` and update the API query. // Check for an app of the form `user/application` and update the API query.
let name: string; let name: string;
@ -245,7 +253,9 @@ async function getOrSelectApplication(
name = appName; name = appName;
} }
const applications = await sdk.models.application.getAll(options); const applications = (await sdk.models.application.getAll(
options,
)) as ApplicationWithDeviceType[];
if (applications.length === 0) { if (applications.length === 0) {
const shouldCreateApp = await getCliForm().ask({ const shouldCreateApp = await getCliForm().ask({
@ -264,7 +274,7 @@ async function getOrSelectApplication(
// We've found at least one app with the given name. // We've found at least one app with the given name.
// Filter out apps for non-matching device types and see what we're left with. // Filter out apps for non-matching device types and see what we're left with.
const validApplications = applications.filter((app) => const validApplications = applications.filter((app) =>
_.includes(compatibleDeviceTypes, app.device_type), _.includes(compatibleDeviceTypes, app.is_for__device_type[0].slug),
); );
if (validApplications.length === 0) { if (validApplications.length === 0) {
@ -285,13 +295,19 @@ async function createOrSelectAppOrExit(
sdk: BalenaSdk.BalenaSDK, sdk: BalenaSdk.BalenaSDK,
compatibleDeviceTypes: string[], compatibleDeviceTypes: string[],
deviceType: string, deviceType: string,
) { ): Promise<ApplicationWithDeviceType> {
const options = {
$filter: { device_type: { $in: compatibleDeviceTypes } },
};
// No application specified, show a list to select one. // No application specified, show a list to select one.
const applications = await sdk.models.application.getAll(options); const applications = (await sdk.models.application.getAll({
$expand: { is_for__device_type: { $select: 'slug' } },
$filter: {
is_for__device_type: {
$any: {
$alias: 'dt',
$expr: { dt: { slug: { $in: compatibleDeviceTypes } } },
},
},
},
})) as ApplicationWithDeviceType[];
if (applications.length === 0) { if (applications.length === 0) {
const shouldCreateApp = await getCliForm().ask({ const shouldCreateApp = await getCliForm().ask({
@ -314,14 +330,13 @@ async function createApplication(
sdk: BalenaSdk.BalenaSDK, sdk: BalenaSdk.BalenaSDK,
deviceType: string, deviceType: string,
name?: string, name?: string,
): Promise<BalenaSdk.Application> { ): Promise<ApplicationWithDeviceType> {
const validation = await import('./validation'); const validation = await import('./validation');
let username = await sdk.auth.whoami(); const username = await sdk.auth.whoami();
if (!username) { if (!username) {
throw new sdk.errors.BalenaNotLoggedIn(); throw new sdk.errors.BalenaNotLoggedIn();
} }
username = username.toLowerCase();
const applicationName = await new Promise<string>(async (resolve, reject) => { const applicationName = await new Promise<string>(async (resolve, reject) => {
while (true) { while (true) {
@ -337,7 +352,7 @@ async function createApplication(
await sdk.models.application.get(appName, { await sdk.models.application.get(appName, {
$filter: { $filter: {
$or: [ $or: [
{ slug: { $startswith: `${username}/` } }, { slug: { $startswith: `${username!.toLowerCase()}/` } },
{ $not: { slug: { $contains: '/' } } }, { $not: { slug: { $contains: '/' } } },
], ],
}, },
@ -357,20 +372,28 @@ async function createApplication(
} }
}); });
return sdk.models.application.create({ const app = await sdk.models.application.create({
name: applicationName, name: applicationName,
deviceType, deviceType,
organization: username,
}); });
return (await sdk.models.application.get(app.id, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
} }
async function generateApplicationConfig( async function generateApplicationConfig(
sdk: BalenaSdk.BalenaSDK, sdk: BalenaSdk.BalenaSDK,
app: BalenaSdk.Application, app: ApplicationWithDeviceType,
options: { version: string }, options: { version: string },
) { ) {
const { generateApplicationConfig: configGen } = await import('./config'); const { generateApplicationConfig: configGen } = await import('./config');
const manifest = await sdk.models.device.getManifestBySlug(app.device_type); const manifest = await sdk.models.device.getManifestBySlug(
app.is_for__device_type[0].slug,
);
const opts = const opts =
manifest.options && manifest.options &&
manifest.options.filter((opt) => opt.name !== 'network'); manifest.options.filter((opt) => opt.name !== 'network');

165
npm-shrinkwrap.json generated
View File

@ -2243,44 +2243,6 @@
"resin-image-fs": "^5.0.9" "resin-image-fs": "^5.0.9"
}, },
"dependencies": { "dependencies": {
"balena-sdk": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.1.0.tgz",
"integrity": "sha512-J/T1F3zQI6GChK0zZp5YKdNhBSynx3VaXk7tGcGykNnLuN4ZVeApHPZ7vVLUuyN/hvckax55iKSPO5JSEYA0sg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
},
"dependencies": {
"balena-errors": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz",
"integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==",
"requires": {
"tslib": "^1.11.1",
"typed-error": "^3.0.0"
}
}
}
},
"balena-semver": { "balena-semver": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz", "resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -2354,44 +2316,6 @@
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
}, },
"dependencies": { "dependencies": {
"balena-sdk": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.1.0.tgz",
"integrity": "sha512-J/T1F3zQI6GChK0zZp5YKdNhBSynx3VaXk7tGcGykNnLuN4ZVeApHPZ7vVLUuyN/hvckax55iKSPO5JSEYA0sg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
},
"dependencies": {
"balena-errors": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz",
"integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==",
"requires": {
"tslib": "^1.11.1",
"typed-error": "^3.0.0"
}
}
}
},
"balena-semver": { "balena-semver": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz", "resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -2474,34 +2398,6 @@
"typed-error": "^3.0.0" "typed-error": "^3.0.0"
} }
}, },
"balena-sdk": {
"version": "14.8.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.8.0.tgz",
"integrity": "sha512-GptB/Ju8BtE1E9NODHioScdF4HW8svcDhfdMHPrDjMFWbs38KOoTgiKogDjLlvEjoAg5FIvz8DaUeH27EtZ1Rg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2",
"tslib": "^2.0.0"
}
},
"balena-semver": { "balena-semver": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz", "resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -2638,19 +2534,20 @@
} }
}, },
"balena-sdk": { "balena-sdk": {
"version": "13.8.0", "version": "14.8.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-13.8.0.tgz", "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.8.0.tgz",
"integrity": "sha512-EhaXZq7kNCewhp0oSMeeTslvRIhvaV0QxlS7u3SWmdMoDjH1gdg01sG2I3b20DkN8oDAIoNkaD14eo2eTf0C8w==", "integrity": "sha512-GptB/Ju8BtE1E9NODHioScdF4HW8svcDhfdMHPrDjMFWbs38KOoTgiKogDjLlvEjoAg5FIvz8DaUeH27EtZ1Rg==",
"requires": { "requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30", "@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150", "@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3", "@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20", "@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0", "abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.0.1", "balena-auth": "^3.1.0",
"balena-errors": "^4.4.0", "balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1", "balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.0.1", "balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1", "balena-register-device": "^6.1.1",
"balena-request": "^10.0.9", "balena-request": "^10.0.9",
"balena-semver": "^2.3.0", "balena-semver": "^2.3.0",
@ -2660,15 +2557,16 @@
"memoizee": "^0.4.14", "memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1", "moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0", "ndjson": "^1.5.0",
"semver": "^7.3.2" "semver": "^7.3.2",
"tslib": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"balena-errors": { "balena-errors": {
"version": "4.4.0", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz", "resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.1.tgz",
"integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==", "integrity": "sha512-912lPp1LyBjkpxRg6m/EpOCssqMhgkzyYbrKwtT2uRvixm89WOlJrj5sPkxnbPnp5IoMNaoRONxFt1jtiQf50Q==",
"requires": { "requires": {
"tslib": "^1.11.1", "tslib": "^2.0.0",
"typed-error": "^3.0.0" "typed-error": "^3.0.0"
} }
}, },
@ -2689,9 +2587,9 @@
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
}, },
"tslib": { "tslib": {
"version": "1.13.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
} }
} }
}, },
@ -2794,33 +2692,6 @@
} }
} }
}, },
"balena-sdk": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.1.0.tgz",
"integrity": "sha512-J/T1F3zQI6GChK0zZp5YKdNhBSynx3VaXk7tGcGykNnLuN4ZVeApHPZ7vVLUuyN/hvckax55iKSPO5JSEYA0sg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
}
},
"balena-semver": { "balena-semver": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz", "resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -12604,9 +12475,9 @@
} }
}, },
"ref-napi": { "ref-napi": {
"version": "2.0.3", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-2.0.3.tgz", "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-2.0.4.tgz",
"integrity": "sha512-zsAhPhh9gUlk0pP5iR9nhvwFeC/E9G1X0cdH/qQRTwx3VDgVi40Aflq/EdbobcVg++RNaMxZsbaQV+/E2u57LQ==", "integrity": "sha512-Ees+O+abgS4skRDU/RyICk9gbSaVjYCN1FXivAlcxdj/yW9N+GFKbB9fYACa+lgBjjqjSGHw5SkoP5GU0anwwQ==",
"optional": true, "optional": true,
"requires": { "requires": {
"debug": "^4.1.1", "debug": "^4.1.1",

View File

@ -26,6 +26,7 @@
"pkg": { "pkg": {
"scripts": [ "scripts": [
"build/**/*.js", "build/**/*.js",
"node_modules/balena-sdk/es2018/index.js",
"node_modules/balena-sync/build/capitano/*.js", "node_modules/balena-sync/build/capitano/*.js",
"node_modules/balena-sync/build/sync/*.js", "node_modules/balena-sync/build/sync/*.js",
"node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js", "node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js",
@ -192,7 +193,7 @@
"balena-image-manager": "^7.0.1", "balena-image-manager": "^7.0.1",
"balena-preload": "^10.2.0", "balena-preload": "^10.2.0",
"balena-release": "^3.0.0", "balena-release": "^3.0.0",
"balena-sdk": "^13.6.0", "balena-sdk": "^14.8.0",
"balena-semver": "^2.2.0", "balena-semver": "^2.2.0",
"balena-settings-client": "^4.0.5", "balena-settings-client": "^4.0.5",
"balena-settings-storage": "^6.0.0", "balena-settings-storage": "^6.0.0",

View File

@ -35,26 +35,6 @@ export class BalenaAPIMock extends NockMock {
notFound = false, notFound = false,
optional = false, optional = false,
persist = false, persist = false,
} = {}) {
const interceptor = this.optGet(/^\/v5\/application($|[(?])/, {
optional,
persist,
});
if (notFound) {
interceptor.reply(200, { d: [] });
} else {
interceptor.replyWithFile(
200,
path.join(apiResponsePath, 'application-GET-v5-expanded-app-type.json'),
jHeader,
);
}
}
public expectGetApplicationV6({
notFound = false,
optional = false,
persist = false,
} = {}) { } = {}) {
const interceptor = this.optGet(/^\/v6\/application($|[(?])/, { const interceptor = this.optGet(/^\/v6\/application($|[(?])/, {
optional, optional,
@ -99,10 +79,10 @@ export class BalenaAPIMock extends NockMock {
} }
public expectGetMyApplication(opts: ScopeOpts = {}) { public expectGetMyApplication(opts: ScopeOpts = {}) {
this.optGet(/^\/v5\/my_application($|[(?])/, opts).reply( this.optGet(/^\/v6\/my_application($|[(?])/, opts).reply(
200, 200,
JSON.parse(`{"d": [{ JSON.parse(`{"d": [{
"user": [{ "username": "bob", "__metadata": {} }], "organization": [{ "handle": "bob", "__metadata": {} }],
"id": 1301645, "id": 1301645,
"__metadata": { "uri": "/resin/my_application(@id)?@id=1301645" }}]} "__metadata": { "uri": "/resin/my_application(@id)?@id=1301645" }}]}
`), `),
@ -116,13 +96,16 @@ export class BalenaAPIMock extends NockMock {
} }
public expectGetRelease(opts: ScopeOpts = {}) { public expectGetRelease(opts: ScopeOpts = {}) {
this.optGet(/^\/v5\/release($|[(?])/, opts).replyWithFile( this.optGet(/^\/v6\/release($|[(?])/, opts).replyWithFile(
200, 200,
path.join(apiResponsePath, 'release-GET-v5.json'), path.join(apiResponsePath, 'release-GET-v6.json'),
jHeader, jHeader,
); );
} }
/**
* Mocks balena-release call
*/
public expectPatchRelease({ public expectPatchRelease({
replyBody = 'OK', replyBody = 'OK',
statusCode = 200, statusCode = 200,
@ -136,6 +119,9 @@ export class BalenaAPIMock extends NockMock {
); );
} }
/**
* Mocks balena-release call
*/
public expectPostRelease(opts: ScopeOpts = {}) { public expectPostRelease(opts: ScopeOpts = {}) {
this.optPost(/^\/v6\/release($|[(?])/, opts).replyWithFile( this.optPost(/^\/v6\/release($|[(?])/, opts).replyWithFile(
200, 200,
@ -144,6 +130,9 @@ export class BalenaAPIMock extends NockMock {
); );
} }
/**
* Mocks balena-release call
*/
public expectPatchImage({ public expectPatchImage({
replyBody = 'OK', replyBody = 'OK',
statusCode = 200, statusCode = 200,
@ -157,6 +146,9 @@ export class BalenaAPIMock extends NockMock {
); );
} }
/**
* Mocks balena-release call
*/
public expectPostImage(opts: ScopeOpts = {}) { public expectPostImage(opts: ScopeOpts = {}) {
this.optPost(/^\/v6\/image($|[(?])/, opts).replyWithFile( this.optPost(/^\/v6\/image($|[(?])/, opts).replyWithFile(
201, 201,
@ -165,6 +157,9 @@ export class BalenaAPIMock extends NockMock {
); );
} }
/**
* Mocks balena-release call
*/
public expectPostImageLabel(opts: ScopeOpts = {}) { public expectPostImageLabel(opts: ScopeOpts = {}) {
this.optPost(/^\/v6\/image_label($|[(?])/, opts).replyWithFile( this.optPost(/^\/v6\/image_label($|[(?])/, opts).replyWithFile(
201, 201,
@ -173,6 +168,9 @@ export class BalenaAPIMock extends NockMock {
); );
} }
/**
* Mocks balena-release call
*/
public expectPostImageIsPartOfRelease(opts: ScopeOpts = {}) { public expectPostImageIsPartOfRelease(opts: ScopeOpts = {}) {
this.optPost( this.optPost(
/^\/v6\/image__is_part_of__release($|[(?])/, /^\/v6\/image__is_part_of__release($|[(?])/,
@ -350,23 +348,10 @@ export class BalenaAPIMock extends NockMock {
); );
} }
/**
* Mocks balena-release call
*/
public expectGetUser(opts: ScopeOpts = {}) { public expectGetUser(opts: ScopeOpts = {}) {
this.optGet(/^\/v5\/user/, opts).reply(200, {
d: [
{
id: 99999,
actor: 1234567,
username: 'gh_user',
created_at: '2018-08-19T13:55:04.485Z',
__metadata: {
uri: '/resin/user(@id)?@id=43699',
},
},
],
});
}
public expectGetUserV6(opts: ScopeOpts = {}) {
this.optGet(/^\/v6\/user/, opts).reply(200, { this.optGet(/^\/v6\/user/, opts).reply(200, {
d: [ d: [
{ {

View File

@ -80,10 +80,9 @@ describe('balena deploy', function () {
api.expectGetMixpanel({ optional: true }); api.expectGetMixpanel({ optional: true });
api.expectGetDeviceTypes(); api.expectGetDeviceTypes();
api.expectGetApplication(); api.expectGetApplication();
api.expectGetApplicationV6();
api.expectPostRelease(); api.expectPostRelease();
api.expectGetRelease(); api.expectGetRelease();
api.expectGetUserV6(); api.expectGetUser();
api.expectGetService({ serviceName: 'main' }); api.expectGetService({ serviceName: 'main' });
api.expectPostService409(); api.expectPostService409();
api.expectGetAuth(); api.expectGetAuth();

View File

@ -74,7 +74,7 @@ describe('balena device', function () {
it('should list device details for provided uuid', async () => { it('should list device details for provided uuid', async () => {
api.scope api.scope
.get( .get(
/^\/v5\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/, /^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
) )
.replyWithFile(200, path.join(apiResponsePath, 'device.json'), { .replyWithFile(200, path.join(apiResponsePath, 'device.json'), {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -96,7 +96,7 @@ describe('balena device', function () {
// e.g. When user has a device associated with app that user is no longer a collaborator of. // e.g. When user has a device associated with app that user is no longer a collaborator of.
api.scope api.scope
.get( .get(
/^\/v5\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/, /^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
) )
.replyWithFile( .replyWithFile(
200, 200,

View File

@ -75,7 +75,7 @@ describe('balena devices', function () {
it('should list devices from own and collaborator apps', async () => { it('should list devices from own and collaborator apps', async () => {
api.scope api.scope
.get( .get(
'/v5/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name)', '/v6/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name),is_of__device_type($select=slug),is_running__release($select=commit)',
) )
.replyWithFile(200, path.join(apiResponsePath, 'devices.json'), { .replyWithFile(200, path.join(apiResponsePath, 'devices.json'), {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@ -1,35 +0,0 @@
{
"d": [
{
"application_type": [
{
"name": "Starter",
"slug": "microservices-starter",
"supports_multicontainer": true,
"is_legacy": false,
"__metadata": {}
}
],
"id": 1301645,
"user": {
"__deferred": {
"uri": "/resin/user(43699)"
},
"__id": 43699
},
"depends_on__application": null,
"actor": 3423895,
"app_name": "testApp",
"slug": "gh_user/testApp",
"commit": "96eec431d57e6976d3a756df33fde7e2",
"device_type": "raspberrypi3",
"should_track_latest_release": true,
"is_accessible_by_support_until__date": null,
"is_public": false,
"is_host": false,
"__metadata": {
"uri": "/resin/application(@id)?@id=1301645"
}
}
]
}

View File

@ -21,8 +21,16 @@
"actor": 3423895, "actor": 3423895,
"app_name": "testApp", "app_name": "testApp",
"slug": "gh_user/testApp", "slug": "gh_user/testApp",
"commit": "96eec431d57e6976d3a756df33fde7e2", "should_be__running_release": [
"device_type": "raspberrypi3", {
"commit": "96eec431d57e6976d3a756df33fde7e2"
}
],
"is_for__device_type": [
{
"slug": "raspberrypi3"
}
],
"should_track_latest_release": true, "should_track_latest_release": true,
"is_accessible_by_support_until__date": null, "is_accessible_by_support_until__date": null,
"is_public": false, "is_public": false,

View File

@ -1,14 +1,13 @@
{ {
"d": [ "d": [
{ {
"belongs_to__application": [ "belongs_to__application": [],
],
"id": 1747415, "id": 1747415,
"is_managed_by__device": null, "is_managed_by__device": null,
"device_name": "sparkling-wood", "device_name": "sparkling-wood",
"device_type": "raspberrypi4-64", "is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2", "uuid": "fda508c8583011b8466c26abdd5159f2",
"is_on__commit": "18756d3386c25a044db66b89e0409804", "is_running__release": [{ "commit": "18756d3386c25a044db66b89e0409804" }],
"note": null, "note": null,
"is_online": false, "is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z", "last_connectivity_event": "2019-11-23T00:26:35.074Z",

View File

@ -10,9 +10,9 @@
"id": 1747415, "id": 1747415,
"is_managed_by__device": null, "is_managed_by__device": null,
"device_name": "sparkling-wood", "device_name": "sparkling-wood",
"device_type": "raspberrypi4-64", "is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2", "uuid": "fda508c8583011b8466c26abdd5159f2",
"is_on__commit": "18756d3386c25a044db66b89e0409804", "is_running__release": [{ "commit": "18756d3386c25a044db66b89e0409804" }],
"note": null, "note": null,
"is_online": false, "is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z", "last_connectivity_event": "2019-11-23T00:26:35.074Z",

View File

@ -16,11 +16,14 @@
}, },
"is_managed_by__device": null, "is_managed_by__device": null,
"actor": 4180757, "actor": 4180757,
"should_be_running__release": null,
"device_name": "sparkling-wood", "device_name": "sparkling-wood",
"device_type": "raspberrypi4-64", "is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2", "uuid": "fda508c8583011b8466c26abdd5159f2",
"is_on__commit": "18756d3386c25a044db66b89e0409804", "is_running__release": [
{
"commit": "18756d3386c25a044db66b89e0409804"
}
],
"note": null, "note": null,
"local_id": null, "local_id": null,
"status": "Idle", "status": "Idle",
@ -61,8 +64,7 @@
} }
}, },
{ {
"belongs_to__application": [ "belongs_to__application": [],
],
"id": 1747416, "id": 1747416,
"belongs_to__user": { "belongs_to__user": {
"__deferred": { "__deferred": {
@ -72,11 +74,14 @@
}, },
"is_managed_by__device": null, "is_managed_by__device": null,
"actor": 4180757, "actor": 4180757,
"should_be_running__release": null,
"device_name": "dashing-spruce", "device_name": "dashing-spruce",
"device_type": "raspberrypi4-64", "is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f3", "uuid": "fda508c8583011b8466c26abdd5159f3",
"is_on__commit": "18756d3386c25a044db66b89e0409804", "is_running__release": [
{
"commit": "18756d3386c25a044db66b89e0409804"
}
],
"note": null, "note": null,
"local_id": null, "local_id": null,
"status": "Idle", "status": "Idle",

View File

@ -16,7 +16,7 @@
*/ */
declare module 'balena-device-init' { declare module 'balena-device-init' {
import { DeviceType } from 'balena-sdk'; import { DeviceTypeJson } from 'balena-sdk';
import * as Bluebird from 'bluebird'; import * as Bluebird from 'bluebird';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
@ -81,21 +81,23 @@ declare module 'balena-device-init' {
export function configure( export function configure(
image: string, image: string,
manifest: DeviceType, manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
config: {}, config: {},
options?: {}, options?: {},
): Bluebird<InitializeEmitter>; ): Bluebird<InitializeEmitter>;
export function initialize( export function initialize(
image: string, image: string,
manifest: DeviceType, manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
config: {}, config: {},
): Bluebird<InitializeEmitter>; ): Bluebird<InitializeEmitter>;
export function getImageOsVersion( export function getImageOsVersion(
image: string, image: string,
manifest: DeviceType, manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
): Bluebird<string | null>; ): Bluebird<string | null>;
export function getImageManifest(image: string): Bluebird<DeviceType | null>; export function getImageManifest(
image: string,
): Bluebird<BalenaSdk.DeviceTypeJson.DeviceType.DeviceType | null>;
} }

10
typings/global.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { Application, DeviceType, Device } from 'balena-sdk';
declare global {
type ApplicationWithDeviceType = Application & {
is_for__device_type: [DeviceType];
};
type DeviceWithDeviceType = Device & {
is_of__device_type: [DeviceType];
};
}