Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/cli/binding/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ pub enum Commands {
/// Arguments to pass to vite dev
args: Vec<String>,
},
/// Preview production build
Preview {
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
/// Arguments to pass to vite preview
args: Vec<String>,
},
/// Build documentation
Doc {
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
Expand Down Expand Up @@ -431,6 +437,13 @@ pub async fn main<
workspace.unload().await?;
summary
}
Commands::Preview { args } => {
let workspace = Workspace::partial_load(cwd)?;
let vite_fn = options.map(|o| o.vite).expect("preview command requires CliOptions");
let summary = vite_cmd("preview", vite_fn, &workspace, args).await?;
workspace.unload().await?;
summary
}
Commands::Doc { args } => {
let workspace = Workspace::partial_load(cwd)?;
let doc_fn = options.map(|o| o.doc).expect("doc command requires CliOptions");
Expand Down Expand Up @@ -941,6 +954,28 @@ mod tests {
}
}

#[test]
fn test_args_preview_command() {
let args = Args::try_parse_from(["vite-plus", "preview"]).unwrap();
assert_eq!(args.task, None);
assert!(args.task_args.is_empty());
assert!(matches!(args.commands, Commands::Preview { .. }));
assert!(!args.debug);
}

#[test]
fn test_args_preview_command_with_args() {
let args =
Args::try_parse_from(["vite-plus", "preview", "--port", "3000", "--host"]).unwrap();
assert_eq!(args.task, None);
assert!(args.task_args.is_empty());
if let Commands::Preview { args } = &args.commands {
assert_eq!(args, &vec!["--port".to_string(), "3000".to_string(), "--host".to_string()]);
} else {
panic!("Expected Preview command");
}
}

#[test]
fn test_args_complex_task_args() {
let args = Args::try_parse_from([
Expand Down
74 changes: 58 additions & 16 deletions packages/cli/binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ impl From<JsCommandResolvedResult> for ResolveCommandResult {
}
}

static BUILTIN_COMMANDS: &[&str] = &["lint", "fmt", "build", "test", "doc", "lib"];
static BUILTIN_COMMANDS: &[&str] =
&["dev", "lint", "fmt", "build", "test", "doc", "lib", "preview"];

/// Main entry point for the CLI, called from JavaScript.
///
Expand Down Expand Up @@ -267,29 +268,70 @@ fn js_error_to_resolve_universal_vite_config_error(err: napi::Error) -> Error {
fn parse_args() -> Args {
// ArgsOs [node, vite-plus, ...]
let mut raw_args = std::env::args_os().skip(2);
if let Some(first) = raw_args.next()
&& let Some(first) = first.to_str()
&& BUILTIN_COMMANDS.contains(&first)
{
let forwarded_args = raw_args

// No arguments provided, default to dev command
let Some(first) = raw_args.next() else {
return Args {
task: None,
task_args: vec![],
commands: Commands::Dev { args: vec![] },
debug: false,
no_debug: true,
};
};

// If first arg is not valid UTF-8, fall through to clap parsing
let Some(first_str) = first.to_str() else {
return Args::parse_from(std::env::args_os().skip(1));
};

// Collect remaining args for potential forwarding
let remaining_args: Vec<_> = raw_args.collect();

// Handle builtin commands with fast-path parsing (bypasses clap for better arg forwarding)
if let Some(cmd) = parse_builtin_command(first_str, &remaining_args) {
return cmd;
}

// If first arg starts with '-' but is NOT a help/version flag, treat as options for dev command
// e.g. `vite --port 3000` should be treated as `vite dev --port 3000`
if first_str.starts_with('-') && !matches!(first_str, "-h" | "--help" | "-V" | "--version") {
Comment thread
fengmk2 marked this conversation as resolved.
let forwarded_args: Vec<String> = std::iter::once(first)
.chain(remaining_args)
.map(|a| a.into_string().unwrap_or_else(|os_str| os_str.to_string_lossy().into_owned()))
.collect();
return Args {
task: None,
task_args: vec![],
commands: match first {
"lint" => Commands::Lint { args: forwarded_args },
"fmt" => Commands::Fmt { args: forwarded_args },
"build" => Commands::Build { args: forwarded_args },
"test" => Commands::Test { args: forwarded_args },
"doc" => Commands::Doc { args: forwarded_args },
"lib" => Commands::Lib { args: forwarded_args },
_ => unreachable!(),
},
commands: Commands::Dev { args: forwarded_args },
debug: false,
no_debug: true,
};
}
// Parse CLI arguments (skip first arg which is the node binary)

// Fall through to clap parsing for other commands (run, cache, install, help, etc.)
Args::parse_from(std::env::args_os().skip(1))
}

fn parse_builtin_command(cmd: &str, raw_args: &[std::ffi::OsString]) -> Option<Args> {
if !BUILTIN_COMMANDS.contains(&cmd) {
return None;
}

let forwarded_args: Vec<String> =
raw_args.iter().map(|a| a.to_string_lossy().into_owned()).collect();

let commands = match cmd {
"dev" => Commands::Dev { args: forwarded_args },
"lint" => Commands::Lint { args: forwarded_args },
"fmt" => Commands::Fmt { args: forwarded_args },
"build" => Commands::Build { args: forwarded_args },
"test" => Commands::Test { args: forwarded_args },
"doc" => Commands::Doc { args: forwarded_args },
"lib" => Commands::Lib { args: forwarded_args },
"preview" => Commands::Preview { args: forwarded_args },
_ => return None,
};

Some(Args { task: None, task_args: vec![], commands, debug: false, no_debug: true })
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/command-dev-with-port/snap.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
> vite dev --port 12312312312 2>&1 | grep RangeError # intentionally use an invalid port (exceeds 0-65535) to trigger RangeError
RangeError [ERR_SOCKET_BAD_PORT]: options.port should be >= 0 and < 65536. Received type number (12312312312).

> vite --port 12312312313 2>&1 | grep RangeError # vite without args should be alias to dev command
RangeError [ERR_SOCKET_BAD_PORT]: options.port should be >= 0 and < 65536. Received type number (12312312313).
3 changes: 2 additions & 1 deletion packages/cli/snap-tests/command-dev-with-port/steps.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"VITE_DISABLE_AUTO_INSTALL": "1"
},
"commands": [
"vite dev --port 12312312312 2>&1 | grep RangeError # intentionally use an invalid port (exceeds 0-65535) to trigger RangeError"
"vite dev --port 12312312312 2>&1 | grep RangeError # intentionally use an invalid port (exceeds 0-65535) to trigger RangeError",
"vite --port 12312312313 2>&1 | grep RangeError # vite without args should be alias to dev command"
]
}
62 changes: 62 additions & 0 deletions packages/cli/snap-tests/command-helper/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Commands:
test Run test
lib Build library
dev Run development server
preview Preview production build
doc Build documentation
cache Manage the task cache
install Install command. It will be passed to the package manager's install command currently
Expand Down Expand Up @@ -333,3 +334,64 @@ Options:
--experimental <features> Experimental features.. Use '--help --experimental' for more info.
-h, --help Display this message


> vite preview -h # preview help message
vite/<semver>

Usage:
$ vite preview [root]

Options:
--host [host] [string] specify hostname
--port <port> [number] specify port
--strictPort [boolean] exit if specified port is already in use
--open [path] [boolean | string] open browser on startup
--outDir <dir> [string] output directory (default: dist)
-c, --config <file> [string] use specified config file
--base <path> [string] public base path (default: /)
-l, --logLevel <level> [string] info | warn | error | silent
--clearScreen [boolean] allow/disable clear screen when logging
--configLoader <loader> [string] use 'bundle' to bundle the config with Rolldown, or 'runner' (experimental) to process it on the fly, or 'native' (experimental) to load using the native runtime (default: bundle)
-d, --debug [feat] [string | boolean] show debug logs
-f, --filter <filter> [string] filter debug logs
-m, --mode <mode> [string] set env mode
-h, --help Display this message


> vite dev -h # dev help message
vite/<semver>

Usage:
$ vite [root]

Commands:
[root] start dev server
build [root] build for production
optimize [root] pre-bundle dependencies (deprecated, the pre-bundle process runs automatically and does not need to be called)
preview [root] locally preview production build

For more info, run any command with the `--help` flag:
$ vite --help
$ vite build --help
$ vite optimize --help
$ vite preview --help

Options:
--host [host] [string] specify hostname
--port <port> [number] specify port
--open [path] [boolean | string] open browser on startup
--cors [boolean] enable CORS
--strictPort [boolean] exit if specified port is already in use
--force [boolean] force the optimizer to ignore the cache and re-bundle
--experimentalBundle [boolean] use experimental full bundle mode (this is highly experimental)
-c, --config <file> [string] use specified config file
--base <path> [string] public base path (default: /)
-l, --logLevel <level> [string] info | warn | error | silent
--clearScreen [boolean] allow/disable clear screen when logging
--configLoader <loader> [string] use 'bundle' to bundle the config with Rolldown, or 'runner' (experimental) to process it on the fly, or 'native' (experimental) to load using the native runtime (default: bundle)
-d, --debug [feat] [string | boolean] show debug logs
-f, --filter <filter> [string] filter debug logs
-m, --mode <mode> [string] set env mode
-h, --help Display this message
-v, --version Display version number

4 changes: 3 additions & 1 deletion packages/cli/snap-tests/command-helper/steps.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"vite fmt -h # fmt help message",
"vite lint -h # lint help message",
"vite build -h # build help message",
"vite test -h # test help message"
"vite test -h # test help message",
"vite preview -h # preview help message",
"vite dev -h # dev help message"
]
}
2 changes: 2 additions & 0 deletions packages/cli/snap-tests/command-preview/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
2 changes: 2 additions & 0 deletions packages/cli/snap-tests/command-preview/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
> vite preview --port 12312312312 2>&1 | grep RangeError # intentionally use an invalid port (exceeds 0-65535) to trigger RangeError
RangeError [ERR_SOCKET_BAD_PORT]: options.port should be >= 0 and < 65536. Received type number (12312312312).
9 changes: 9 additions & 0 deletions packages/cli/snap-tests/command-preview/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"ignoredPlatforms": ["win32"],
"env": {
"VITE_DISABLE_AUTO_INSTALL": "1"
},
"commands": [
"vite preview --port 12312312312 2>&1 | grep RangeError # intentionally use an invalid port (exceeds 0-65535) to trigger RangeError"
]
}
13 changes: 12 additions & 1 deletion packages/global/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
// Parse command line arguments to intercept 'new', 'gen', and 'migration' commands
const args = process.argv.slice(2);

const LOCAL_CLI_COMMANDS = ['dev', 'build', 'test', 'lint', 'fmt', 'format', 'lib', 'doc', 'run'];
const LOCAL_CLI_COMMANDS = [
'dev',
'build',
'test',
'lint',
'fmt',
'format',
'lib',
'doc',
'run',
'preview',
];

const command = args[0];

Expand Down
Loading