nodejs/cli.rs
1//! Command-line interface for the `node` binary.
2
3use clap::Parser;
4
5#[derive(Parser, Debug)]
6#[command(
7 name = "node",
8 version,
9 about = "JavaScript on fusevm — a compiled JS runtime (bytecode VM + Cranelift JIT)",
10 long_about = None,
11)]
12pub struct Cli {
13 /// Evaluate a one-liner instead of a file (`node -e 'console.log(1+1)'`).
14 #[arg(short = 'e', long = "eval", value_name = "SRC")]
15 pub eval: Option<String>,
16
17 /// Evaluate a one-liner and print its result (`node -p '1+1'` → `2`).
18 #[arg(short = 'p', long = "print", value_name = "SRC")]
19 pub print: Option<String>,
20
21 /// Start the interactive REPL.
22 #[arg(long = "repl")]
23 pub repl: bool,
24
25 /// Speak the Language Server Protocol over stdio.
26 #[arg(long = "lsp")]
27 pub lsp: bool,
28
29 /// Speak the Debug Adapter Protocol over stdio.
30 #[arg(long = "dap")]
31 pub dap: bool,
32
33 /// Ahead-of-time compile the script to a standalone native executable.
34 #[arg(long = "build")]
35 pub build: bool,
36
37 /// Print the compiled fusevm bytecode for the script and exit.
38 #[arg(long = "dump-bytecode")]
39 pub dump_bytecode: bool,
40
41 /// Print the lexer token stream for the script and exit.
42 #[arg(long = "dump-tokens")]
43 pub dump_tokens: bool,
44
45 /// Print the parsed AST for the script and exit.
46 #[arg(long = "dump-ast")]
47 pub dump_ast: bool,
48
49 /// Print a fusevm bytecode disassembly listing for the script and exit.
50 #[arg(long = "disasm")]
51 pub disasm: bool,
52
53 /// Run the script, then report which fusevm tiers took each of its chunks.
54 #[arg(long = "tiers")]
55 pub tiers: bool,
56
57 /// Silence all `process.emitWarning` output (Node's `--no-warnings`).
58 #[arg(long = "no-warnings")]
59 pub no_warnings: bool,
60
61 /// Silence DeprecationWarnings only (Node's `--no-deprecation`).
62 #[arg(long = "no-deprecation")]
63 pub no_deprecation: bool,
64
65 /// Suppress the one-time "(Use `node --trace-warnings ...`)" hint on warnings.
66 #[arg(long = "trace-warnings")]
67 pub trace_warnings: bool,
68
69 /// Suppress that hint for DeprecationWarnings (Node's `--trace-deprecation`).
70 #[arg(long = "trace-deprecation")]
71 pub trace_deprecation: bool,
72
73 /// The `.js` script to run (omit with --repl / --lsp / --dap / -e).
74 #[arg(value_name = "FILE")]
75 pub file: Option<String>,
76
77 /// Arguments passed through to the JS program.
78 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
79 pub argv: Vec<String>,
80}
81
82/// Parse the process arguments.
83pub fn parse() -> Cli {
84 Cli::parse()
85}
86
87/// How Node divides the command line, which is NOT the raw `argv` the OS handed
88/// over: the flags the RUNTIME consumed go to `process.execArgv`, and
89/// `process.argv` is `[execPath, entryScript, ...userArgs]` with those flags
90/// removed. The split is entry-point dependent and observably so —
91/// `node -e 'src' z` reports `execArgv = ["-e","src"]` and `argv = [exec,"z"]`,
92/// with NO `argv[1]`, while `node s.js z` reports `execArgv = []` and
93/// `argv = [exec, "/abs/s.js", "z"]`. Measured on node v26.7.0.
94pub struct Argv {
95 /// Runtime flags, including `-e`/`--eval` and its source.
96 pub exec: Vec<String>,
97 /// The entry script as given, or `None` for `-e` and for stdin.
98 pub script: Option<String>,
99 /// Everything after the entry point, passed through to the program.
100 pub user: Vec<String>,
101}
102
103/// Compute [`Argv`] from the raw command line.
104///
105/// This walks the raw arguments rather than reading the parsed [`Cli`], because
106/// clap assigns the FIRST positional to `file` even under `-e`, where that
107/// positional is really the program's own first argument.
108pub fn split_argv<I: IntoIterator<Item = String>>(raw: I) -> Argv {
109 let mut out = Argv {
110 exec: Vec::new(),
111 script: None,
112 user: Vec::new(),
113 };
114 let mut it = raw.into_iter().skip(1);
115 let mut eval_seen = false;
116 while let Some(a) = it.next() {
117 if a == "-e" || a == "--eval" || a == "-p" || a == "--print" {
118 out.exec.push(a);
119 if let Some(src) = it.next() {
120 out.exec.push(src);
121 }
122 eval_seen = true;
123 } else if a.starts_with('-') && a != "-" {
124 // `-` is Node's "read stdin" entry point, not a flag.
125 out.exec.push(a);
126 } else {
127 // The first non-flag ends the runtime's own arguments. Under `-e`
128 // there is no entry script, so it is already a program argument.
129 if !eval_seen {
130 out.script = Some(a);
131 } else {
132 out.user.push(a);
133 }
134 out.user.extend(it);
135 break;
136 }
137 }
138 out
139}