diff --git a/bin/balena b/bin/balena deleted file mode 100755 index 6a27aff0..00000000 --- a/bin/balena +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env node - -// We boost the threadpool size as ext2fs can deadlock with some -// operations otherwise, if the pool runs out. -process.env.UV_THREADPOOL_SIZE = '64'; - -// Disable oclif registering ts-node -process.env.OCLIF_TS_NODE = 0; - -async function run() { - // Use fast-boot to cache require lookups, speeding up startup - await require('../build/fast-boot').start(); - - // Set the desired es version for downstream modules that support it - require('@balena/es-version').set('es2018'); - - // Run the CLI - await require('../build/app').run(undefined, { dir: __dirname }); -} - -run(); diff --git a/bin/balena b/bin/balena new file mode 120000 index 00000000..af43f663 --- /dev/null +++ b/bin/balena @@ -0,0 +1 @@ +run.js \ No newline at end of file diff --git a/bin/balena-dev b/bin/balena-dev deleted file mode 100755 index ceaf47c4..00000000 --- a/bin/balena-dev +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env node - -// **************************************************************************** -// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE -// Before opening a PR you should build and test your changes using bin/balena -// **************************************************************************** - -// We boost the threadpool size as ext2fs can deadlock with some -// operations otherwise, if the pool runs out. -process.env.UV_THREADPOOL_SIZE = '64'; - -// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because: -// * fast-boot2's cacheKiller option is configured to include the timestamps of -// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI -// behavior when changes are made to dependencies during development. This is -// generally a good thing, however, `balena-dev` (a few lines below) edits -// `package.json` to modify oclif paths, and this results in cache -// invalidation and a performance hit rather than speedup. -// * Even if the timestamps are removed from cacheKiller, so that there is no -// cache invalidation, fast-boot's speedup is barely noticeable when ts-node -// is used, e.g. 1.43s vs 1.4s when running `balena version`. -// * `fast-boot` causes unexpected behavior when used with `npm link` or -// when the `node_modules` folder is manually modified (affecting transitive -// dependencies) during development (e.g. bug investigations). A workaround -// is to use `balena-dev` without `fast-boot`. See also notes in -// `CONTRIBUTING.md`. - -const path = require('path'); -const rootDir = path.join(__dirname, '..'); - -// Allow balena-dev to work with oclif by temporarily -// pointing oclif config options to lib/ instead of build/ -modifyOclifPaths(); -// Undo changes on exit -process.on('exit', function () { - modifyOclifPaths(true); -}); -// Undo changes in case of ctrl-c -process.on('SIGINT', function () { - modifyOclifPaths(true); - // Note process exit here will interfere with commands that do their own SIGINT handling, - // but without it commands can not be exited. - // So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c. - // Ideally a better solution is needed. - process.exit(); -}); - -// Set the desired es version for downstream modules that support it -require('@balena/es-version').set('es2018'); - -// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the -// default option. We upgraded ts-node and found that adding 'transpile-only' -// was necessary to avoid a mysterious 'null' error message. On the plus side, -// it is supposed to run faster. We still benefit from type checking when -// running 'npm run build'. -require('ts-node').register({ - project: path.join(rootDir, 'tsconfig.json'), - transpileOnly: true, -}); -require('../lib/app').run(undefined, { dir: __dirname, development: true }); - -// Modify package.json oclif paths from build/ -> lib/, or vice versa -function modifyOclifPaths(revert) { - const fs = require('fs'); - const packageJsonPath = path.join(rootDir, 'package.json'); - - const packageJson = fs.readFileSync(packageJsonPath, 'utf8'); - const packageObj = JSON.parse(packageJson); - - if (!packageObj.oclif) { - return; - } - - let oclifSectionText = JSON.stringify(packageObj.oclif); - if (!revert) { - oclifSectionText = oclifSectionText.replace(/\/build\//g, '/lib/'); - } else { - oclifSectionText = oclifSectionText.replace(/\/lib\//g, '/build/'); - } - - packageObj.oclif = JSON.parse(oclifSectionText); - fs.writeFileSync( - packageJsonPath, - `${JSON.stringify(packageObj, null, 2)}\n`, - 'utf8', - ); -} diff --git a/bin/balena-dev b/bin/balena-dev new file mode 120000 index 00000000..dfbc145e --- /dev/null +++ b/bin/balena-dev @@ -0,0 +1 @@ +dev.js \ No newline at end of file diff --git a/bin/dev.cmd b/bin/dev.cmd new file mode 100644 index 00000000..968fc307 --- /dev/null +++ b/bin/dev.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/bin/dev.js b/bin/dev.js new file mode 100644 index 00000000..ceaf47c4 --- /dev/null +++ b/bin/dev.js @@ -0,0 +1,87 @@ +#!/usr/bin/env node + +// **************************************************************************** +// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE +// Before opening a PR you should build and test your changes using bin/balena +// **************************************************************************** + +// We boost the threadpool size as ext2fs can deadlock with some +// operations otherwise, if the pool runs out. +process.env.UV_THREADPOOL_SIZE = '64'; + +// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because: +// * fast-boot2's cacheKiller option is configured to include the timestamps of +// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI +// behavior when changes are made to dependencies during development. This is +// generally a good thing, however, `balena-dev` (a few lines below) edits +// `package.json` to modify oclif paths, and this results in cache +// invalidation and a performance hit rather than speedup. +// * Even if the timestamps are removed from cacheKiller, so that there is no +// cache invalidation, fast-boot's speedup is barely noticeable when ts-node +// is used, e.g. 1.43s vs 1.4s when running `balena version`. +// * `fast-boot` causes unexpected behavior when used with `npm link` or +// when the `node_modules` folder is manually modified (affecting transitive +// dependencies) during development (e.g. bug investigations). A workaround +// is to use `balena-dev` without `fast-boot`. See also notes in +// `CONTRIBUTING.md`. + +const path = require('path'); +const rootDir = path.join(__dirname, '..'); + +// Allow balena-dev to work with oclif by temporarily +// pointing oclif config options to lib/ instead of build/ +modifyOclifPaths(); +// Undo changes on exit +process.on('exit', function () { + modifyOclifPaths(true); +}); +// Undo changes in case of ctrl-c +process.on('SIGINT', function () { + modifyOclifPaths(true); + // Note process exit here will interfere with commands that do their own SIGINT handling, + // but without it commands can not be exited. + // So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c. + // Ideally a better solution is needed. + process.exit(); +}); + +// Set the desired es version for downstream modules that support it +require('@balena/es-version').set('es2018'); + +// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the +// default option. We upgraded ts-node and found that adding 'transpile-only' +// was necessary to avoid a mysterious 'null' error message. On the plus side, +// it is supposed to run faster. We still benefit from type checking when +// running 'npm run build'. +require('ts-node').register({ + project: path.join(rootDir, 'tsconfig.json'), + transpileOnly: true, +}); +require('../lib/app').run(undefined, { dir: __dirname, development: true }); + +// Modify package.json oclif paths from build/ -> lib/, or vice versa +function modifyOclifPaths(revert) { + const fs = require('fs'); + const packageJsonPath = path.join(rootDir, 'package.json'); + + const packageJson = fs.readFileSync(packageJsonPath, 'utf8'); + const packageObj = JSON.parse(packageJson); + + if (!packageObj.oclif) { + return; + } + + let oclifSectionText = JSON.stringify(packageObj.oclif); + if (!revert) { + oclifSectionText = oclifSectionText.replace(/\/build\//g, '/lib/'); + } else { + oclifSectionText = oclifSectionText.replace(/\/lib\//g, '/build/'); + } + + packageObj.oclif = JSON.parse(oclifSectionText); + fs.writeFileSync( + packageJsonPath, + `${JSON.stringify(packageObj, null, 2)}\n`, + 'utf8', + ); +} diff --git a/bin/run.cmd b/bin/run.cmd new file mode 100644 index 00000000..968fc307 --- /dev/null +++ b/bin/run.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\run" %* diff --git a/bin/run.js b/bin/run.js new file mode 100644 index 00000000..6a27aff0 --- /dev/null +++ b/bin/run.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +// We boost the threadpool size as ext2fs can deadlock with some +// operations otherwise, if the pool runs out. +process.env.UV_THREADPOOL_SIZE = '64'; + +// Disable oclif registering ts-node +process.env.OCLIF_TS_NODE = 0; + +async function run() { + // Use fast-boot to cache require lookups, speeding up startup + await require('../build/fast-boot').start(); + + // Set the desired es version for downstream modules that support it + require('@balena/es-version').set('es2018'); + + // Run the CLI + await require('../build/app').run(undefined, { dir: __dirname }); +} + +run(); diff --git a/lib/app.ts b/lib/app.ts index 4794bd51..3c9df20d 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -157,7 +157,7 @@ async function oclifRun(command: string[], options: AppOptions) { await Promise.all([trackPromise, deprecationPromise, runPromise]); } -/** CLI entrypoint. Called by the `bin/balena` and `bin/balena-dev` scripts. */ +/** CLI entrypoint. Called by the `bin/run.js` and `bin/dev.js` scripts. */ export async function run(cliArgs = process.argv, options: AppOptions) { try { const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import( diff --git a/lib/fast-boot.ts b/lib/fast-boot.ts index bc842340..d66dd499 100644 --- a/lib/fast-boot.ts +++ b/lib/fast-boot.ts @@ -20,7 +20,7 @@ * we have permissions over the cache file before even attempting to load * fast boot. * DON'T IMPORT BALENA-CLI MODULES HERE, as this module is loaded directly - * from `bin/balena`, before the CLI's entrypoint in `lib/app.ts`. + * from `bin/run.js`, before the CLI's entrypoint in `lib/app.ts`. */ import * as fs from 'fs'; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 214847b3..33992501 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -98,7 +98,7 @@ "window-size": "^1.1.0" }, "bin": { - "balena": "bin/balena" + "balena": "bin/run.js" }, "devDependencies": { "@balena/lint": "^7.2.1", @@ -4030,9 +4030,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz", - "integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==", + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", "dependencies": { "undici-types": "~5.26.4" } @@ -6098,9 +6098,9 @@ } }, "node_modules/balena-sdk/node_modules/@types/node": { - "version": "18.19.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.23.tgz", - "integrity": "sha512-wtE3d0OUfNKtZYAqZb8HAWGxxXsImJcPUAgZNw+dWFxO6s5tIwIjyKnY76tsTatsNCLJPkVYwUpq15D38ng9Aw==", + "version": "18.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.24.tgz", + "integrity": "sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==", "dependencies": { "undici-types": "~5.26.4" } @@ -28545,9 +28545,9 @@ } }, "@types/node": { - "version": "20.11.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.26.tgz", - "integrity": "sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==", + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", "requires": { "undici-types": "~5.26.4" } @@ -30152,9 +30152,9 @@ }, "dependencies": { "@types/node": { - "version": "18.19.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.23.tgz", - "integrity": "sha512-wtE3d0OUfNKtZYAqZb8HAWGxxXsImJcPUAgZNw+dWFxO6s5tIwIjyKnY76tsTatsNCLJPkVYwUpq15D38ng9Aw==", + "version": "18.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.24.tgz", + "integrity": "sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==", "requires": { "undici-types": "~5.26.4" } diff --git a/package.json b/package.json index 104fa0c6..8c8ee347 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "oclif.manifest.json" ], "bin": { - "balena": "./bin/balena" + "balena": "./bin/run.js" }, "pkg": { "scripts": [ @@ -75,7 +75,7 @@ "ci": "npm run test && npm run catch-uncommitted", "lint": "npm run lint-tsconfig && npm run lint-other", "lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ lib/ tests/ typings/", - "lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ .mocharc.js .mocharc-standalone.js", + "lint-other": "balena-lint -e ts -e js --fix bin/run.js bin/dev.js completion/ .mocharc.js .mocharc-standalone.js", "update": "ts-node --transpile-only ./automation/update-module.ts", "prepare": "echo {} > bin/.fast-boot.json", "prepublishOnly": "npm run build" diff --git a/patches/all/oclif+3.17.2.dev.patch b/patches/all/oclif+3.17.2.dev.patch index 1d380bfd..9ffeed30 100644 --- a/patches/all/oclif+3.17.2.dev.patch +++ b/patches/all/oclif+3.17.2.dev.patch @@ -16,7 +16,7 @@ index c0926bd..e4f645c 100644 File /r bin File /r client diff --git a/node_modules/oclif/lib/tarballs/build.js b/node_modules/oclif/lib/tarballs/build.js -index 384ea4b..4d6593a 100644 +index 384ea4b..c9607f3 100644 --- a/node_modules/oclif/lib/tarballs/build.js +++ b/node_modules/oclif/lib/tarballs/build.js @@ -30,7 +30,9 @@ async function build(c, options = {}) { @@ -30,12 +30,10 @@ index 384ea4b..4d6593a 100644 await Promise.all((await fs.promises.readdir(path.join(c.workspace(), 'package'), { withFileTypes: true })) .map(i => fs.move(path.join(c.workspace(), 'package', i.name), path.join(c.workspace(), i.name)))); await Promise.all([ -@@ -38,6 +40,13 @@ async function build(c, options = {}) { +@@ -38,6 +40,11 @@ async function build(c, options = {}) { fs.promises.rm(path.join(c.workspace(), path.basename(tarball)), { recursive: true }), fs.remove(path.join(c.workspace(), 'bin', 'run.cmd')), ]); -+ // rename the original balena-cli ./bin/balena entry point for oclif compatibility -+ await fs.move(path.join(c.workspace(), 'bin', 'balena'), path.join(c.workspace(), 'bin', 'run')); + // The oclif installers are a production installation, while the source + // `bin` folder may contain a `.fast-boot.json` file of a dev installation. + // This has previously led to issues preventing the CLI from starting, so