Skip to content

Fix arguments when using -e with Node.js or Bun#4

Open
kit494way wants to merge 2 commits intolpil:mainfrom
kit494way:fix-arguments-js
Open

Fix arguments when using -e with Node.js or Bun#4
kit494way wants to merge 2 commits intolpil:mainfrom
kit494way:fix-arguments-js

Conversation

@kit494way
Copy link

Fix arguments when using special arguments (i.e. -e, --eval, -p, --print) with Node.js or Bun.

When special arguments are specified, process.argv[1] is not a script path, and command line arguments are stored starting from process.argv[1].
In Node.js, util.parseArgs(), which parses process.argv, extracts from index 1 of process.argv when a special argument is specified.
https://github.com/nodejs/node/blob/7079041e0a73738185ff5b7480d4775158b527c3/lib/internal/util/parse_args/parse_args.js#L58-L72

Deno also implements process.argv, but it behaves differently from Node.js and Bun.
Therefore, change the processing order so that Deno.args takes precedence over process.argv.

Before this change

Node.js

When executing as Node.js script file, an argument arg1 is included in arguments.

echo "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" >main.mjs && node main.mjs arg1

Output:

Argv {
  runtime: '/home/kit494way/.local/share/mise/installs/node/24.2.0/bin/node',
  program: '/home/kit494way/projects/argv/main.mjs',
  arguments: NonEmpty { head: 'arg1', tail: Empty {} }
}

When evaluating string as Node.js script, an arguments arg1 is treated as program and not included in arguments.

node -e "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" arg1

Output:

Argv {
  runtime: '/home/kit494way/.local/share/mise/installs/node/24.2.0/bin/node',
  program: 'arg1',
  arguments: Empty {}
}

Bun

When executing as Bun script file, an argument arg1 is included in arguments.

echo "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" >main.mjs && bun main.mjs arg1

Output:

Argv {
  runtime: "/home/kit494way/.local/share/mise/installs/bun/1.2.16/bin/bun",
  program: "/home/kit494way/projects/argv/main.mjs",
  arguments: NonEmpty {
    head: "arg1",
    tail: Empty {
      toArray: [Function: toArray],
      atLeastLength: [Function: atLeastLength],
      hasLength: [Function: hasLength],
      countLength: [Function: countLength],
      [Symbol(Symbol.iterator)]: [Function],
    },
    toArray: [Function: toArray],
    atLeastLength: [Function: atLeastLength],
    hasLength: [Function: hasLength],
    countLength: [Function: countLength],
    [Symbol(Symbol.iterator)]: [Function],
  },
  withFields: [Function: withFields],
}

When evaluating string as Bun script, an arguments arg1 is treated as program and not included in arguments.

bun -e "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" arg1

Output:

Argv {
  runtime: "/home/kit494way/.local/share/mise/installs/bun/1.2.16/bin/bun",
  program: "arg1",
  arguments: Empty {
    toArray: [Function: toArray],
    atLeastLength: [Function: atLeastLength],
    hasLength: [Function: hasLength],
    countLength: [Function: countLength],
    [Symbol(Symbol.iterator)]: [Function],
  },
  withFields: [Function: withFields],
}

After this change

Node.js

Regardless of whether you execute it as a script file or evaluate a string as a script, the argument arg1 is correctly included in arguments.

Execute as Node.js script file:

echo "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" >main.mjs && node main.mjs arg1

Output:

Argv {
  runtime: '/home/kit494way/.local/share/mise/installs/node/24.2.0/bin/node',
  program: '/home/kit494way/projects/argv/main.mjs',
  arguments: NonEmpty { head: 'arg1', tail: Empty {} }
}

Evaluate string as Node.js script:

node -e "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" arg1

Output:

Argv {
  runtime: '/home/kit494way/.local/share/mise/installs/node/24.2.0/bin/node',
  program: '',
  arguments: NonEmpty { head: 'arg1', tail: Empty {} }
}

Bun

