balena-cli 23.2.21 → 23.2.22

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/build/app.d.ts +0 -1
  3. package/build/app.js +4 -8
  4. package/build/app.js.map +1 -1
  5. package/build/commands/config/write.d.ts +1 -1
  6. package/build/commands/config/write.js +3 -3
  7. package/build/commands/config/write.js.map +1 -1
  8. package/build/commands/deploy/index.js +1 -1
  9. package/build/commands/deploy/index.js.map +1 -1
  10. package/build/commands/device/ssh.js +1 -1
  11. package/build/commands/device/ssh.js.map +1 -1
  12. package/build/deprecation.d.ts +1 -1
  13. package/build/deprecation.js +3 -3
  14. package/build/deprecation.js.map +1 -1
  15. package/build/help.d.ts +1 -1
  16. package/build/help.js +3 -3
  17. package/build/help.js.map +1 -1
  18. package/build/preparser.d.ts +1 -1
  19. package/build/preparser.js +2 -2
  20. package/build/preparser.js.map +1 -1
  21. package/build/utils/compose.d.ts +1 -1
  22. package/build/utils/compose_ts.js +4 -4
  23. package/build/utils/compose_ts.js.map +1 -1
  24. package/build/utils/deploy-legacy.js +11 -12
  25. package/build/utils/deploy-legacy.js.map +1 -1
  26. package/build/utils/helpers.d.ts +2 -2
  27. package/build/utils/helpers.js +6 -6
  28. package/build/utils/helpers.js.map +1 -1
  29. package/build/utils/qemu.js +3 -6
  30. package/build/utils/qemu.js.map +1 -1
  31. package/build/utils/streams.js.map +1 -1
  32. package/build/utils/sudo.js +1 -1
  33. package/build/utils/sudo.js.map +1 -1
  34. package/build/utils/umount.js +1 -1
  35. package/build/utils/umount.js.map +1 -1
  36. package/build/utils/which.d.ts +1 -1
  37. package/build/utils/which.js +2 -2
  38. package/build/utils/which.js.map +1 -1
  39. package/npm-shrinkwrap.json +4 -2
  40. package/oclif.manifest.json +455 -455
  41. package/package.json +2 -2
  42. package/src/app.ts +4 -14
  43. package/src/commands/config/write.ts +7 -3
  44. package/src/commands/deploy/index.ts +1 -1
  45. package/src/commands/device/ssh.ts +1 -1
  46. package/src/deprecation.ts +3 -4
  47. package/src/help.ts +5 -4
  48. package/src/preparser.ts +2 -2
  49. package/src/utils/compose.ts +1 -1
  50. package/src/utils/compose_ts.ts +8 -4
  51. package/src/utils/deploy-legacy.ts +20 -19
  52. package/src/utils/helpers.ts +9 -7
  53. package/src/utils/qemu.ts +3 -7
  54. package/src/utils/streams.ts +1 -1
  55. package/src/utils/sudo.ts +1 -1
  56. package/src/utils/umount.ts +1 -1
  57. package/src/utils/which.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balena-cli",
3
- "version": "23.2.21",
3
+ "version": "23.2.22",
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",
@@ -256,6 +256,6 @@
256
256
  }
257
257
  },
258
258
  "versionist": {
259
- "publishedAt": "2026-01-13T21:32:28.241Z"
259
+ "publishedAt": "2026-01-14T11:22:38.472Z"
260
260
  }
261
261
  }
package/src/app.ts CHANGED
@@ -51,24 +51,14 @@ async function checkNodeVersion() {
51
51
  }
52
52
 
53
53
  /** Setup balena-sdk options that are shared with imported packages */
