balena-cli 23.2.15-build-update-compose-7-3-0-97bf26975807627a264274621ffcf8380aea838e-1 → 23.2.15-build-update-compose-tar-dependencies-132ab39d4a4a27c2766acc7a6aee1cedfbe13a2c-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balena-cli",
3
- "version": "23.2.15-build-update-compose-7-3-0-97bf26975807627a264274621ffcf8380aea838e-1",
3
+ "version": "23.2.15-build-update-compose-tar-dependencies-132ab39d4a4a27c2766acc7a6aee1cedfbe13a2c-1",
4
4
  "description": "The official balena Command Line Interface",
5
5
  "main": "./build/app.js",
6
6
  "homepage": "https://github.com/balena-io/balena-cli",
@@ -176,7 +176,7 @@
176
176
  "typescript": "^5.9.2"
177
177
  },
178
178
  "dependencies": {
179
- "@balena/compose": "^7.3.0",
179
+ "@balena/compose": "^7.2.1",
180
180
  "@balena/dockerignore": "^1.0.2",
181
181
  "@balena/env-parsing": "^1.1.8",
182
182
  "@balena/es-version": "^1.0.1",
@@ -243,8 +243,8 @@
243
243
  "stream-to-promise": "^2.2.0",
244
244
  "string-width": "^4.2.3",
245
245
  "strip-ansi-stream": "^1.0.0",
246
- "tar-stream": "^2.1.3",
247
- "tar-utils": "^3.0.3",
246
+ "tar-stream": "^3.1.7",
247
+ "tar-utils": "^3.1.1",
248
248
  "through2": "^2.0.3",
249
249
  "tmp": "^0.2.1",
250
250
  "typed-error": "^3.2.1",
@@ -262,6 +262,6 @@
262
262
  "balena-request": "14.0.6"
263
263
  },
264
264
  "versionist": {
265
- "publishedAt": "2026-01-05T23:04:56.483Z"
265
+ "publishedAt": "2026-01-07T12:07:55.047Z"
266
266
  }
267
267
  }
@@ -208,7 +208,7 @@ export default class OsConfigureCmd extends Command {
208
208
  })) as DeviceWithDeviceType & {
209
209
  belongs_to__application: BalenaSdk.PineDeferred;
210
210
  };
