// step 4: RNX operations registered as just-bash commands. // // `rnx` inside a box shell is the same CLI, re-invoked as a child process with // the shell's own cwd. that is deliberate — spelling the verbs a second time // in shell-command form would be a second implementation of every command, and // the spec is explicit that the CLI and the in-shell commands are clients of // one implementation, not two. import { spawn } from 'node:child_process' import path from 'node:path' import { defineCommand, type CustomCommand } from '@rnx/box/shell' import { PROJECT_MOUNT } from '@rnx/box/shell' import { rnxPublicBrand } from '../../../src/public-brand' import { rnxSelfInvocation } from '../../self-invocation' import { RNX_PLACEMENT_ENV } from '../../ws-bridge' // map a shell path under /project back onto the real checkout, so a child // process started from `cd src && rnx describe` sees the directory the user // is actually standing in. function hostCwd(root: string, shellCwd: string): string { if (shellCwd === PROJECT_MOUNT) return root if (!shellCwd.startsWith(`${PROJECT_MOUNT}/`)) return root return path.join(root, shellCwd.slice(PROJECT_MOUNT.length + 1)) } export function createRnxCommand(options: { root: string shellCwd: () => string }): CustomCommand { return defineCommand(rnxPublicBrand.commandName, async (args) => { const { executable, prefixArgs } = rnxSelfInvocation() const child = spawn(executable, [...prefixArgs, ...args], { cwd: hostCwd(options.root, options.shellCwd()), // mark the child as running inside a local box. this is the only thing // that sets a placement — nothing infers one from cwd — and it is what // lets `rnx box` refuse to nest inside itself. env: { ...process.env, [RNX_PLACEMENT_ENV]: 'local-box' }, stdio: ['ignore', 'pipe', 'pipe'], }) let stdout = '' let stderr = '' child.stdout.on('data', (chunk) => { stdout += String(chunk) }) child.stderr.on('data', (chunk) => { stderr += String(chunk) }) const exitCode = await new Promise((resolve) => { child.once('error', (error) => { stderr += `${rnxPublicBrand.commandName}: ${error.message}\n` resolve(1) }) child.once('close', (code) => resolve(code ?? 0)) }) return { stdout, stderr, exitCode } }) }