// The guest `npm` command: a script the kernel seeds at // /usr/local/lib/node_modules/npm/bin/npm-cli.js (linked from /usr/local/bin/npm), and // again as bin/npx-cli.js (linked from /usr/local/bin/npx), where it runs as npx. The // runtime runs it like any other. It parses argv as npm 11.7.0 does, hands installs and // package lookups to the kernel through the runtime's npm hooks, runs scripts and bins // under node (run.ts, exec.ts), and prints what npm prints: reify-output.js's summary on // stdout, warnings and errors in npm's display format (lib/utils/display.js, // error-message.js) on stderr. Only node: builtins and the hooks: it is guest code. import { existsSync } from "node:fs"; import { homedir } from "node:os"; import { basename, dirname, join } from "node:path"; import process from "node:process"; import { npxArgv, parse, type Parsed } from "./command"; import { exec, type RuntimeHooks } from "./exec"; import { changedMessage, CI_USAGE, fail, logLines, NpmFailure, usage, VERSION } from "./output"; import { run } from "./run"; import { NPM_ROOT } from "./run-script"; /** npm's localPrefix: the nearest directory up from `cwd` with a package.json or a * node_modules, else `cwd` itself. */ function localPrefix(cwd: string): string { for (let dir = cwd; ; dir = dirname(dir)) { if (existsSync(join(dir, "package.json")) || existsSync(join(dir, "node_modules"))) return dir; if (dir === dirname(dir)) return cwd; } } /** @npmcli/config's set-envs.js, and the user-agent and npm_command npm sets: what every * script npm runs finds in its environment. */ function setEnvs({ config, name }: Parsed, prefix: string): void { const env = process.env; const home = homedir(); Object.assign(env, { npm_config_user_agent: `npm/${VERSION} node/${process.version} ${process.platform} ${process.arch} workspaces/false`, npm_config_cache: join(home, ".npm"), npm_config_globalconfig: "/usr/local/etc/npmrc", npm_config_init_module: join(home, ".npm-init.js"), npm_config_userconfig: join(home, ".npmrc"), npm_config_prefix: "/usr/local", ...config, INIT_CWD: process.cwd(), HOME: home, npm_config_global_prefix: "/usr/local", npm_config_local_prefix: prefix, npm_config_node_gyp: `${NPM_ROOT}/node_modules/node-gyp/bin/node-gyp.js`, npm_config_npm_version: VERSION, npm_execpath: `${NPM_ROOT}/bin/npm-cli.js`, NODE: process.execPath, npm_node_execpath: process.execPath, COLOR: "0", }); if (name) env.npm_command = name; } async function main(): Promise { const prefix = localPrefix(process.cwd()); const npx = /^npx(?:-cli\.js)?$/.test(basename(process.argv[1] ?? "")); const argv = process.argv.slice(2); const parsed = parse(npx ? npxArgv(argv) : argv, prefix); const { command, warnings } = parsed; for (const w of warnings) process.stderr.write(logLines("warn", "", w)); const hooks = (globalThis as Record | undefined>)[Symbol.for("nib.runtime")]; const needHooks = (): RuntimeHooks => { if (!hooks?.npm || !hooks.npmLookup) throw new NpmFailure({ message: "npm needs the @nibling/kernel runtime's installer, which this process does not have" }); return hooks as RuntimeHooks; }; switch (command.kind) { case "version": process.stdout.write(`${VERSION}\n`); return; case "help": process.stdout.write(usage()); process.exitCode = 1; return; case "unknown": process.stdout.write(`Unknown command: "${command.command}"\n\nTo see a list of supported npm commands, run:\n npm help\n`); process.exitCode = 1; return; case "unsupported": return fail({ code: "ENOTSUP", message: `\`npm ${command.command}\` is not available in this runtime; install, ci, uninstall, run, exec and the lifecycle commands are.` }); case "error": return fail(command.error); case "run": setEnvs(parsed, prefix); return await run(command, prefix); case "exec": setEnvs(parsed, prefix); return await exec(command, prefix, needHooks()); case "install": { const outcome = await needHooks().npm(command.request); if (!outcome.ok) { const er = outcome.error; // A usage error carries the command's usage after it (BaseCommand#usageError). return fail(er.code === "EUSAGE" && command.request.command === "ci" ? { ...er, message: `\n${er.message}\n\n${CI_USAGE}` } : er); } for (const w of outcome.report.warnings) process.stderr.write(logLines("warn", "", w)); process.stdout.write(changedMessage(outcome.report.diff)); } } } main().catch((e: unknown) => { if (e instanceof NpmFailure) return fail(e.error); throw e; });