Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289// npm 11.7.0's command line, as the guest npm reads it: cmd-list.js's commands, aliases// and abbreviations (deref), nopt over the flags install, ci, uninstall, run and exec// take, and npx-cli.js's rewrite of npx's argv into `npm exec`. Pure, so the oracle// tests parse each case's argv as the guest command does.import type { InstallError, InstallRequest, SaveType } from "../shared/protocol";
/** What one invocation asks for, before any of it runs. */export type Command = | { kind: "version" } | { kind: "help" } | { kind: "unknown"; command: string } /** A command npm has that this one does not run. */ | { kind: "unsupported"; command: string } | { kind: "error"; error: InstallError } | { kind: "install"; request: InstallRequest } /** run-script.js: `event` null lists the scripts. test, start, stop and restart are * run with their own name as the event. */ | { kind: "run"; event: string | null; args: string[]; ifPresent: boolean; ignoreScripts: boolean } /** exec.js and libnpmexec: run `call`, else args[0], from a bin found or installed. */ | { kind: "exec"; args: string[]; packages: string[]; call: string; yes: boolean | null };
/** npm's warnings and config notes from parsing, printed before the command runs. */export interface Parsed { command: Command; /** The top-level command, as npm exports it to scripts in npm_command. */ name: string | null; warnings: string[]; /** set-envs.js: each config the command line set away from its default, as * `npm_config_<key>`, for the scripts it runs. */ config: Record<string, string>;}
// lib/utils/cmd-list.js: every command, with its description for did-you-mean.js.export const COMMANDS: Readonly<Record<string, string>> = { access: "Set access level on published packages", adduser: "Add a registry user account", audit: "Run a security audit", bugs: "Report bugs for a package in a web browser", cache: "Manipulates packages and npx cache", ci: "Clean install a project", completion: "Tab Completion for npm", config: "Manage the npm configuration files", dedupe: "Reduce duplication in the package tree", deprecate: "Deprecate a version of a package", diff: "The registry diff command", "dist-tag": "Modify package distribution tags", docs: "Open documentation for a package in a web browser", doctor: "Check the health of your npm environment", edit: "Edit an installed package", exec: "Run a command from a local or remote npm package", explain: "Explain installed packages", explore: "Browse an installed package", "find-dupes": "Find duplication in the package tree", fund: "Retrieve funding information", get: "Get a value from the npm configuration", help: "Get help on npm", "help-search": "Search npm help documentation", init: "Create a package.json file", install: "Install a package", "install-ci-test": "Install a project with a clean slate and run tests", "install-test": "Install package(s) and run tests", link: "Symlink a package folder", ll: "List installed packages", login: "Login to a registry user account", logout: "Log out of the registry", ls: "List installed packages", org: "Manage orgs", outdated: "Check for outdated packages", owner: "Manage package owners", pack: "Create a tarball from a package", ping: "Ping npm registry", pkg: "Manages your package.json", prefix: "Display prefix", profile: "Change settings on your registry profile", prune: "Remove extraneous packages", publish: "Publish a package", query: "Retrieve a filtered list of packages", rebuild: "Rebuild a package", repo: "Open package repository page in the browser", restart: "Restart a package", root: "Display npm root", run: "Run arbitrary package scripts", sbom: "Generate a Software Bill of Materials (SBOM)", search: "Search for packages", set: "Set a value in the npm configuration", shrinkwrap: "Lock down dependency versions for publication", star: "Mark your favorite packages", stars: "View packages marked as favorites", start: "Start a package", stop: "Stop a package", team: "Manage organization teams and team memberships", test: "Test a package", token: "Manage your authentication tokens", undeprecate: "Undeprecate a version of a package", uninstall: "Remove a package", unpublish: "Remove a package from the registry", unstar: "Remove an item from your favorite packages", update: "Update packages", version: "Bump a package version", view: "View registry info", whoami: "Display npm username",};const ALIASES: Record<string, string> = { author: "owner", home: "docs", issues: "bugs", info: "view", show: "view", find: "search", add: "install", unlink: "uninstall", remove: "uninstall", rm: "uninstall", r: "uninstall", un: "uninstall", rb: "rebuild", list: "ls", ln: "link", create: "init", i: "install", it: "install-test", cit: "install-ci-test", up: "update", c: "config", s: "search", se: "search", tst: "test", t: "test", ddp: "dedupe", v: "view", "run-script": "run", "clean-install": "ci", "clean-install-test": "install-ci-test", x: "exec", why: "explain", la: "ll", verison: "version", ic: "ci", innit: "init", in: "install", ins: "install", inst: "install", insta: "install", instal: "install", isnt: "install", isnta: "install", isntal: "install", isntall: "install", "install-clean": "ci", "isntall-clean": "ci", hlep: "help", "dist-tags": "dist-tag", upgrade: "update", udpate: "update", rum: "run", sit: "install-ci-test", urn: "run", ogr: "org", "add-user": "adduser",};
/** cmd-list.js deref: a command, an alias of one, or a prefix only one of them has. */function deref(c: string): string | undefined { const name = c.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`); if (Object.hasOwn(COMMANDS, name)) return name; if (ALIASES[name]) return ALIASES[name]; const words = [...Object.keys(COMMANDS), ...Object.keys(ALIASES)]; const matches = words.filter((w) => w.startsWith(name)); let a = matches.length === 1 ? matches[0] : undefined; while (a !== undefined && ALIASES[a]) a = ALIASES[a]; return a;}
/** fastest-levenshtein's distance. */function distance(a: string, b: string): number { let row = Array.from({ length: b.length + 1 }, (_, j) => j); for (let i = 1; i <= a.length; i++) { const next = [i]; for (let j = 1; j <= b.length; j++) { next[j] = Math.min(row[j]! + 1, next[j - 1]! + 1, row[j - 1]! + (a[i - 1] === b[j - 1] ? 0 : 1)); } row = next; } return row[b.length]!;}
/** did-you-mean.js: commands, scripts and bins close to `typed`, as the lines npm adds to an error. */export function didYouMean(pkg: { scripts?: Record<string, string>; bin?: Record<string, string> }, typed: string): string { const close = (word: string): boolean => distance(typed, word) < typed.length * 0.4; const best = [ ...Object.entries(COMMANDS).filter(([cmd]) => close(cmd) && typed !== cmd), ...Object.keys(pkg.scripts ?? {}) .filter((s) => close(s) && !["stop", "start", "test", "restart"].includes(s)) .map((s) => [`run ${s}`, `run the "${s}" package script`]), ...Object.keys(pkg.bin ?? {}) .filter((b) => close(b)) .map((b) => [`exec ${b}`, `run the "${b}" command from either this or a remote npm package`]), ]; if (best.length === 0) return ""; return `\n\nDid you mean ${best.length === 1 ? "this" : "one of these"}?\n${best.slice(0, 3).map(([msg, comment]) => ` npm ${msg} # ${comment}`).join("\n")}`;}
const SAVE_TYPES: Record<string, SaveType> = { "-D": "dev", "--save-dev": "dev", "-O": "optional", "--save-optional": "optional", "-P": "prod", "--save-prod": "prod", "--save-peer": "peer",};/** Flags npm takes that change nothing here: no audit, funding count or progress runs, * and output is never parseable or JSON. */const NO_EFFECT = new Set([ "--audit", "--no-audit", "--fund", "--no-fund", "--no-ignore-scripts", "-p", "--parseable", "--progress", "--no-progress", "--update-notifier", "--no-update-notifier", "--foreground-scripts",]);/** Flags taking a value, the next argument when not given with `=`; null keeps none of it. */const VALUED: Record<string, "package" | "call" | null> = { "--package": "package", "-c": "call", "--call": "call", "--loglevel": null };
const LIFECYCLE = new Set(["test", "start", "stop", "restart"]);
/** nopt over the flags these commands take, then deref on the first positional; a * command runs in `prefix`, npm's localPrefix. */export function parse(argv: readonly string[], prefix: string): Parsed { const warnings: string[] = []; const config: Record<string, string> = {}; const positional: string[] = []; const packages: string[] = []; let saveType: SaveType | null = null; let save = true; let exact = false; let version = false; let yes: boolean | null = null; let call = ""; let ifPresent = false; let ignoreScripts = false; for (let i = 0; i < argv.length; i++) { const arg = argv[i]!; if (arg === "--") { positional.push(...argv.slice(i + 1)); break; } const eq = arg.indexOf("="); const key = eq === -1 ? arg : arg.slice(0, eq); if (!arg.startsWith("-") || arg === "-") positional.push(arg); else if (Object.hasOwn(VALUED, key)) { const value = eq === -1 ? argv[++i] ?? "" : arg.slice(eq + 1); const into = VALUED[key]; if (into === "package") packages.push(value); if (into === "call") call = value; if (into) config[`npm_config_${into}`] = into === "package" ? packages.join("\n\n") : value; } else if (SAVE_TYPES[arg]) saveType = SAVE_TYPES[arg]!; else if (arg === "-E" || arg === "--save-exact") exact = true; else if (arg === "--no-save" || arg === "--save=false") save = false; else if (arg === "-S" || arg === "--save" || arg === "--save=true") save = true; else if (arg === "-v" || arg === "--version") version = true; else if (arg === "-y" || arg === "--yes" || arg === "--yes=true") yes = true; else if (arg === "-n" || arg === "--no-yes" || arg === "--yes=false") yes = false; else if (arg === "--if-present") ifPresent = true; else if (arg === "--ignore-scripts") ignoreScripts = true; else if (!NO_EFFECT.has(key)) warnings.push(`Unknown cli config "${key}". This will stop working in the next major version of npm.`); } if (yes !== null) config.npm_config_yes = String(yes); if (ifPresent) config.npm_config_if_present = "true"; if (ignoreScripts) config.npm_config_ignore_scripts = "true"; const result = (command: Command, name: string | null = null): Parsed => ({ command, name, warnings, config }); if (version) return result({ kind: "version" }); const [first, ...args] = positional; if (first === undefined) return result({ kind: "help" }); const name = deref(first); if (name === undefined) return result({ kind: "unknown", command: first }); switch (name) { case "install": return result({ kind: "install", request: { command: "install", cwd: prefix, add: args, save: save && { type: saveType, exact } } }, name); case "uninstall": if (!args.length) return result({ kind: "error", error: { message: "Must provide a package name to remove" } }, name); return result({ kind: "install", request: { command: "uninstall", cwd: prefix, remove: args, save } }, name); case "ci": return result({ kind: "install", request: { command: "ci", cwd: prefix } }, name); case "run": return result({ kind: "run", event: args[0] ?? null, args: args.slice(1), ifPresent, ignoreScripts }, name); case "exec": return result({ kind: "exec", args, packages, call, yes }, name); default: if (LIFECYCLE.has(name)) return result({ kind: "run", event: name, args, ifPresent, ignoreScripts }, name); return result({ kind: "unsupported", command: name }, name); }}
// npx-cli.js: npm's flags that take no value (definitions of type Boolean), and npx's own.const NPX_SWITCHES = new Set([ "all", "allow-same-version", "audit", "bin-links", "browser", "bypass-2fa", "color", "commit-hooks", "description", "dev", "diff-ignore-all-space", "diff-name-only", "diff-no-prefix", "diff-text", "dry-run", "engine-strict", "expect-results", "force", "foreground-scripts", "format-package-lock", "fund", "git-tag-version", "global", "global-style", "if-present", "ignore-scripts", "include-staged", "include-workspace-root", "init-private", "install-links", "json", "legacy-bundling", "legacy-peer-deps", "link", "long", "offline", "omit-lockfile-registry-resolved", "optional", "package-lock", "package-lock-only", "parseable", "prefer-dedupe", "prefer-offline", "prefer-online", "production", "progress", "provenance", "read-only", "rebuild-bundle", "save", "save-bundle", "save-dev", "save-exact", "save-optional", "save-peer", "save-prod", "packages-all", "shrinkwrap", "sign-git-commit", "sign-git-tag", "strict-peer-deps", "strict-ssl", "timing", "unicode", "update-notifier", "usage", "version", "versions", "workspaces", "workspaces-update", "yes", "no-install", "quiet", "q", "v", "help", "h",]);const NPX_OPTS = new Set(["package", "p", "cache", "userconfig", "call", "c", "shell"]);/** The definitions' shorthands npx expands before it looks at a flag again. */const SHORTHANDS: Record<string, string[]> = { d: ["--loglevel", "info"], dd: ["--loglevel", "verbose"], ddd: ["--loglevel", "silly"], q: ["--loglevel", "warn"], s: ["--loglevel", "silent"], silent: ["--loglevel", "silent"], verbose: ["--loglevel", "verbose"], help: ["--usage"], c: ["--call"], g: ["--global"], S: ["--save"], D: ["--save-dev"], E: ["--save-exact"], O: ["--save-optional"], P: ["--save-prod"], "?": ["--usage"], H: ["--usage"], h: ["--usage"], v: ["--version"], y: ["--yes"],};
/** npx-cli.js: npx's argv as the `npm exec` argv it runs, `--` put before the first * positional so that everything from the command on is the command's. */export function npxArgv(argv: readonly string[]): string[] { const out = ["exec", ...argv]; for (let i = 1; i < out.length; i++) { const arg = out[i]!; if (arg === "--") break; if (!arg.startsWith("-")) { out.splice(i, 0, "--"); break; } const [key = "", ...v] = arg.replace(/^-+/, "").split("="); if (key === "p") out[i] = ["--package", ...v].join("="); else if (key === "shell") out[i] = ["--script-shell", ...v].join("="); else if (key === "no-install") out[i] = "--yes=false"; else if (SHORTHANDS[key]) { out.splice(i, 1, ...SHORTHANDS[key]!, ...(v.length ? [v.join("=")] : [])); i--; continue; } if (v.length === 0 && !NPX_SWITCHES.has(key) && (NPX_OPTS.has(key) || !/^-/.test(out[i + 1] ?? ""))) i++; } return out;}