Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119// npm script parsing for the guest npm, checked in Node: each script is held to the// commands sh would run for it (assignments, words, the && / || / ; that gates each and// its line), or to the construct it refuses; then npx's argv rewrite and the run and// exec command lines. tests/npm-cli-guest.spec.ts runs them.import { test, expect } from "@playwright/test";import { npxArgv, parse } from "../src/npm-cli/command";import { parseScript, shQuote, type Script, type Step, type When } from "../src/npm-cli/script";
const env = { HOME: "/home/user", SPACED: " a b ", EMPTY: "", STAR: "*.js" };
type Cmd = [assign: Record<string, string>, argv: [string, ...string[]], when?: When, line?: number];const steps = (...cmds: Cmd[]): Script => ({ kind: "commands", steps: cmds.map(([assign, argv, when = "always", line = 1]): Step => ({ when, command: { assign, argv, line } })),});const refused = (construct: string, near: string): Script => ({ kind: "refused", construct, near } as Script);
const cases: Array<[script: string, expected: Script]> = [ ["node show.js hi", steps([{}, ["node", "show.js", "hi"]])], ["", steps()], [" # nothing but a comment", steps()], ["node a.js # trailing comment", steps([{}, ["node", "a.js"]])], ["node a#b", steps([{}, ["node", "a#b"]])], ["node 'a b' \"c d\" e\\ f", steps([{}, ["node", "a b", "c d", "e f"]])], [`node 'it'\\''s' "say \\"hi\\"" "a\\b" '\\n'`, steps([{}, ["node", "it's", 'say "hi"', "a\\b", "\\n"]])], [`node "" ''`, steps([{}, ["node", "", ""]])], ["node a.js && node b.js", steps([{}, ["node", "a.js"]], [{}, ["node", "b.js"], "ok"])], ["a || b; c && d || e", steps([{}, ["a"]], [{}, ["b"], "failed"], [{}, ["c"]], [{}, ["d"], "ok"], [{}, ["e"], "failed"])], ["a &&\n b\nc", steps([{}, ["a"]], [{}, ["b"], "ok", 2], [{}, ["c"], "always", 3])], ["a\\\n b", steps([{}, ["a", "b"]])], ["a;", steps([{}, ["a"]])], ["\n\na\n\n", steps([{}, ["a"], "always", 3])], ["FOO=bar BAZ='q x' node a.js FOO=no", steps([{ FOO: "bar", BAZ: "q x" }, ["node", "a.js", "FOO=no"]])], ["FOO= node a.js", steps([{ FOO: "" }, ["node", "a.js"]])], ["'FOO'=bar node", steps([{}, ["FOO=bar", "node"]])], ["node $HOME/x \"$HOME\" ${HOME}y $NOPE", steps([{}, ["node", "/home/user/x", "/home/user", "/home/usery"]])], ["node $SPACED \"$SPACED\" x${SPACED}y", steps([{}, ["node", "a", "b", " a b ", "x", "a", "b", "y"]])], ["node $EMPTY \"$EMPTY\"", steps([{}, ["node", ""]])], ["X=$SPACED node", steps([{ X: " a b " }, ["node"]])], ["node $ \"$\" a$", steps([{}, ["node", "$", "$", "a$"]])], ["echo {a,b} }", steps([{}, ["echo", "{a,b}", "}"]])], ["node 'if' \\!", steps([{}, ["node", "if", "!"]])], ["node a.js | tee", refused("a pipe", "|")], ["node a.js &", refused("a background job", "&")], ["node a.js > out", refused("a redirect", ">")], ["node a.js 2>&1", refused("a redirect", ">")], ["node < in", refused("a redirect", "<")], ["(node a.js)", refused("a subshell", "(")], ["node $(pwd)", refused("command substitution", "$(")], ["node \"`pwd`\"", refused("command substitution", "`")], ["node $((1 + 2))", refused("arithmetic expansion", "$((")], ["node $1", refused("parameter expansion", "$1")], ["node \"$@\"", refused("parameter expansion", "$@")], ["node ${X:-y}", refused("parameter expansion", "${X:-y}")], ["node *.js", refused("a glob", "*")], ["node $STAR", refused("a glob", "*.js")], ["node ~/x", refused("tilde expansion", "~")], ["if true; then a; fi", refused("a reserved word", "if")], ["! node a.js", refused("a reserved word", "!")], ["FOO=1", refused("an assignment with no command", "FOO=")], ["FOO=1 && node a.js", refused("an assignment with no command", "FOO=")], ["node 'a", refused("an unterminated quote", "'")], ["node \"a", refused("an unterminated quote", "\"")], ["&& node", refused("a syntax error", "&&")], ["node &&", refused("a syntax error", "end of script")], ["a ;; b", refused("a syntax error", ";;")], ["; a", refused("a syntax error", ";")],];
for (const [script, expected] of cases) { test(`parses ${JSON.stringify(script)}`, () => { expect(parseScript(script, env)).toEqual(expected); });}
test("an argument npm appends to a script parses back to itself", () => { const args = ["plain", "b c", "it's", "", "$HOME", "a\"b", "*", "--flag=x y", "'quoted'"]; expect(args.map(shQuote)).toEqual(["plain", "'b c'", "'it'\\''s'", "''", "'$HOME'", "'a\"b'", "'*'", "'--flag=x y'", "\\''quoted'\\'"]); expect(parseScript(`node show.js ${args.map(shQuote).join(" ")}`, env)).toEqual(steps([{}, ["node", "show.js", ...args]]));});
test("npx's argv becomes npm exec's as npx-cli.js rewrites it", () => { expect([ npxArgv(["--yes", "hello", "x", "--flag"]), npxArgv(["-y", "-p", "hello", "hello", "y"]), npxArgv(["-c", "hello there"]), npxArgv(["--registry", "http://r", "hello"]), npxArgv(["-q", "hello"]), ]).toEqual([ ["exec", "--yes", "--", "hello", "x", "--flag"], ["exec", "--yes", "--package", "hello", "--", "hello", "y"], ["exec", "--call", "hello there"], ["exec", "--registry", "http://r", "--", "hello"], ["exec", "--loglevel", "warn", "--", "hello"], ]);});
test("run, the lifecycle commands and exec parse as npm reads them", () => { const command = (...argv: string[]) => parse(argv, "/app").command; expect([ command("run"), command("urn", "greet", "--", "a", "--b"), command("t", "--if-present"), command("restart"), command("x", "-c", "hello there", "--yes"), command("exec", "--package=a", "--package", "b", "--no-yes", "--", "bin", "-v"), ]).toEqual([ { kind: "run", event: null, args: [], ifPresent: false, ignoreScripts: false }, { kind: "run", event: "greet", args: ["a", "--b"], ifPresent: false, ignoreScripts: false }, { kind: "run", event: "test", args: [], ifPresent: true, ignoreScripts: false }, { kind: "run", event: "restart", args: [], ifPresent: false, ignoreScripts: false }, { kind: "exec", args: [], packages: [], call: "hello there", yes: true }, { kind: "exec", args: ["bin", "-v"], packages: ["a", "b"], call: "", yes: false }, ]); expect(parse(["exec", "--package=a", "--package", "b", "--yes", "--", "bin"], "/app").config).toEqual({ npm_config_package: "a\n\nb", npm_config_yes: "true", });});