diff --git a/lib/app.ts b/lib/app.ts index 9eb2992c..9681f028 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -15,17 +15,63 @@ * limitations under the License. */ -import { globalInit } from './app-common'; -import { AppOptions, routeCliFramework } from './preparser'; - /** * CLI entrypoint, but see also `bin/balena` and `bin/balena-dev` which * call this function. */ -export async function run(cliArgs = process.argv, options: AppOptions = {}) { +export async function run( + cliArgs = process.argv, + options: import('./preparser').AppOptions = {}, +) { + // The 'pkgExec' special/internal command provides a Node.js interpreter + // for use of the standalone zip package. See pkgExec function. + if (cliArgs.length > 3 && cliArgs[2] === 'pkgExec') { + return pkgExec(cliArgs[3], cliArgs.slice(4)); + } + + const { globalInit } = await import('./app-common'); + const { routeCliFramework } = await import('./preparser'); + // globalInit() must be called very early on (before other imports) because // it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk // shared options, and performs node version requirement checks. globalInit(); await routeCliFramework(cliArgs, options); } + +/** + * Implements the 'pkgExec' command, used as a way to provide a Node.js + * interpreter for child_process.spawn()-like operations when the CLI is + * executing as a standalone zip package (built-in Node interpreter) and + * the system may not have a separate Node.js installation. A present use + * case is a patched version of the 'windosu' package that requires a + * Node.js interpreter to spawn a privileged child process. + * + * @param modFunc Path to a JS module that will be executed via require(). + * The modFunc argument may optionally contain a function name separated + * by '::', for example '::main' in: + * 'C:\\snapshot\\balena-cli\\node_modules\\windosu\\lib\\pipe.js::main' + * in which case that function is executed in the require'd module. + * @param args Optional arguments to passed through process.argv and as + * arguments to the function specified via modFunc. + */ +async function pkgExec(modFunc: string, args: string[]) { + const [modPath, funcName] = modFunc.split('::'); + let replacedModPath = modPath; + const match = modPath + .replace(/\\/g, '/') + .match(/\/snapshot\/balena-cli\/(.+)/); + if (match) { + replacedModPath = `../${match[1]}`; + } + process.argv = [process.argv[0], process.argv[1], ...args]; + try { + const mod: any = await import(replacedModPath); + if (funcName) { + await mod[funcName](...args); + } + } catch (err) { + console.error(`Error executing pkgExec "${modFunc}" [${args.join()}]`); + console.error(err); + } +} diff --git a/patches/windosu+0.3.0.patch b/patches/windosu+0.3.0.patch new file mode 100644 index 00000000..d55d5ab7 --- /dev/null +++ b/patches/windosu+0.3.0.patch @@ -0,0 +1,38 @@ +diff --git a/node_modules/windosu/lib/pipe.js b/node_modules/windosu/lib/pipe.js +index dc81fa5..a381cc7 100644 +--- a/node_modules/windosu/lib/pipe.js ++++ b/node_modules/windosu/lib/pipe.js +@@ -42,7 +42,8 @@ function pipe(path, options) { + return d.promise; + } + module.exports = pipe; +-if (module === require.main) { ++ ++function main() { + if (!process.argv[4]) { + console.error('Incorrect arguments!'); + process.exit(-1); +@@ -52,3 +53,8 @@ if (module === require.main) { + serve: process.argv[3] == 'server' + }); + } ++module.exports.main = main; ++ ++if (module === require.main) { ++ main(); ++} +diff --git a/node_modules/windosu/lib/windosu.js b/node_modules/windosu/lib/windosu.js +index 6502812..dd0391a 100644 +--- a/node_modules/windosu/lib/windosu.js ++++ b/node_modules/windosu/lib/windosu.js +@@ -16,7 +16,9 @@ module.exports.exec = function (command, options, callback) { + temp: temp, + command: command, + cliWidth: cliWidth(), +- pipe: '"' + process.execPath + '" "' + path.join(__dirname, 'pipe.js') + '"', ++ pipe: process.pkg ++ ? '"' + process.execPath + '" pkgExec "' + path.join(__dirname, 'pipe.js') + '::main"' ++ : '"' + process.execPath + '" "' + path.join(__dirname, 'pipe.js') + '"', + input: inputName = id + '-in', + output: outputName = id + '-out', + stderr_redir: process.stdout.isTTY ? '2>&1' : '2> %ERROR%'