54
- function setupBalenaSdkSharedOptions(settings: CliSettings) {
55
- const BalenaSdk = require('balena-sdk') as typeof import('balena-sdk');
54
+ async function setupBalenaSdkSharedOptions(settings: CliSettings) {
55
+ const BalenaSdk = await import('balena-sdk');
56
56
  BalenaSdk.setSharedOptions({
57
57
  apiUrl: settings.get<string>('apiUrl'),
58
58
  dataDirectory: settings.get<string>('dataDirectory'),
59
59
  });
60
60
  }
61
61
 
62
- /**
63
- * Addresses the console warning:
64
- * (node:49500) MaxListenersExceededWarning: Possible EventEmitter memory
65
- * leak detected. 11 error listeners added. Use emitter.setMaxListeners() to
66
- * increase limit
67
- */
68
- export function setMaxListeners(maxListeners: number) {
69
- require('events').EventEmitter.defaultMaxListeners = maxListeners;
70
- }
71
-
72
62
  /** Selected CLI initialization steps */
73
63
  async function init() {
74
64
  if (process.env.BALENARC_NO_SENTRY) {
@@ -85,7 +75,7 @@ async function init() {
85
75
  // Proxy setup should be done early on, before loading balena-sdk
86
76
  await (await import('./utils/proxy')).setupGlobalHttpProxy(settings);
87
77
 
88
- setupBalenaSdkSharedOptions(settings);
78
+ await setupBalenaSdkSharedOptions(settings);
89
79
 
90
80
  // check for CLI updates once a day
91
81
  if (!process.env.BALENARC_OFFLINE_MODE) {
@@ -190,7 +180,7 @@ export async function run(cliArgs = process.argv, options: AppOptions) {
190
180
  await init();
191
181
 
192
182
  // Look for commands that have been removed and if so, exit with a notice
193
- checkDeletedCommand(cliArgs.slice(2));
183
+ await checkDeletedCommand(cliArgs.slice(2));
194
184
 
195
185
  const args = await preparseArgs(cliArgs);
196
186
  await oclifRun(args, options);
@@ -68,7 +68,7 @@ export default class ConfigWriteCmd extends Command {
68
68
  const configJSON = await config.read(drive);
69
69
 
70
70
  console.info(`Setting ${params.key} to ${params.value}`);
71
- ConfigWriteCmd.updateConfigJson(configJSON, params.key, params.value);
71
+ await ConfigWriteCmd.updateConfigJson(configJSON, params.key, params.value);
72
72
 
73
73
  await denyMount(drive, async () => {
74
74
  await safeUmount(drive);
@@ -84,8 +84,12 @@ export default class ConfigWriteCmd extends Command {
84
84
  }
85
85
 
86
86
  /** Call Lodash's _.setWith(). Moved here for ease of testing. */
87
- static updateConfigJson(configJSON: object, key: string, value: string) {
88
- const _ = require('lodash') as typeof import('lodash');
87
+ static async updateConfigJson(
88
+ configJSON: object,
89
+ key: string,
90
+ value: string,
91
+ ) {
92
+ const _ = await import('lodash');
89
93
  // note: _.setWith() is needed instead of _.set() because, given a key
90
94
  // like `os.udevRules.101`, _.set() creates a udevRules array (rather
91
95
  // than a dictionary) and sets the 101st array element to value, while
@@ -319,7 +319,7 @@ ${dockerignoreHelp}
319
319
 
320
320
  let release: Pick<Release['Read'], 'commit' | 'id'>;
321
321
  if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
322
- const { deployLegacy } = require('../../utils/deploy-legacy');
322
+ const { deployLegacy } = await import('../../utils/deploy-legacy');
323
323
 
324
324
  const ux = getCliUx();
325
325
  const msg = ux.colorize(
@@ -127,7 +127,7 @@ export default class DeviceSSHCmd extends Command {
127
127
  } = await import('../../utils/patterns');
128
128
  const sdk = getBalenaSdk();
129
129
 
130
- const proxyConfig = getProxyConfig();
130
+ const proxyConfig = await getProxyConfig();
131
131
  const useProxy = !!proxyConfig && !options.noproxy;
132
132
 
133
133
  // this will be a tunnelled SSH connection...
@@ -205,14 +205,13 @@ or release date not available`);
205
205
  const { ExpectedError } = await import('./errors');
206
206
  throw new ExpectedError(this.getExpiryMsg(daysElapsed));
207
207
  } else if (daysElapsed > this.deprecationDays && process.stderr.isTTY) {
208
- console.error(this.getDeprecationMsg(daysElapsed));
208
+ console.error(await this.getDeprecationMsg(daysElapsed));
209
209
  }
210
210
  }
211
211
 
212
212
  /** Separate function for the benefit of code testing */
213
- getDeprecationMsg(daysElapsed: number) {
214
- const { warnify } =
215
- require('./utils/messages') as typeof import('./utils/messages');
213
+ async getDeprecationMsg(daysElapsed: number) {
214
+ const { warnify } = await import('./utils/messages');
216
215
  return warnify(`\
217
216
  CLI version ${this.nextMajorVersion} was released ${daysElapsed} days ago: please upgrade.
218
217
  This version of the balena CLI (${this.currentVersion}) will exit with an error
package/src/help.ts CHANGED
@@ -52,7 +52,7 @@ export default class BalenaHelp extends Help {
52
52
  const subject = getHelpSubject(argv);
53
53
  if (!subject) {
54
54
  const verbose = argv.includes('-v') || argv.includes('--verbose');
55
- console.log(this.getCustomRootHelp(verbose));
55
+ console.log(await this.getCustomRootHelp(verbose));
56
56
  return;
57
57
  }
58
58
 
@@ -89,7 +89,7 @@ export default class BalenaHelp extends Help {
89
89
  );
90
90
  }
91
91
 
92
- getCustomRootHelp(showAllCommands: boolean): string {
92
+ async getCustomRootHelp(showAllCommands: boolean): Promise<string> {
93
93
  const ux = getCliUx();
94
94
  let commands = this.config.commands;
95
95
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
@@ -155,8 +155,9 @@ See: https://git.io/JRHUW#deprecation-policy`,
155
155
  ];
156
156
  globalOps[0][0] = globalOps[0][0].padEnd(cmdLength);
157
157
 
158
- const { deprecationPolicyNote, reachingOut } =
159
- require('./utils/messages') as typeof import('./utils/messages');
158
+ const { deprecationPolicyNote, reachingOut } = await import(
159
+ './utils/messages'
160
+ );
160
161
 
161
162
  return [
162
163
  ux.colorize('bold', 'USAGE'),
package/src/preparser.ts CHANGED
@@ -104,8 +104,8 @@ function extractBooleanFlag(argv: string[], flag: string): boolean {
104
104
  * Check whether the command line refers to a command that has been deprecated
105
105
  * and removed and, if so, exit with an informative error message.
106
106
  */
107
- export function checkDeletedCommand(argvSlice: string[]): void {
108
- const { ExpectedError } = require('./errors') as typeof import('./errors');
107
+ export async function checkDeletedCommand(argvSlice: string[]): Promise<void> {
108
+ const { ExpectedError } = await import('./errors');
109
109
 
110
110
  if (argvSlice[0] === 'help') {
111
111
  argvSlice = argvSlice.slice(1);
@@ -17,7 +17,7 @@
17
17
 
18
18
  import type { Renderer } from './compose_ts';
19
19
  import type * as SDK from 'balena-sdk';
20
- import type Dockerode = require('dockerode');
20
+ import type * as Dockerode from 'dockerode';
21
21
  import * as path from 'path';
22
22
  import type { Composition, ImageDescriptor } from '@balena/compose/dist/parse';
23
23
  import type { RetryParametersObj } from 'pinejs-client-core';
@@ -570,7 +570,7 @@ async function inspectBuiltImages({
570
570
  ),
571
571
  );
572
572
 
573
- const humanize = require('humanize');
573
+ const humanize = await import('humanize');
574
574
  const summaryMsgByService: { [serviceName: string]: string } = {};
575
575
  for (const image of images) {
576
576
  summaryMsgByService[image.serviceName] = `Image size: ${humanize.filesize(
@@ -775,7 +775,11 @@ export async function tarDirectory(
775
775
  const serviceDirs = await getServiceDirsFromComposition(dir, composition);
776
776
  const { filteredFileList, dockerignoreFiles } =
777
777
  await filterFilesWithDockerignore(dir, multiDockerignore, serviceDirs);
778
- printDockerignoreWarn(dockerignoreFiles, serviceDirs, multiDockerignore);
778
+ await printDockerignoreWarn(
779
+ dockerignoreFiles,
780
+ serviceDirs,
781
+ multiDockerignore,
782
+ );
779
783
  void (async () => {
780
784
  try {
781
785
  for (const fileStats of filteredFileList) {
@@ -826,7 +830,7 @@ export async function tarDirectory(
826
830
  * @param serviceDirsByService Map of service names to service subdirectories
827
831
  * @param multiDockerignore Whether --multi-dockerignore (-m) was provided
828
832
  */
829
- function printDockerignoreWarn(
833
+ async function printDockerignoreWarn(
830
834
  dockerignoreFiles: Array<import('./ignore').FileStats>,
831
835
  serviceDirsByService: Dictionary<string>,
832
836
  multiDockerignore: boolean,
@@ -900,7 +904,7 @@ function printDockerignoreWarn(
900
904
  }
901
905
  }
902
906
  if (msg.length) {
903
- const { warnify } = require('./messages') as typeof import('./messages');
907
+ const { warnify } = await import('./messages');
904
908
  logFunc.call(logger, ' \n' + warnify(msg.join('\n'), ''));
905
909
  }
906
910
  }
@@ -21,23 +21,23 @@ import type * as Dockerode from 'dockerode';
21
21
  import type Logger = require('./logger');
22
22
  import type got from 'got';
23
23
 
24
- const getBuilderPushEndpoint = function (
24
+ const getBuilderPushEndpoint = async function (
25
25
  baseUrl: string,
26
26
  owner: string,
27
27
  app: string,
28
28
  ) {
29
- const querystring = require('querystring') as typeof import('querystring');
29
+ const querystring = await import('querystring');
30
30
  const args = querystring.stringify({ owner, app });
31
31
  return `https://builder.${baseUrl}/v1/push?${args}`;
32
32
  };
33
33
 
34
- const getBuilderLogPushEndpoint = function (
34
+ const getBuilderLogPushEndpoint = async function (
35
35
  baseUrl: string,
36
36
  buildId: number,
37
37
  owner: string,
38
38
  app: string,
39
39
  ) {
40
- const querystring = require('querystring') as typeof import('querystring');
40
+ const querystring = await import('querystring');
41
41
  const args = querystring.stringify({ owner, app, buildId });
42
42
  return `https://builder.${baseUrl}/v1/pushLogs?${args}`;
43
43
  };
@@ -47,12 +47,12 @@ const getBuilderLogPushEndpoint = function (
47
47
  * @param {string} imageId
48
48
  * @param {string} bufferFile
49
49
  */
50
- const bufferImage = function (
50
+ const bufferImage = async function (
51
51
  docker: Dockerode,
52
52
  imageId: string,
53
53
  bufferFile: string,
54
54
  ): Promise<NodeJS.ReadableStream & { length: number }> {
55
- const streamUtils = require('./streams') as typeof import('./streams');
55
+ const streamUtils = await import('./streams');
56
56
 
57
57
  const image = docker.getImage(imageId);
58
58
  const sizePromise = image.inspect().then((img) => img.Size);
@@ -141,7 +141,7 @@ const uploadImage = async function (
141
141
  );
142
142
 
143
143
  const uploadRequest = got.stream.post(
144
- getBuilderPushEndpoint(url, username, appName),
144
+ await getBuilderPushEndpoint(url, username, appName),
145
145
  {
146
146
  headers: {
147
147
  'Content-Encoding': 'gzip',
@@ -168,14 +168,17 @@ const uploadLogs = async function (
168
168
  appName: string,
169
169
  ) {
170
170
  const { default: got } = await import('got');
171
- return got.post(getBuilderLogPushEndpoint(url, buildId, username, appName), {
172
- headers: {
173
- Authorization: `Bearer ${token}`,
171
+ return got.post(
172
+ await getBuilderLogPushEndpoint(url, buildId, username, appName),
173
+ {
174
+ headers: {
175
+ Authorization: `Bearer ${token}`,
176
+ },
177
+ body: Buffer.from(logs),
178
+ responseType: 'json',
179
+ throwHttpErrors: false,
174
180
  },
175
- body: Buffer.from(logs),
176
- responseType: 'json',
177
- throwHttpErrors: false,
178
- });
181
+ );
179
182
  };
180
183
 
181
184
  /**
@@ -196,7 +199,8 @@ export const deployLegacy = async function (
196
199
  shouldUploadLogs: boolean;
197
200
  },
198
201
  ): Promise<number> {
199
- const tmp = require('tmp') as typeof import('tmp');
202
+ const tmp = await import('tmp');
203
+ const { promises: fs } = await import('fs');
200
204
  const tmpNameAsync = promisify(tmp.tmpName);
201
205
 
202
206
  // Ensure the tmp files gets deleted
@@ -216,10 +220,7 @@ export const deployLegacy = async function (
216
220
  // If the file was never written to (for instance because an error
217
221
  // has occured before any data was written) this call will throw an
218
222
  // ugly error, just suppress it
219
-
220
- (require('fs') as typeof import('fs')).promises
221
- .unlink(bufferFile)
222
- .catch(() => undefined),
223
+ fs.unlink(bufferFile).catch(() => undefined),
223
224
  );
224
225
 
225
226
  if (shouldUploadLogs) {
@@ -90,8 +90,7 @@ export async function sudo(
90
90
  }
91
91
 
92
92
  export async function runCommand<T>(commandArgs: string[]): Promise<T> {
93
- const { isSubcommand } =
94
- require('../preparser') as typeof import('../preparser');
93
+ const { isSubcommand } = await import('../preparser');
95
94
  if (await isSubcommand(commandArgs)) {
96
95
  commandArgs = [
97
96
  commandArgs[0] + ':' + commandArgs[1],
@@ -99,7 +98,7 @@ export async function runCommand<T>(commandArgs: string[]): Promise<T> {
99
98
  ];
100
99
  }
101
100
 
102
- const { run } = require('@oclif/core') as typeof import('@oclif/core');
101
+ const { run } = await import('@oclif/core');
103
102
  return run(commandArgs) as Promise<T>;
104
103
  }
105
104
 
@@ -323,14 +322,17 @@ export function isWindowsComExeShell() {
323
322
  * functions, set this to false because child_process.spawn() always uses
324
323
  * env.ComSpec (cmd.exe) on Windows, even when running on MSYS / MSYS2.
325
324
  */
326
- export function shellEscape(args: string[], detectShell = false): string[] {
325
+ export async function shellEscape(
326
+ args: string[],
327
+ detectShell = false,
328
+ ): Promise<string[]> {
327
329
  const isCmdExe = detectShell
328
330
  ? isWindowsComExeShell()
329
331
  : process.platform === 'win32';
330
332
  if (isCmdExe) {
331
333
  return args.map((v) => windowsCmdExeEscapeArg(v));
332
334
  } else {
333
- const shellEscapeFunc: typeof import('shell-escape') = require('shell-escape');
335
+ const shellEscapeFunc = await import('shell-escape');
334
336
  return args.map((v) => shellEscapeFunc([v]));
335
337
  }
336
338
  }
@@ -364,7 +366,7 @@ export interface ProxyConfig {
364
366
  * Check whether a proxy has been configured (whether global-tunnel-ng or
365
367
  * global-agent) and if so, return a ProxyConfig object.
366
368
  */
367
- export function getProxyConfig(): ProxyConfig | undefined {
369
+ export async function getProxyConfig(): Promise<ProxyConfig | undefined> {
368
370
  const tunnelNgConfig: any = (global as any).PROXY_CONFIG;
369
371
  // global-tunnel-ng
370
372
  if (tunnelNgConfig) {
@@ -390,7 +392,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
390
392
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
391
393
  const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
392
394
  if (proxyUrl) {
393
- const { URL } = require('url') as typeof import('url');
395
+ const { URL } = await import('url');
394
396
  let url: InstanceType<typeof URL>;
395
397
  try {
396
398
  url = new URL(proxyUrl);
package/src/utils/qemu.ts CHANGED
@@ -16,8 +16,9 @@
16
16
  */
17
17
 
18
18
  import type * as Dockerode from 'dockerode';
19
-
20
19
  import { ExpectedError } from '../errors';
20
+ import * as path from 'path';
21
+ import * as fs from 'fs';
21
22
  import { getBalenaSdk, stripIndent } from './lazy';
22
23
  import Logger = require('./logger');
23
24
 
@@ -25,15 +26,12 @@ export const QEMU_VERSION = 'v7.0.0+balena1';
25
26
  export const QEMU_BIN_NAME = 'qemu-execve';
26
27
 
27
28
  export function qemuPathInContext(context: string) {
28
- const path = require('path') as typeof import('path');
29
29
  const binDir = path.join(context, '.balena');
30
30
  const binPath = path.join(binDir, QEMU_BIN_NAME);
31
31
  return path.relative(context, binPath);
32
32
  }
33
33
 
34
34
  export function copyQemu(context: string, arch: string) {
35
- const path = require('path') as typeof import('path');
36
- const fs = require('fs') as typeof import('fs');
37
35
  // Create a hidden directory in the build context, containing qemu
38
36
  const binDir = path.join(context, '.balena');
39
37
  const binPath = path.join(binDir, QEMU_BIN_NAME);
@@ -68,11 +66,9 @@ export function copyQemu(context: string, arch: string) {
68
66
  export const getQemuPath = function (balenaArch: string) {
69
67
  const qemuArch = balenaArchToQemuArch(balenaArch);
70
68
  const balena = getBalenaSdk();
71
- const path = require('path') as typeof import('path');
72
- const { promises: fs } = require('fs') as typeof import('fs');
73
69
 
74
70
  return balena.settings.get('binDirectory').then((binDir) =>
75
- fs
71
+ fs.promises
76
72
  .mkdir(binDir)
77
73
  .catch(function (err) {
78
74
  if (err.code === 'EEXIST') {
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import fs = require('fs');
17
+ import * as fs from 'fs';
18
18
 
19
19
  export function buffer(
20
20
  stream: NodeJS.ReadableStream,
package/src/utils/sudo.ts CHANGED
@@ -56,7 +56,7 @@ export async function executeWithPrivileges(
56
56
  await spawnAndPipe(command, opts, stderr);
57
57
  } else {
58
58
  opts.shell = true;
59
- const escapedCmd = shellEscape(command);
59
+ const escapedCmd = await shellEscape(command);
60
60
  // running as ordinary user: elevate privileges
61
61
  if (process.platform === 'win32') {
62
62
  await windosuExec(escapedCmd, stderr);
@@ -40,7 +40,7 @@ export async function umount(device: string): Promise<void> {
40
40
  }
41
41
  const { sanitizePath, whichBin } = await import('./which');
42
42
  // sanitize user's input (regular expression attacks ?)
43
- device = sanitizePath(device);
43
+ device = await sanitizePath(device);
44
44
  const cmd: string[] = [];
45
45
 
46
46
  if (process.platform === 'darwin') {
@@ -34,8 +34,8 @@ export async function exists(filename: string, mode = F_OK) {
34
34
  * and '.' or '..' with an underscore, plus other rules enforced by the filenamify
35
35
  * package. See https://github.com/sindresorhus/filenamify/
36
36
  */
37
- export function sanitizePath(filepath: string) {
38
- const filenamify = require('filenamify') as typeof import('filenamify');
37
+ export async function sanitizePath(filepath: string) {
38
+ const filenamify = await import('filenamify');
39
39
  // normalize also converts forward slash to backslash on Windows
40
40
  return path
41
41
  .normalize(filepath)