| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent bc56e4d commit 8e0b89b
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,6 @@ | |||
| 1 | + <!doctype html> | ||
| 2 | + <html> | ||
| 3 | + <body> | ||
| 4 | + <h1>positional root app</h1> | ||
| 5 | + </body> | ||
| 6 | + </html> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,12 +1,15 @@ | |||
| 1 | 1 | # The vendored node_modules/vite/package.json shadows the runner's run-root | |
| 2 | 2 | # `vite` -> core link with a core at a version the CLI never shipped with, | |
| 3 | 3 | # simulating a dependency-bot bump of the `vite` alias without the matching | |
| 4 | - # vite-plus bump (issue #2356). | ||
| 4 | + # vite-plus bump (issue #2356). The app/ subdir vendors a real-vite-shaped | ||
| 5 | + # package, so the positional-root step passes only when the guard checks the | ||
| 6 | + # selected root instead of the workspace cwd. | ||
| 5 | 7 | [[case]] | |
| 6 | 8 | name = "core_version_guard" | |
| 7 | 9 | vp = "local" | |
| 8 | 10 | steps = [ | |
| 9 | 11 | { argv = ["vp", "build"], continue-on-failure = true }, | |
| 10 | 12 | { argv = ["vp", "test"], continue-on-failure = true }, | |
| 13 | + { argv = ["vp", "build", "app"], comment = "the guard checks the positional root, where vite is real Vite" }, | ||
| 11 | 14 | { argv = ["vp", "build"], comment = "VP_SKIP_CORE_VERSION_CHECK=1 skips the guard", envs = [["VP_SKIP_CORE_VERSION_CHECK", "1"]] }, | |
| 12 | 15 | ] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,6 +16,19 @@ error: Failed to resolve vite command: GenericFailure, Error: The project's `vit | |||
| 16 | 16 | error: Failed to resolve test command: GenericFailure, Error: The project's `vite` alias resolves to @voidzero-dev/vite-plus-core@<version>, but this vite-plus CLI requires @voidzero-dev/vite-plus-core@<version>: the two packages are published in lockstep and other pairings are untested. A dependency bot usually causes this by updating vite-plus and the `vite` alias in separate PRs. Update the `vite` alias to npm:@voidzero-dev/vite-plus-core@<version> where it is declared (catalog, overrides, resolutions, or dependencies), or run `vp migrate` to realign it. Set VP_SKIP_CORE_VERSION_CHECK=1 to skip this check. | |
| 17 | 17 | ``` | |
| 18 | 18 | ||
| 19 | + ## `vp build app` | ||
| 20 | + | ||
| 21 | + the guard checks the positional root, where vite is real Vite | ||
| 22 | + | ||
| 23 | + ``` | ||
| 24 | + note: `vp build app` sets Vite's root without changing the working directory. To run as if started there, use `vp -C app build`. | ||
| 25 | + ✓ 2 modules transformed. | ||
| 26 | + computing gzip size... | ||
| 27 | + app/dist/index.html <size> kB │ gzip: <size> kB | ||
| 28 | + | ||
| 29 | + ✓ built in <duration> | ||
| 30 | + ``` | ||
| 31 | + | ||
| 19 | 32 | ## `VP_SKIP_CORE_VERSION_CHECK=1 vp build` | |
| 20 | 33 | ||
| 21 | 34 | VP_SKIP_CORE_VERSION_CHECK=1 skips the guard | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -153,6 +153,62 @@ fn classify_args<'a>(command: &str, args: &'a [String]) -> ArgTarget<'a> { | |||
| 153 | 153 | ArgTarget::Bare | |
| 154 | 154 | } | |
| 155 | 155 | ||
| 156 | + /// The directory Vite loads the app config from, when the args select one: | ||
| 157 | + /// the parent of an explicit `-c`/`--config` file (which wins over a | ||
| 158 | + /// positional), else the `[root]` positional. `None` means the command's cwd. | ||
| 159 | + /// Walks the args like [`classify_args`] (cac/mri value consumption), but | ||
| 160 | + /// scans them all instead of returning at the first target, because Vite | ||
| 161 | + /// accepts `--config` and `[root]` in either order. | ||
| 162 | + /// | ||
| 163 | + /// Used to aim the core-version guard at the copy of `vite` the app's config | ||
| 164 | + /// and plugins will import: Vite keeps the process cwd unchanged and rebases | ||
| 165 | + /// config lookup onto the selected root. | ||
| 166 | + pub(super) fn vite_config_dir(args: &[String], cwd: &AbsolutePath) -> Option<AbsolutePathBuf> { | ||
| 167 | + let mut positional: Option<&str> = None; | ||
| 168 | + let mut config: Option<&str> = None; | ||
| 169 | + let mut iter = args.iter().peekable(); | ||
| 170 | + while let Some(arg) = iter.next() { | ||
| 171 | + if !arg.starts_with('-') { | ||
| 172 | + positional.get_or_insert(arg); | ||
| 173 | + continue; | ||
| 174 | + } | ||
| 175 | + // `--` terminates options: the first following token is the | ||
| 176 | + // positional, and nothing after it can be a flag. | ||
| 177 | + if arg == "--" { | ||
| 178 | + if positional.is_none() { | ||
| 179 | + positional = iter.next().map(String::as_str); | ||
| 180 | + } | ||
| 181 | + break; | ||
| 182 | + } | ||
| 183 | + if arg == "-c" || arg == "--config" { | ||
| 184 | + if let Some(next) = iter.peek() { | ||
| 185 | + if !next.starts_with('-') { | ||
| 186 | + config = iter.next().map(String::as_str); | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + continue; | ||
| 190 | + } | ||
| 191 | + if let Some(value) = arg.strip_prefix("-c=").or_else(|| arg.strip_prefix("--config=")) { | ||
| 192 | + config = Some(value); | ||
| 193 | + continue; | ||
| 194 | + } | ||
| 195 | + let is_boolean = VITE_BOOLEAN_FLAGS.contains(&arg.as_str()) || arg.starts_with("--no-"); | ||
| 196 | + if !is_boolean | ||
| 197 | + && !arg.contains('=') | ||
| 198 | + && iter.peek().is_some_and(|next| !next.starts_with('-')) | ||
| 199 | + { | ||
| 200 | + iter.next(); | ||
| 201 | + } | ||
| 202 | + } | ||
| 203 | + if let Some(config) = config { | ||
| 204 | + // The config's parent dir, resolved like Vite resolves `--config` | ||
| 205 | + // (relative to cwd). An empty parent means the config sits in cwd. | ||
| 206 | + let parent = std::path::Path::new(config).parent().unwrap_or(std::path::Path::new("")); | ||
| 207 | + return Some(cwd.join(parent).clean()); | ||
| 208 | + } | ||
| 209 | + positional.map(|root| cwd.join(root).clean()) | ||
| 210 | + } | ||
| 211 | + | ||
| 156 | 212 | /// Heuristic ranking signal: does a directory look runnable for `command`? | |
| 157 | 213 | /// Used for ordering and single-candidate auto-selection, never for hiding. | |
| 158 | 214 | /// The rules are documented in rfcs/cwd-flag.md ("The likely-runnable | |
@@ -513,6 +569,44 @@ pub(super) fn resolve_app_target( | |||
| 513 | 569 | mod tests { | |
| 514 | 570 | use super::*; | |
| 515 | 571 | ||
| 572 | + #[test] | ||
| 573 | + fn vite_config_dir_selects_the_app_dir() { | ||
| 574 | + let to_args = |args: &[&str]| args.iter().map(|s| (*s).to_string()).collect::<Vec<_>>(); | ||
| 575 | + let root = if cfg!(windows) { "C:\\ws" } else { "/ws" }; | ||
| 576 | + let cwd = AbsolutePath::new(root).unwrap(); | ||
| 577 | + let dir = |rel: &str| Some(cwd.join(rel).clean()); | ||
| 578 | + | ||
| 579 | + // No target selection: fall back to the command's cwd. | ||
| 580 | + assert_eq!(vite_config_dir(&to_args(&[]), cwd), None); | ||
| 581 | + assert_eq!(vite_config_dir(&to_args(&["--mode", "production"]), cwd), None); | ||
| 582 | + | ||
| 583 | + // The `[root]` positional, wherever cac would see one. | ||
| 584 | + assert_eq!(vite_config_dir(&to_args(&["apps/web"]), cwd), dir("apps/web")); | ||
| 585 | + assert_eq!( | ||
| 586 | + vite_config_dir(&to_args(&["--mode", "production", "apps/web"]), cwd), | ||
| 587 | + dir("apps/web") | ||
| 588 | + ); | ||
| 589 | + assert_eq!(vite_config_dir(&to_args(&["-w", "apps/web"]), cwd), dir("apps/web")); | ||
| 590 | + assert_eq!(vite_config_dir(&to_args(&["--", "apps/web"]), cwd), dir("apps/web")); | ||
| 591 | + | ||
| 592 | + // An explicit config wins over the positional in either order; its | ||
| 593 | + // parent is where the config's imports resolve from. | ||
| 594 | + assert_eq!( | ||
| 595 | + vite_config_dir(&to_args(&["-c", "apps/web/vite.config.ts"]), cwd), | ||
| 596 | + dir("apps/web") | ||
| 597 | + ); | ||
| 598 | + assert_eq!( | ||
| 599 | + vite_config_dir(&to_args(&["apps/web", "--config=conf/vite.config.ts"]), cwd), | ||
| 600 | + dir("conf") | ||
| 601 | + ); | ||
| 602 | + assert_eq!( | ||
| 603 | + vite_config_dir(&to_args(&["-c", "conf/vite.config.ts", "apps/web"]), cwd), | ||
| 604 | + dir("conf") | ||
| 605 | + ); | ||
| 606 | + // A config in the cwd itself keeps the cwd as the guard dir. | ||
| 607 | + assert_eq!(vite_config_dir(&to_args(&["-c", "vite.config.ts"]), cwd), dir("")); | ||
| 608 | + } | ||
| 609 | + | ||
| 516 | 610 | #[test] | |
| 517 | 611 | fn bare_means_no_positional_target_and_no_help() { | |
| 518 | 612 | let to_args = |args: &[&str]| args.iter().map(|s| (*s).to_string()).collect::<Vec<_>>(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,25 @@ use super::{ | |||
| 12 | 12 | types::{CliOptions, ResolvedSubcommand, ResolvedUniversalViteConfig, SynthesizableSubcommand}, | |
| 13 | 13 | }; | |
| 14 | 14 | ||
| 15 | + /// The directory string handed to the JS `vite` resolver: the Vite app dir | ||
| 16 | + /// selected by a `[root]` positional or an explicit `-c`/`--config` file | ||
| 17 | + /// (where the app's config and plugins resolve `vite` from), falling back to | ||
| 18 | + /// the command's cwd. | ||
| 19 | + fn vite_resolver_dir( | ||
| 20 | + args: &[String], | ||
| 21 | + cwd: &AbsolutePath, | ||
| 22 | + cwd_string: &str, | ||
| 23 | + ) -> anyhow::Result<String> { | ||
| 24 | + match super::app_target::vite_config_dir(args, cwd) { | ||
| 25 | + Some(dir) => Ok(dir | ||
| 26 | + .as_path() | ||
| 27 | + .to_str() | ||
| 28 | + .ok_or_else(|| anyhow::anyhow!("vite root is not valid UTF-8"))? | ||
| 29 | + .to_string()), | ||
| 30 | + None => Ok(cwd_string.to_string()), | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + | ||
| 15 | 34 | /// Resolves synthesizable subcommands to concrete programs and arguments. | |
| 16 | 35 | /// Used by both direct CLI execution and CommandHandler. | |
| 17 | 36 | pub struct SubcommandResolver { | |
@@ -162,7 +181,8 @@ impl SubcommandResolver { | |||
| 162 | 181 | } | |
| 163 | 182 | SynthesizableSubcommand::Build { args } => { | |
| 164 | 183 | let cli_options = self.cli_options()?; | |
| 165 | - let resolved = (cli_options.vite)(cwd_string.clone()).await?; | ||
| 184 | + let resolved = | ||
| 185 | + (cli_options.vite)(vite_resolver_dir(&args, cwd, &cwd_string)?).await?; | ||
| 166 | 186 | let js_path = resolved.bin_path; | |
| 167 | 187 | let js_path_str = js_path | |
| 168 | 188 | .to_str() | |
@@ -245,7 +265,8 @@ impl SubcommandResolver { | |||
| 245 | 265 | } | |
| 246 | 266 | SynthesizableSubcommand::Dev { args } => { | |
| 247 | 267 | let cli_options = self.cli_options()?; | |
| 248 | - let resolved = (cli_options.vite)(cwd_string.clone()).await?; | ||
| 268 | + let resolved = | ||
| 269 | + (cli_options.vite)(vite_resolver_dir(&args, cwd, &cwd_string)?).await?; | ||
| 249 | 270 | let js_path = resolved.bin_path; | |
| 250 | 271 | let js_path_str = js_path | |
| 251 | 272 | .to_str() | |
@@ -263,7 +284,8 @@ impl SubcommandResolver { | |||
| 263 | 284 | } | |
| 264 | 285 | SynthesizableSubcommand::Preview { args } => { | |
| 265 | 286 | let cli_options = self.cli_options()?; | |
| 266 | - let resolved = (cli_options.vite)(cwd_string.clone()).await?; | ||
| 287 | + let resolved = | ||
| 288 | + (cli_options.vite)(vite_resolver_dir(&args, cwd, &cwd_string)?).await?; | ||
| 267 | 289 | let js_path = resolved.bin_path; | |
| 268 | 290 | let js_path_str = js_path | |
| 269 | 291 | .to_str() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments