UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

195 lines (194 loc) • 8.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PseudoTtyProcess = void 0; exports.getRunNxBaseCommand = getRunNxBaseCommand; exports.getNxBin = getNxBin; exports.readInstalledNxBin = readInstalledNxBin; exports.runNxArgvSync = runNxArgvSync; exports.runNxSync = runNxSync; exports.runNxAsync = runNxAsync; const child_process_1 = require("child_process"); const fs_1 = require("fs"); const path_1 = require("path"); const package_manager_1 = require("./package-manager"); const workspace_root_1 = require("./workspace-root"); const exit_codes_1 = require("./exit-codes"); const fileutils_1 = require("./fileutils"); const shell_quoting_1 = require("./shell-quoting"); function getRunNxBaseCommand(packageManagerCommand, cwd = process.cwd()) { if ((0, fs_1.existsSync)((0, path_1.join)(workspace_root_1.workspaceRoot, 'package.json'))) { if (!packageManagerCommand) { // `readLocalNxVersion` (command-line/migrate/migrate.ts) mirrors this // selector to predict which nx a spawn will run, for the workspaces // `getNxBin` declines to resolve; keep the two in sync. const pm = (0, package_manager_1.detectPackageManager)(workspace_root_1.workspaceRoot); packageManagerCommand = (0, package_manager_1.getPackageManagerCommand)(pm, workspace_root_1.workspaceRoot); } return `${packageManagerCommand.exec} nx`; } else { const offsetFromRoot = (0, path_1.relative)(cwd, (0, workspace_root_1.workspaceRootInner)(cwd, null)); if (process.platform === 'win32') { return '.\\' + (0, path_1.join)(`${offsetFromRoot}`, 'nx.bat'); } else { return './' + (0, path_1.join)(`${offsetFromRoot}`, 'nx'); } } } /** * Locate an nx entry point to spawn for the workspace at `root`, so a caller * can run it directly instead of going through a shell. `findInstalledNxBin` * decides which one. * * Null means nothing may be spawned directly, leaving the caller to fall back * to `getRunNxBaseCommand`. Null is therefore always safe: it costs the * argument fidelity a direct spawn buys, never the ability to run. */ function getNxBin(root = workspace_root_1.workspaceRoot) { // A workspace with no root package.json runs nx through the `./nx` wrapper, // which reinstalls `.nx/installation` whenever it drifts from nx.json's // `installation.version`. Spawning the resolved entry point would skip that // sync, and a migration is precisely when the version changes. if (!(0, fs_1.existsSync)((0, path_1.join)(root, 'package.json'))) { return null; } return findInstalledNxBin(root); } /** * The entry point the nx installed directly under `dir` names, with no ascent * to `dir`'s ancestors. For an installation that declares nx itself, such as * the temp CLI `nx migrate` builds, an ancestor's nx is never the right answer. */ function readInstalledNxBin(dir) { const packageDir = (0, path_1.join)(dir, 'node_modules', 'nx'); const manifest = (0, path_1.join)(packageDir, 'package.json'); if (!(0, fs_1.existsSync)(manifest)) { return null; } let bin; try { ({ bin } = (0, fileutils_1.readJsonFile)(manifest)); } catch { return null; } // npm accepts both the single-entry shorthand and the map form. const entry = typeof bin === 'string' ? bin : bin?.nx; return typeof entry === 'string' ? (0, path_1.join)(packageDir, entry) : null; } // Ascend to the nearest installed nx and take the entry point its `bin` field // names, which is the file a package manager links into `node_modules/.bin`. // Deliberately npx-shaped: npx and bun ascend unconditionally while pnpm and // yarn stop at an outer workspace, so this can name an nx those two would // decline to run. // // Deliberately not a resolver. Resolvers answer from NODE_PATH once their // explicit paths miss, and `nxCliPath` (command-line/migrate/migrate.ts) points // NODE_PATH at the temp installation before spawning it; they also answer // through package self-reference, which hands back the running nx whatever // `paths` they are given. Either one lets the temp installation hand off to // itself, which for `--run-migrations` re-enters the same hand-off and respawns // without end. function findInstalledNxBin(root) { for (let dir = root;; dir = (0, path_1.dirname)(dir)) { // The nearest install wins, so an unusable manifest there ends the search // rather than deferring to an ancestor. if ((0, fs_1.existsSync)((0, path_1.join)(dir, 'node_modules', 'nx', 'package.json'))) { return readInstalledNxBin(dir); } if (dir === (0, path_1.dirname)(dir)) { return null; } } } /** * Run a nx command, passing the arguments through as an argv array. * * When `getNxBin` names an entry point, the child is spawned directly with no * shell in between, so every argument reaches the child exactly as provided: * shell metacharacters (`(`, `%`, `^`, spaces, quotes) are data, not syntax. * Otherwise falls back to the package-manager + shell path, where every * argument goes through `quoteShellArg` and the Windows limits it documents * apply. */ function runNxArgvSync(argv, options) { let { nxBin, ...spawnOptions } = options ?? {}; spawnOptions.cwd ??= process.cwd(); spawnOptions.windowsHide ??= true; nxBin ??= getNxBin((0, workspace_root_1.workspaceRootInner)(spawnOptions.cwd, null) ?? workspace_root_1.workspaceRoot); if (!nxBin) { runNxSync(argv.map(shell_quoting_1.quoteShellArg).join(' '), spawnOptions); return; } const result = (0, child_process_1.spawnSync)(process.execPath, [nxBin, ...argv], spawnOptions); if (result.error) { throw result.error; } if (result.status !== 0) { const error = new Error(`Command failed: nx ${argv.join(' ')} (exit code ${result.status})`); error.status = result.status ?? 1; throw error; } } function runNxSync(cmd, options) { let { packageManagerCommand, ...execSyncOptions } = options ?? {}; execSyncOptions.cwd ??= process.cwd(); execSyncOptions.windowsHide ??= true; const baseCmd = getRunNxBaseCommand(packageManagerCommand, execSyncOptions.cwd); (0, child_process_1.execSync)(`${baseCmd} ${cmd}`, execSyncOptions); } async function runNxAsync(cmd, options) { options ??= {}; options.cwd ??= process.cwd(); let { silent, packageManagerCommand, ...execSyncOptions } = options; silent ??= true; const baseCmd = getRunNxBaseCommand(packageManagerCommand, execSyncOptions.cwd); return new Promise((resolve, reject) => { const child = (0, child_process_1.exec)(`${baseCmd} ${cmd}`, { ...execSyncOptions, windowsHide: true }, (error, stdout, stderr) => { if (error) { reject(stderr || stdout || error.message); } else { resolve(); } }); if (!silent) { child.stdout?.pipe(process.stdout); child.stderr?.pipe(process.stderr); } }); } class PseudoTtyProcess { constructor(childProcess) { this.childProcess = childProcess; this.isAlive = true; this.exitCallbacks = []; childProcess.onExit((message) => { this.isAlive = false; const exitCode = (0, exit_codes_1.messageToCode)(message); this.exitCallbacks.forEach((cb) => cb(exitCode)); }); } onExit(callback) { this.exitCallbacks.push(callback); } onOutput(callback) { this.childProcess.onOutput(callback); } kill() { try { this.childProcess.kill(); } catch { // when the child process completes before we explicitly call kill, this will throw // do nothing } finally { if (this.isAlive == true) { this.isAlive = false; } } } } exports.PseudoTtyProcess = PseudoTtyProcess;