// What the guest npm prints, in npm 11.7.0's formats: display.js's log lines, // reify-output.js's summary, error-message.js's explanation of an error, and the usage // texts. Shared by every command. import { homedir } from "node:os"; import { join } from "node:path"; import process from "node:process"; import type { InstallError, InstallReport } from "../shared/protocol"; export const VERSION = "11.7.0"; const started = Date.now(); /** display.js #writeLog: "npm [ ]" before each line, a space only before text. */ export function logLines(level: "warn" | "error", title: string, text: string): string { const prefix = ["npm", level, title || null].filter((p) => p !== null).join(" "); return text.split(/\r?\n/).map((l) => `${prefix}${l ? " " : ""}${l}\n`).join(""); } // The `ms` package's short format, as reify-output.js prints the time taken. function ms(n: number): string { const units: Array<[number, string]> = [[86_400_000, "d"], [3_600_000, "h"], [60_000, "m"], [1000, "s"]]; for (const [size, unit] of units) if (Math.abs(n) >= size) return `${Math.round(n / size)}${unit}`; return `${n}ms`; } const plural = (n: number, word: string): string => `${n} ${word}${n === 1 ? "" : "s"}`; /** reify-output.js packagesChangedMessage, with nothing audited. */ export function changedMessage({ added, removed, changed }: InstallReport["diff"]): string { const msg = ["\n"]; if (added === 0 && removed === 0 && changed === 0) msg.push("up to date"); else { if (added) msg.push(`added ${plural(added, "package")}`); if (removed) { if (added) msg.push(", "); if (added && !changed) msg.push("and "); msg.push(`removed ${plural(removed, "package")}`); } if (changed) { if (added || removed) msg.push(", and "); msg.push(`changed ${plural(changed, "package")}`); } } msg.push(` in ${ms(Date.now() - started)}`); return `${msg.join("")}\n`; } export const CI_USAGE = `Clean install a project Usage: npm ci Options: [--install-strategy <hoisted|nested|shallow|linked>] [--legacy-bundling] [--global-style] [--omit <dev|optional|peer> [--omit <dev|optional|peer> ...]] [--include <prod|dev|optional|peer> [--include <prod|dev|optional|peer> ...]] [--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit] [--no-bin-links] [--no-fund] [--dry-run] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [--workspaces] [--include-workspace-root] [--install-links] aliases: clean-install, ic, install-clean, isntall-clean Run "npm help ci" for more info`; const ERESOLVE_FIX = `Fix the upstream dependency conflict, or retry this command with --force or --legacy-peer-deps to accept an incorrect (and potentially broken) dependency resolution.`; type Line = [title: string, ...text: string[]]; /** error-message.js errorMessage for the errors the installer raises: what npm logs as * `npm error` after the error's own fields. */ function explain(er: InstallError): Line[] { switch (er.code) { case "ERESOLVE": { const at = er.message.indexOf("\n"); const [head, rest] = at === -1 ? [er.message, ""] : [er.message.slice(0, at), er.message.slice(at + 1).replace(/^\n/, "")]; return [["ERESOLVE", head], ["", ""], ["", `${rest}\n\n${ERESOLVE_FIX}`]]; } case "E404": { const lines: Line[] = [["404", er.message.replace(/^404\s+/, "")]]; if (er.pkgid && er.pkgid !== "-") { lines.push( ["404", ""], ["404", "", `The requested resource '${er.pkgid}' could not be found or you do not have permission to access it.`], ["404", ""], ["404", "Note that you can also install from a"], ["404", "tarball, folder, http url, or git url."], ); } return lines; } case "ETARGET": return [["notarget", er.message], ["notarget", "In most cases you or one of your dependencies are requesting\na package version that doesn't exist."]]; case "EBADPLATFORM": return [["notsup", er.message]]; case "ENOENT": return [["enoent", er.message], ["enoent", "This is related to npm not being able to find a file.\n"]]; default: return [["", er.message]]; } } /** getError and outputError: the error's code, syscall, path and errno, then its explanation. */ function errorText(er: InstallError): string { const fields: Line[] = (["code", "syscall", "path", "errno"] as const) .filter((k) => er[k] !== undefined && er[k] !== "" && er[k] !== 0) .map((k) => [k, String(er[k])]); return [...fields, ...explain(er)].map(([title, ...text]) => logLines("error", title, text.join(" "))).join(""); } export function usage(): string { return `npm <command> Usage: npm install install all the dependencies in your project npm install <foo> add the <foo> dependency to your project npm test run this project's tests npm run <foo> run the script named <foo> npm <command> -h quick help on <command> npm -l display usage info for all commands npm help <term> search for help on <term> npm help npm more involved overview All commands: access, adduser, audit, bugs, cache, ci, completion, config, dedupe, deprecate, diff, dist-tag, docs, doctor, edit, exec, explain, explore, find-dupes, fund, get, help, help-search, init, install, install-ci-test, install-test, link, ll, login, logout, ls, org, outdated, owner, pack, ping, pkg, prefix, profile, prune, publish, query, rebuild, repo, restart, root, run, sbom, search, set, shrinkwrap, star, stars, start, stop, team, test, token, undeprecate, uninstall, unpublish, unstar, update, version, view, whoami Specify configs in the ini-formatted file: ${join(homedir(), ".npmrc")} or on the command line via: npm <command> --key=value More configuration info: npm help config Configuration fields: npm help 7 config npm@${VERSION} /usr/local/lib/node_modules/npm `; } export const EXEC_USAGE = `Run a command from a local or remote npm package Usage: npm exec -- <pkg>[@<version>] [args...] npm exec --package=<pkg>[@<version>] -- <cmd> [args...] npm exec -c '<cmd> [args...]' npm exec --package=foo -c '<cmd> [args...]' Options: [--package <package-spec> [--package <package-spec> ...]] [-c|--call <call>] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [--workspaces] [--include-workspace-root] alias: x Run "npm help exec" for more info`; /** An npm error, as a command throws it for main to print. */ export class NpmFailure extends Error { constructor(readonly error: InstallError) { super(error.message); } } export function fail(er: InstallError): void { process.stderr.write(errorText(er)); // npm exits with the error's errno when it has one (ENOENT's -2 is 254), else 1. process.exitCode = (er.errno ?? 1) & 0xff || 1; }