Skip to main content

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}