211
- deviceTypeSlug = device.is_of__device_type[0].slug!;
211
+ deviceTypeSlug = device.is_of__device_type[0].slug;
212
212
  } else if (fleetSlugOrId != null) {
213
213
  app = await getApplication(balena, fleetSlugOrId, {
214
214
  $select: 'slug',
@@ -226,7 +226,7 @@ export default class OsConfigureCmd extends Command {
226
226
 
227
227
  const deviceTypeManifest = await helpers.getManifest(
228
228
  params.image,
229
- deviceTypeSlug!,
229
+ deviceTypeSlug,
230
230
  );
231
231
 
232
232
  const { normalizeOsVersion } = await import('../../utils/normalization');
@@ -242,7 +242,7 @@ export default class OsConfigureCmd extends Command {
242
242
  );
243
243
  await validateSecureBootOptionAndWarn(
244
244
  secureBoot,
245
- deviceTypeSlug!,
245
+ deviceTypeSlug,
246
246
  osVersion,
247
247
  );
248
248
 
@@ -258,7 +258,7 @@ export default class OsConfigureCmd extends Command {
258
258
  };
259
259
  const answers: AugmentedAnswers = {
260
260
  ...baseAnswers,
261
- deviceType: deviceTypeSlug!,
261
+ deviceType: deviceTypeSlug,
262
262
  version: osVersion,
263
263
  developmentMode: developmentMode,
264
264
  secureBoot: secureBoot,
@@ -89,10 +89,10 @@ export default class ReleaseListCmd extends Command {
89
89
  (r) => r.id,
90
90
  );
91
91
 
92
- const augmentedReleases = releases.map((release) => {
93
- const extra = releasesWithExplicitReadFieldsById[release.id];
94
- return typeof extra === 'object' ? { ...release, ...extra } : release;
95
- });
92
+ const augmentedReleases = releases.map((release) => ({
93
+ ...release,
94
+ ...releasesWithExplicitReadFieldsById[release.id],
95
+ }));
96
96
 
97
97
  await pipeline(
98
98
  Readable.from(augmentedReleases),
@@ -15,41 +15,37 @@
15
15
  * limitations under the License.
16
16
  */
17
17
 
18
- import type { BalenaModel } from 'balena-sdk';
19
- import type { Composition, ImageDescriptor } from '@balena/compose/dist/parse';
18
+ import type * as compose from '@balena/compose';
20
19
  import type { Pack } from 'tar-stream';
20
+ import type { BalenaModel } from 'balena-sdk';
21
21
 
22
22
  interface Image {
23
23
  context: string;
24
24
  tag: string;
25
25
  }
26
26
 
27
- interface ImageProperties {
28
- dockerfile?: string;
29
- projectType?: string;
30
- size?: number;
31
- startTime?: Date;
32
- endTime?: Date;
33
- }
34
-
35
27
  export interface BuiltImage {
36
28
  logs: string;
37
29
  name: string;
38
- props: ImageProperties;
30
+ props: {
31
+ dockerfile?: string;
32
+ projectType?: string;
33
+ size?: number;
34
+ startTime?: Date;
35
+ endTime?: Date;
36
+ };
39
37
  serviceName: string;
40
38
  }
41
39
 
42
- type ServiceImage = Omit<
43
- BalenaModel['image']['Read'],
44
- 'created_at' | 'is_a_build_of__service'
45
- >;
46
-
47
40
  export interface TaggedImage {
48
41
  localImage: import('dockerode').Image;
49
- serviceImage: ServiceImage;
42
+ serviceImage: Omit<
43
+ BalenaModel['image']['Read'],
44
+ 'created_at' | 'is_a_build_of__service'
45
+ >;
50
46
  serviceName: string;
51
47
  logs: string;
52
- props: ImageProperties;
48
+ props: BuiltImage.props;
53
49
  registry: string;
54
50
  repo: string;
55
51
  }
@@ -79,12 +75,12 @@ export interface ComposeCliFlags {
79
75
  export interface ComposeProject {
80
76
  path: string;
81
77
  name: string;
82
- composition: Composition;
83
- descriptors: ImageDescriptor[];
78
+ composition: compose.parse.Composition;
79
+ descriptors: compose.parse.ImageDescriptor[];
84
80
  }
85
81
 
86
82
  export interface Release {
87
- client: import('@balena/compose').release.Request['client'];
83
+ client: compose.release.Request['client'];
88
84
  release: Pick<
89
85
  BalenaModel['release']['Read'],
90
86
  | 'id'
@@ -93,27 +89,18 @@ export interface Release {
93
89
  | 'composition'
94
90
  | 'source'
95
91
  | 'is_final'
92
+ | 'contract'
96
93
  | 'semver'
97
94
  | 'start_timestamp'
98
95
  | 'end_timestamp'
99
- > & {
100
- contract: Contract | null;
101
- };
102
- serviceImages: Dictionary<ServiceImage>;
96
+ >;
97
+ serviceImages: Dictionary<
98
+ Omit<BalenaModel['image']['Read'], 'created_at' | 'is_a_build_of__service'>
99
+ >;
103
100
  }
104
101
 
105
- // TODO: The balena contract model for release.contract is `{ [key: string]: JSON } | JSON[] | null`
106
- // which appears to be incorrect, as a contract is represented as `{ [key: string]: any }`
107
- // (more specifically, `{ type: string, [key: string]: any }`) when querying a release for its contract.
108
- // @balena/contrato also defines a ContractObject as the type below, see:
109
- // https://github.com/balena-io/contrato/blob/543e891249431aa98474e17fecc66617ab9ca8f3/lib/types.ts#L15-L17
110
- export type Contract = {
111
- type: string;
112
- [key: string]: any;
113
- };
114
-
115
102
  interface TarDirectoryOptions {
116
- composition?: Composition;
103
+ composition?: compose.parse.Composition;
117
104
  convertEol?: boolean;
118
105
  multiDockerignore?: boolean;
119
106
  preFinalizeCallback?: (pack: Pack) => void | Promise<void>;
@@ -26,7 +26,6 @@ import type {
26
26
  ComposeOpts,
27
27
  ComposeProject,
28
28
  Release,
29
- Contract,
30
29
  TaggedImage,
31
30
  } from './compose-types';
32
31
  import Logger = require('./logger');
@@ -129,7 +128,7 @@ export const createRelease = async function (
129
128
  composition: Composition,
130
129
  draft: boolean,
131
130
  semver: string | undefined,
132
- contract: Contract | undefined,
131
+ contract: import('@balena/compose').release.Request['contract'],
133
132
  imgDescriptors: ImageDescriptor[],
134
133
  ): Promise<Release> {
135
134
  const crypto = require('crypto') as typeof import('crypto');
@@ -157,7 +156,7 @@ export const createRelease = async function (
157
156
  );
158
157
 
159
158
  const { id: userId } = await sdk.auth.getUserInfo();
160
- const { release, serviceImages } = await releaseMod.create({
159
+ const { release, serviceImages: $serviceImages } = await releaseMod.create({
161
160
  client: pinejsClient,
162
161
  user: userId,
163
162
  application: appId,
@@ -166,20 +165,23 @@ export const createRelease = async function (
166
165
  commit: crypto.pseudoRandomBytes(16).toString('hex').toLowerCase(),
167
166
  semver,
168
167
  is_final: !draft,
169
- contract: contract ?? undefined,
168
+ contract,
170
169
  imgDescriptors,
171
170
  });
172
171
 
173
- for (const serviceImage of Object.values(serviceImages)) {
174
- if ('created_at' in serviceImage) {
175
- // created_at is not an optional field in the SDK, but we need to delete it
176
- serviceImage.created_at = undefined as any;
177
- }
178
- if ('is_a_build_of__service' in serviceImage) {
179
- // is_a_build_of__service is not an optional field in the SDK, but we need to delete it
180
- serviceImage.is_a_build_of__service = undefined as any;
181
- }
182
- }
172
+ const serviceImages = Object.fromEntries(
173
+ Object.entries($serviceImages).map(([key, serviceImage]) => {
174
+ if (
175
+ 'created_at' in serviceImage ||
176
+ 'is_a_build_of__service' in serviceImage
177
+ ) {
178
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- omit the created_at, is_a_build_of__service props
179
+ const { created_at, is_a_build_of__service, ...si } = serviceImage;
180
+ return [key, si];
181
+ }
182
+ return [key, serviceImage];
183
+ }),
184
+ );
183
185
 
184
186
  return {
185
187
  client: pinejsClient,
@@ -194,7 +196,7 @@ export const createRelease = async function (
194
196
  'semver',
195
197
  'start_timestamp',
196
198
  'end_timestamp',
197
- ]) as Release['release'],
199
+ ]),
198
200
  serviceImages,
199
201
  };
200
202
  };
@@ -37,16 +37,14 @@ import type {
37
37
  BuiltImage,
38
38
  ComposeOpts,
39
39
  ComposeProject,
40
+ Release,
40
41
  TaggedImage,
41
42
  TarDirectoryOptions,
42
- Release,
43
- Contract,
44
43
  } from './compose-types';
45
44
  import type { DeviceInfo } from './device/api';
46
45
  import { getBalenaSdk, getCliUx, stripIndent } from './lazy';
47
46
  import Logger = require('./logger');
48
47
  import { exists } from './which';
49
- import { pick } from './helpers';
50
48
 
51
49
  const allowedContractTypes = ['sw.application', 'sw.block'];
52
50
 
@@ -1332,13 +1330,13 @@ async function pushAndUpdateServiceImages(
1332
1330
  serviceImage.image_size = `${imgInfo.Size}`;
1333
1331
  serviceImage.content_hash = imgDigest;
1334
1332
  serviceImage.build_log = logs;
1335
- serviceImage.dockerfile = props.dockerfile ?? null;
1336
- serviceImage.project_type = props.projectType ?? null;
1333
+ serviceImage.dockerfile = props.dockerfile;
1334
+ serviceImage.project_type = props.projectType;
1337
1335
  if (props.startTime) {
1338
- serviceImage.start_timestamp = props.startTime.toISOString();
1336
+ serviceImage.start_timestamp = props.startTime;
1339
1337
  }
1340
1338
  if (props.endTime) {
1341
- serviceImage.end_timestamp = props.endTime.toISOString();
1339
+ serviceImage.end_timestamp = props.endTime;
1342
1340
  }
1343
1341
  serviceImage.push_timestamp = new Date().toISOString();
1344
1342
  serviceImage.status = 'success';
@@ -1380,23 +1378,23 @@ async function pushServiceImages(
1380
1378
  logger.logDebug(
1381
1379
  `Saving image ${serviceImage.is_stored_at__image_location}`,
1382
1380
  );
1383
- if (skipLogUpload) {
1384
- serviceImage.build_log = null;
1385
- }
1386
1381
 
1387
1382
  // These are the only update-able image fields in bC atm, and passing
1388
1383
  // the whole image object in v7+ would result the allowlist to reject the request.
1389
- const imagePayload = pick(serviceImage, [
1384
+ const imagePayloadFields = [
1390
1385
  'end_timestamp',
1391
1386
  'project_type',
1392
1387
  'error_message',
1393
- 'build_log',
1394
1388
  'push_timestamp',
1395
1389
  'status',
1396
1390
  'content_hash',
1397
1391
  'dockerfile',
1398
1392
  'image_size',
1399
- ]);
1393
+ ];
1394
+ if (!skipLogUpload) {
1395
+ imagePayloadFields.push('build_log');
1396
+ }
1397
+ const imagePayload = _.pick(serviceImage, imagePayloadFields);
1400
1398
 
1401
1399
  if (
1402
1400
  typeof imagePayload.error_message === 'string' &&
@@ -1554,7 +1552,7 @@ export function createRunLoop(tick: (...args: any[]) => void) {
1554
1552
 
1555
1553
  async function getContractContent(
1556
1554
  filePath: string,
1557
- ): Promise<Contract | undefined> {
1555
+ ): Promise<import('@balena/compose').release.Request['contract']> {
1558
1556
  let fileContentAsString;
1559
1557
  try {
1560
1558
  fileContentAsString = await fs.readFile(filePath, 'utf8');
@@ -1584,7 +1582,7 @@ async function getContractContent(
1584
1582
  return asJson;
1585
1583
  }
1586
1584
 
1587
- function isContract(obj: any): obj is Contract {
1585
+ function isContract(obj: any): obj is Dictionary<any> {
1588
1586
  return obj?.type && allowedContractTypes.includes(obj.type);
1589
1587
  }
1590
1588