Regardless of whether you execute it as a script file or evaluate a string as a script, the argument arg1 is correctly included in arguments.

Execute as Bun script file:

echo "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" >main.mjs && bun main.mjs arg1

Output:

Argv {
  runtime: "/home/kit494way/.local/share/mise/installs/bun/1.2.16/bin/bun",
  program: "/home/kit494way/projects/argv/main.mjs",
  arguments: NonEmpty {
    head: "arg1",
    tail: Empty {
      toArray: [Function: toArray],
      atLeastLength: [Function: atLeastLength],
      hasLength: [Function: hasLength],
      countLength: [Function: countLength],
      [Symbol(Symbol.iterator)]: [Function],
    },
    toArray: [Function: toArray],
    atLeastLength: [Function: atLeastLength],
    hasLength: [Function: hasLength],
    countLength: [Function: countLength],
    [Symbol(Symbol.iterator)]: [Function],
  },
  withFields: [Function: withFields],
}

Evaluate string as Bun script:

bun -e "import {load} from './build/dev/javascript/argv/argv.mjs'; console.log(load())" arg1

Output:

Argv {
  runtime: "/home/kit494way/.local/share/mise/installs/bun/1.2.16/bin/bun",
  program: "",
  arguments: NonEmpty {
    head: "arg1",
    tail: Empty {
      toArray: [Function: toArray],
      atLeastLength: [Function: atLeastLength],
      hasLength: [Function: hasLength],
      countLength: [Function: countLength],
      [Symbol(Symbol.iterator)]: [Function],
    },
    toArray: [Function: toArray],
    atLeastLength: [Function: atLeastLength],
    hasLength: [Function: hasLength],
    countLength: [Function: countLength],
    [Symbol(Symbol.iterator)]: [Function],
  },
  withFields: [Function: withFields],
}

src/argv_ffi.mjs Outdated

if (globalThis.process) {
const specialArgs = ["-e", "--eval", "-p", "--print"];
// In Node.js and Bun, when special arguments are specified, `process.argv[1]` is not a script path,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap this long line please

const specialArgs = ["-e", "--eval", "-p", "--print"];
// In Node.js and Bun, when special arguments are specified, `process.argv[1]` is not a script path,
// and command line arguments are stored starting from `process.argv[1]`.
if (process.execArgv.some((x) => specialArgs.includes(x))) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bit over-eager? There could be --eval in argv and it not be a call to that flag, such as if it was a value for another flag or if it came after --?

Would we need to handle --eval=something?

How can we test this to be sure it works?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process.execArgv is Node.js (or Bun) specific arguments.
Arguments after -- are not included in process.execArgv.
They are included in process.argv.
See https://nodejs.org/api/process.html#processexecargv

I have no good ideas how to test this with automated testing. However, I tested it by manually executing the following commands.

$ node -e "import {load} from './build/dev/javascript/argv/argv.mjs';console.log(JSON.stringify(load(),null,2))" -- --eval arg1
{
  "runtime": "/home/kit494way/.local/share/mise/installs/node/24.2.0/bin/node",
  "program": "",
  "arguments": {
    "head": "--eval",
    "tail": {
      "head": "arg1",
      "tail": {}
    }
  }
}

$ bun -e "import {load} from './build/dev/javascript/argv/argv.mjs';console.log(JSON.stringify(load(),null,2))" -- --eval arg1
{
  "runtime": "/home/kit494way/.local/share/mise/installs/bun/1.2.21/bin/bun",
  "program": "",
  "arguments": {
    "head": "--eval",
    "tail": {
      "head": "arg1",
      "tail": {}
    }
  }
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the --eval=something syntax?

I have no good ideas how to test this with automated testing.

Could use the shellout package to call various runtimes with various arguments, running a Gleam function that prints argv.load(). JSON.stringify couldn't be used as its behaviour is unspecified with Gleam data.

@lpil
Copy link
Owner

lpil commented Aug 26, 2025

Thank you!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants