Skip to content

Commit 668fb7d

Browse files
committed
fix(cli): address Codex review round 7
- rewrite is_bare to mirror cac/mri parsing: a non-flag token after any non-boolean flag is that flag's value (required and optional alike, so vp dev --host 0.0.0.0 and --open /foo elicit again), and only tokens no flag consumes are positionals; the per-tool required-value tables are replaced by smaller boolean tables derived from the shipped --help - pack workspace selectors (-W/--workspace, -F/--filter) define their own target set and always bypass elicitation - document in the RFC that implicit -C runs inside the already-chosen CLI (single-version workspace model); full re-delegation for defaultPackage targets with their own install/runtime pin is tracked in #2057
1 parent b6d7a29 commit 668fb7d

2 files changed

Lines changed: 69 additions & 83 deletions

File tree

packages/cli/binding/src/cli/app_target.rs

Lines changed: 67 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -44,88 +44,69 @@ fn app_command_parts(subcommand: &SynthesizableSubcommand) -> Option<(&'static s
4444
}
4545
}
4646

47-
/// Required-value flags of the Vite CLI (dev/build/preview). Space-separated
48-
/// values of these flags are not positional targets. Optional-value flags
49-
/// (`--host`, `--open`, `--debug`, `--ssr`, `--sourcemap`, `--minify`, ...)
50-
/// are deliberately absent: a token after them stays ambiguous and keeps the
51-
/// conservative fallback.
52-
const VITE_VALUE_FLAGS: &[&str] = &[
53-
"-c",
54-
"--config",
55-
"--config-loader",
56-
"--log-level",
57-
"--out-dir",
58-
"--assets-dir",
59-
"--assets-inline-limit",
60-
"--base",
61-
"-l",
62-
"--logLevel",
63-
"-m",
64-
"--mode",
65-
"--configLoader",
66-
"-f",
67-
"--filter",
68-
"--port",
69-
"--outDir",
70-
"--assetsDir",
71-
"--assetsInlineLimit",
72-
"--target",
47+
/// Boolean flags of the Vite CLI (dev/build/preview), from the shipped
48+
/// `vp <command> --help` (snap-tests/command-helper); keep in sync. Under
49+
/// cac/mri parsing every OTHER flag — required-value, optional-value
50+
/// (`--host [host]`), or unknown — consumes a following non-flag token as
51+
/// its value, so only tokens no flag consumes are positional targets.
52+
const VITE_BOOLEAN_FLAGS: &[&str] = &[
53+
"-w",
54+
"--watch",
55+
"--app",
56+
"--clearScreen",
57+
"--cors",
58+
"--emptyOutDir",
59+
"--experimentalBundle",
60+
"--force",
61+
"--profile",
62+
"--strictPort",
7363
];
7464

75-
/// Required-value flags of the bundled pack CLI (see pack-bin.ts; cac accepts
76-
/// both camelCase and kebab-case spellings). Optional-value flags (`--debug`,
77-
/// `--watch`, `--from-vite`) are deliberately absent, as above.
78-
const PACK_VALUE_FLAGS: &[&str] = &[
79-
"--root",
80-
"-F",
81-
"--filter",
82-
"-c",
83-
"--config",
84-
"--config-loader",
85-
"--configLoader",
86-
"-f",
87-
"--format",
88-
"--deps.never-bundle",
89-
"--target",
90-
"-l",
91-
"--logLevel",
92-
"--log-level",
93-
"-d",
94-
"--out-dir",
95-
"--outDir",
96-
"--platform",
97-
"--tsconfig",
98-
"--ignore-watch",
99-
"--ignoreWatch",
100-
"--env-file",
101-
"--envFile",
102-
"--env-prefix",
103-
"--envPrefix",
104-
"--on-success",
105-
"--onSuccess",
106-
"--copy",
107-
"--public-dir",
108-
"--publicDir",
65+
/// Boolean flags of the bundled pack CLI (tsdown), from `vp pack --help`.
66+
const PACK_BOOLEAN_FLAGS: &[&str] = &[
67+
"--attw",
68+
"--clean",
69+
"--devtools",
70+
"--dts",
71+
"--exe",
72+
"--exports",
73+
"--fail-on-warn",
74+
"--failOnWarn",
75+
"--minify",
76+
"--no-write",
77+
"--publint",
78+
"--report",
79+
"--shims",
80+
"--sourcemap",
81+
"--treeshake",
82+
"--unbundle",
83+
"--unused",
10984
];
11085

111-
/// Bare = no positional target and no help-like flag. Values of the
112-
/// forwarded tool's known required-value flags (`--port 3000`) are skipped;
113-
/// any other non-flag token may be a positional target and conservatively
114-
/// disables elicitation. Help/version requests are answered by the
86+
/// Bare = no positional target and no help-like flag. Mirrors the tools'
87+
/// own cac/mri parsing: a non-flag token after any non-boolean flag is that
88+
/// flag's value (the tool would never see it as a positional), while a token
89+
/// after a boolean flag is a positional target and disables elicitation.
90+
/// pack's workspace selectors already define their own target set and
91+
/// disable elicitation outright. Help/version requests are answered by the
11592
/// underlying tool and must never be redirected.
11693
fn is_bare(command: &str, args: &[String]) -> bool {
117-
let value_flags = if command == "pack" { PACK_VALUE_FLAGS } else { VITE_VALUE_FLAGS };
118-
let mut iter = args.iter();
94+
let is_pack = command == "pack";
95+
let booleans = if is_pack { PACK_BOOLEAN_FLAGS } else { VITE_BOOLEAN_FLAGS };
96+
let mut iter = args.iter().peekable();
11997
while let Some(arg) = iter.next() {
12098
if !arg.starts_with('-') || super::help::is_app_tool_help_or_version_flag(arg) {
12199
return false;
122100
}
123-
// `--env.NAME <value>` defines a compile-time env variable in pack;
124-
// the inline `--env.NAME=value` form already carries its value.
125-
if value_flags.contains(&arg.as_str())
126-
|| (command == "pack" && arg.starts_with("--env.") && !arg.contains('='))
101+
if is_pack && matches!(arg.as_str(), "-W" | "--workspace" | "-F" | "--filter") {
102+
return false;
103+
}
104+
let is_boolean = booleans.contains(&arg.as_str()) || arg.starts_with("--no-");
105+
// An inline `=` already carries the value (`--port=3000`, `--env.FOO=bar`).
106+
if !is_boolean
107+
&& !arg.contains('=')
108+
&& iter.peek().is_some_and(|next| !next.starts_with('-'))
127109
{
128-
// Consume the flag's value; a missing value is the tool's error.
129110
iter.next();
130111
}
131112
}
@@ -374,21 +355,26 @@ mod tests {
374355
assert!(is_bare("build", &to_args(&["-w", "--minify"])));
375356
// A positional target disables elicitation.
376357
assert!(!is_bare("dev", &to_args(&["apps/web"])));
377-
// Known required-value flags consume their value, per command.
358+
// Like cac, any non-boolean flag consumes a following non-flag token
359+
// as its value — required and optional values alike.
378360
assert!(is_bare("dev", &to_args(&["--port", "3000"])));
361+
assert!(is_bare("dev", &to_args(&["--host", "0.0.0.0"])));
362+
assert!(is_bare("dev", &to_args(&["--open", "/foo"])));
379363
assert!(is_bare("build", &to_args(&["--mode", "production", "--minify"])));
380-
assert!(is_bare("build", &to_args(&["--assetsDir", "assets"])));
381364
assert!(is_bare("build", &to_args(&["--port=3000"])));
382365
assert!(is_bare("pack", &to_args(&["--env-file", ".env"])));
383-
assert!(is_bare("pack", &to_args(&["-d", "out", "--env.FOO", "1"])));
384-
// The tables are command-specific: pack's flags mean nothing to Vite,
385-
// and Vite's optional-value `-d, --debug [feat]` must not consume.
386-
assert!(!is_bare("dev", &to_args(&["--env-file", ".env"])));
387-
assert!(!is_bare("dev", &to_args(&["-d", "apps/web"])));
388-
// A token after an unknown or optional-value flag is ambiguous with a
389-
// positional target, so it conservatively counts as non-bare.
366+
assert!(is_bare("pack", &to_args(&["--env.FOO=bar", "--minify"])));
367+
// A token after a boolean flag is a positional; the tables are
368+
// command-specific (--minify is optional-value for Vite build,
369+
// boolean for pack).
390370
assert!(!is_bare("build", &to_args(&["--watch", "apps/web"])));
391-
assert!(!is_bare("dev", &to_args(&["--host", "0.0.0.0"])));
371+
assert!(!is_bare("pack", &to_args(&["--minify", "src/index.ts"])));
372+
assert!(!is_bare("pack", &to_args(&["--env.FOO", "bar", "src/cli.ts"])));
373+
assert!(is_bare("build", &to_args(&["--minify", "esbuild"])));
374+
// pack workspace selectors define their own target set.
375+
assert!(!is_bare("pack", &to_args(&["-W"])));
376+
assert!(!is_bare("pack", &to_args(&["--workspace", "packages/a"])));
377+
assert!(!is_bare("pack", &to_args(&["-F", "ui"])));
392378
// Help/version requests go to the underlying tool, never elicitation.
393379
assert!(!is_bare("dev", &to_args(&["--help"])));
394380
assert!(!is_bare("dev", &to_args(&["-h"])));

rfcs/cwd-flag.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ Rejected alternatives: repurposing the app-command positional to mean "run there
246246

247247
### Target directory resolution
248248

249-
An app command invocation is **bare** when it has no `-C` and no positional target (no Vite `[root]`, no pack entries). Flags keep it bare, including the forwarded tool's known required-value flags together with their values (`--port 3000` for Vite commands, `--env-file .env` for pack); a token following an unknown or optional-value flag is ambiguous with a positional target and conservatively counts as one. For `vp dev` / `build` / `preview` / `pack`, the target directory is resolved in this order:
249+
An app command invocation is **bare** when it has no `-C` and no positional target (no Vite `[root]`, no pack entries). The classification mirrors the tools' own cac parsing: a non-flag token following any non-boolean flag is that flag's value — required and optional values alike (`--port 3000`, `--host 0.0.0.0`) — because the tool itself would never treat it as a positional; only a token no flag consumes is a positional target, and any positional disables elicitation. The boolean-flag tables come from the shipped `--help` of each tool and are command-specific (`--minify` is optional-value for Vite build but boolean for pack). pack's workspace selectors (`-W`/`--workspace`, `-F`/`--filter`) already define their own target set and always disable elicitation. For `vp dev` / `build` / `preview` / `pack`, the target directory is resolved in this order:
250250

251251
1. **`-C <dir>`**: run there. Never triggers the picker.
252252
2. **Positional target present**: forward as today, upstream semantics, vp does not interfere.
@@ -267,7 +267,7 @@ vp -C <dir> <cmd> [args...] === cd <dir> && vp <cmd> [args...]
267267

268268
The child's spawn cwd is `<dir>`, so config lookup, `.env` loading, `process.cwd()` reads in configs and plugins, and relative CLI args all behave as if the user had `cd`'d. The POSIX `PWD` environment variable is refreshed too (by both entry points and for elicitation-retargeted tools), so `process.env.PWD` readers match the `cd` form. Both entry points apply `-C` by changing their own process cwd at startup, before any argument normalization, picker, or command logic runs, which is indistinguishable from having been started in `<dir>`: the global binary consumes the flag first thing in `main` (so command aliases, the no-command picker, and in-process helpers that read the process cwd are all covered), and the local `vp` bin does the same before dispatch.
269269

270-
The global binary also resolves the local `vite-plus` install from `<dir>`, matching `cd` exactly; through the package's own `vp` bin the executing CLI is already chosen, so there the invariant assumes a single Vite+ version per workspace (the supported monorepo model).
270+
The global binary also resolves the local `vite-plus` install from `<dir>`, matching `cd` exactly; through the package's own `vp` bin the executing CLI is already chosen, so there the invariant assumes a single Vite+ version per workspace (the supported monorepo model). The implicit `-C` forms (picker, auto-select, `defaultPackage`) run inside the already-chosen CLI under the same assumption: the spawned tool gets the target's cwd and `PWD`, but the executing CLI and its runtime were resolved at the invocation directory. Within one workspace the two coincide; a `defaultPackage` target carrying its own Vite+ install or runtime pin is resolved by the invoking CLI today (full re-delegation is tracked as follow-up work).
271271

272272
### Entry points and version assumption
273273

0 commit comments

Comments
 (0)