Skip to content

Commit afaedcb

Browse files
committed
refactor(pm): remove unused package manager selector
1 parent fbfc9eb commit afaedcb

6 files changed

Lines changed: 11 additions & 299 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/vp_error/src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,6 @@ pub enum Error {
7474
#[error(transparent)]
7575
JoinError(#[from] tokio::task::JoinError),
7676

77-
#[error("User cancelled by Ctrl+C")]
78-
UserCancelled,
79-
8077
#[error("Hash mismatch: expected {expected}, got {actual}")]
8178
HashMismatch { expected: Str, actual: Str },
8279

crates/vp_pm_cli/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ backon = { workspace = true }
1212
base64-simd = { workspace = true }
1313
clap = { workspace = true, features = ["derive"] }
1414
cow-utils = { workspace = true }
15-
crossterm = { workspace = true }
1615
flate2 = { workspace = true }
1716
futures-util = { workspace = true }
1817
hex = { workspace = true }

crates/vp_pm_cli/src/package_manager.rs

Lines changed: 3 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
1-
#![allow(clippy::disallowed_macros, clippy::print_stdout)]
1+
#![allow(clippy::disallowed_macros)]
2+
#![cfg_attr(test, allow(clippy::print_stdout))]
23

34
use std::{
45
collections::HashMap,
56
env, fmt,
67
fs::{self, File},
7-
io::{self, BufReader, Write},
8+
io::{self, BufReader},
89
path::{Path, PathBuf},
910
time::Duration,
1011
};
1112

12-
use crossterm::{
13-
cursor,
14-
event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
15-
execute,
16-
style::{Color, Print, ResetColor, SetForegroundColor},
17-
terminal,
18-
};
1913
use semver::Version;
2014
use serde::{Deserialize, Serialize};
2115
use tokio::fs::remove_dir_all;
@@ -260,21 +254,6 @@ impl PackageManagerBuilder {
260254
bin_prefix: install_dir.join("bin"),
261255
})
262256
}
263-
264-
/// Build the package manager with default package manager.
265-
/// If the package manager is not specified, prompt the user to select a package manager.
266-
pub async fn build_with_default(&self) -> Result<PackageManager, Error> {
267-
let package_manager = match self.build().await {
268-
Ok(pm) => pm,
269-
Err(Error::UnrecognizedPackageManager) => {
270-
// Prompt user to select a package manager
271-
let selected_type = prompt_package_manager_selection()?;
272-
Self::new(&self.cwd).package_manager_type(selected_type).build().await?
273-
}
274-
Err(e) => return Err(e),
275-
};
276-
Ok(package_manager)
277-
}
278257
}
279258

280259
// Version gates and migration must use the npm on PATH, not the latest registry release.
@@ -1876,223 +1855,6 @@ async fn create_bun_shim_files(bin_prefix: &AbsolutePath) -> Result<(), Error> {
18761855
Ok(())
18771856
}
18781857

1879-
use vp_shared::is_ci_environment;
1880-
1881-
/// Interactive menu for selecting a package manager with keyboard navigation
1882-
fn interactive_package_manager_menu() -> Result<PackageManagerType, Error> {
1883-
let options = [
1884-
("pnpm (recommended)", PackageManagerType::Pnpm),
1885-
("npm", PackageManagerType::Npm),
1886-
("yarn", PackageManagerType::Yarn),
1887-
("bun", PackageManagerType::Bun),
1888-
];
1889-
1890-
let mut selected_index = 0;
1891-
1892-
// Print header and instructions with proper line breaks
1893-
println!("\nNo package manager detected. Please select one:");
1894-
println!(
1895-
" Use ↑↓ arrows to navigate, Enter to select, 1-{} for quick selection",
1896-
options.len()
1897-
);
1898-
println!(" Press Esc, q, or Ctrl+C to cancel installation\n");
1899-
1900-
// Enable raw mode for keyboard input
1901-
terminal::enable_raw_mode()?;
1902-
1903-
// Clear the selection area and hide cursor
1904-
execute!(io::stdout(), cursor::Hide)?;
1905-
1906-
let result = loop {
1907-
// Display menu with current selection
1908-
for (i, (name, _)) in options.iter().enumerate() {
1909-
execute!(io::stdout(), cursor::MoveToColumn(2))?;
1910-
1911-
if i == selected_index {
1912-
// Highlight selected item
1913-
execute!(
1914-
io::stdout(),
1915-
SetForegroundColor(Color::Blue),
1916-
Print("▶ "),
1917-
Print(format!("[{}] ", i + 1)),
1918-
Print(name),
1919-
ResetColor,
1920-
Print(" ← ")
1921-
)?;
1922-
} else {
1923-
execute!(
1924-
io::stdout(),
1925-
Print(" "),
1926-
SetForegroundColor(Color::DarkGrey),
1927-
Print(format!("[{}] ", i + 1)),
1928-
ResetColor,
1929-
Print(name),
1930-
Print(" ")
1931-
)?;
1932-
}
1933-
1934-
if i < options.len() - 1 {
1935-
execute!(io::stdout(), Print("\n"))?;
1936-
}
1937-
}
1938-
1939-
// Move cursor back up for next iteration
1940-
if options.len() > 1 {
1941-
execute!(io::stdout(), cursor::MoveUp((options.len() - 1) as u16))?;
1942-
}
1943-
1944-
// Read keyboard input, skipping non-Press events (e.g. Release on Windows)
1945-
let (code, modifiers) = loop {
1946-
if let Event::Key(KeyEvent { code, modifiers, kind, .. }) = event::read()?
1947-
&& kind == KeyEventKind::Press
1948-
{
1949-
break (code, modifiers);
1950-
}
1951-
};
1952-
1953-
match code {
1954-
// Handle Ctrl+C for exit
1955-
KeyCode::Char('c') if modifiers.contains(event::KeyModifiers::CONTROL) => {
1956-
// Clean up terminal before exiting
1957-
terminal::disable_raw_mode()?;
1958-
execute!(
1959-
io::stdout(),
1960-
cursor::Show,
1961-
cursor::MoveDown(options.len() as u16),
1962-
Print("\n\n"),
1963-
SetForegroundColor(Color::Yellow),
1964-
Print("⚠ Installation cancelled by user\n"),
1965-
ResetColor
1966-
)?;
1967-
return Err(Error::UserCancelled);
1968-
}
1969-
KeyCode::Up => {
1970-
selected_index = selected_index.saturating_sub(1);
1971-
}
1972-
KeyCode::Down if selected_index < options.len() - 1 => {
1973-
selected_index += 1;
1974-
}
1975-
KeyCode::Enter | KeyCode::Char(' ') => {
1976-
break Ok(options[selected_index].1);
1977-
}
1978-
KeyCode::Char('1') => {
1979-
break Ok(options[0].1);
1980-
}
1981-
KeyCode::Char('2') if options.len() > 1 => {
1982-
break Ok(options[1].1);
1983-
}
1984-
KeyCode::Char('3') if options.len() > 2 => {
1985-
break Ok(options[2].1);
1986-
}
1987-
KeyCode::Char('4') if options.len() > 3 => {
1988-
break Ok(options[3].1);
1989-
}
1990-
KeyCode::Esc | KeyCode::Char('q') => {
1991-
// Exit on escape/quit
1992-
terminal::disable_raw_mode()?;
1993-
execute!(
1994-
io::stdout(),
1995-
cursor::Show,
1996-
cursor::MoveDown(options.len() as u16),
1997-
Print("\n\n"),
1998-
SetForegroundColor(Color::Yellow),
1999-
Print("⚠ Installation cancelled by user\n"),
2000-
ResetColor
2001-
)?;
2002-
return Err(Error::UserCancelled);
2003-
}
2004-
_ => {}
2005-
}
2006-
};
2007-
2008-
// Clean up: disable raw mode and show cursor
2009-
terminal::disable_raw_mode()?;
2010-
execute!(io::stdout(), cursor::Show, cursor::MoveDown(options.len() as u16), Print("\n"))?;
2011-
2012-
// Print selection confirmation
2013-
if let Ok(pm) = &result {
2014-
let name = match pm {
2015-
PackageManagerType::Pnpm => "pnpm",
2016-
PackageManagerType::Npm => "npm",
2017-
PackageManagerType::Yarn => "yarn",
2018-
PackageManagerType::Bun => "bun",
2019-
};
2020-
println!("\n✓ Selected package manager: {name}\n");
2021-
}
2022-
2023-
result
2024-
}
2025-
2026-
/// Prompt the user to select a package manager
2027-
fn prompt_package_manager_selection() -> Result<PackageManagerType, Error> {
2028-
// In CI environment, automatically use pnpm without prompting
2029-
if is_ci_environment() {
2030-
tracing::info!("CI environment detected. Using default package manager: pnpm");
2031-
return Ok(PackageManagerType::Pnpm);
2032-
}
2033-
2034-
// Check if stdin is a TTY (terminal) - if not, use default
2035-
if !vp_shared::is_stdin_terminal() {
2036-
tracing::info!("Non-interactive environment detected. Using default package manager: pnpm");
2037-
return Ok(PackageManagerType::Pnpm);
2038-
}
2039-
2040-
// Try interactive menu first, fall back to simple prompt on error
2041-
match interactive_package_manager_menu() {
2042-
Ok(pm) => Ok(pm),
2043-
Err(err) => {
2044-
match err {
2045-
Error::UserCancelled => Err(err),
2046-
// Fallback to simple text prompt if interactive menu fails
2047-
_ => simple_text_prompt(),
2048-
}
2049-
}
2050-
}
2051-
}
2052-
2053-
/// Simple text-based prompt as fallback
2054-
fn simple_text_prompt() -> Result<PackageManagerType, Error> {
2055-
let managers = [
2056-
("pnpm", PackageManagerType::Pnpm),
2057-
("npm", PackageManagerType::Npm),
2058-
("yarn", PackageManagerType::Yarn),
2059-
("bun", PackageManagerType::Bun),
2060-
];
2061-
2062-
println!("\nNo package manager detected. Please select one:");
2063-
println!("────────────────────────────────────────────────");
2064-
2065-
for (i, (name, _)) in managers.iter().enumerate() {
2066-
if i == 0 {
2067-
println!(" [{}] {} (recommended)", i + 1, name);
2068-
} else {
2069-
println!(" [{}] {}", i + 1, name);
2070-
}
2071-
}
2072-
2073-
print!("\nEnter your choice (1-{}) [default: 1]: ", managers.len());
2074-
io::stdout().flush()?;
2075-
2076-
let mut input = String::new();
2077-
io::stdin().read_line(&mut input)?;
2078-
2079-
let choice = input.trim();
2080-
let index = if choice.is_empty() {
2081-
0 // Default to pnpm
2082-
} else {
2083-
choice
2084-
.parse::<usize>()
2085-
.ok()
2086-
.and_then(|n| if n > 0 && n <= managers.len() { Some(n - 1) } else { None })
2087-
.unwrap_or(0) // Default to pnpm if invalid input
2088-
};
2089-
2090-
let (name, selected_type) = &managers[index];
2091-
println!("✓ Selected package manager: {name}\n");
2092-
2093-
Ok(*selected_type)
2094-
}
2095-
20961858
#[cfg(test)]
20971859
mod tests {
20981860
use std::{fs, time::UNIX_EPOCH};

packages/cli/binding/src/lib.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,10 @@ pub async fn run(options: CliOptions) -> Result<i32> {
250250

251251
match result {
252252
Ok(exit_status) => Ok(exit_status.0.into()),
253-
Err(e) => match e {
254-
vp_error::Error::UserCancelled => Ok(130),
255-
_ => {
256-
tracing::error!("Rust error: {:?}", e);
257-
Err(napi::Error::from_reason(format_error_message(&e)))
258-
}
259-
},
253+
Err(e) => {
254+
tracing::error!("Rust error: {:?}", e);
255+
Err(napi::Error::from_reason(format_error_message(&e)))
256+
}
260257
}
261258
}
262259

rfcs/package-manager-detection.md

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -90,50 +90,9 @@ Lower-priority config files that indicate a package manager:
9090

9191
If a caller provides a default package manager type (used internally by some code paths), that default is used with version `"latest"`.
9292

93-
### Priority 6: Interactive selection
93+
Package-manager commands provide pnpm as the default, including outside a project, without prompting or creating a manifest. Without a caller-provided default, Rust detection returns an error when no manager is recognized.
9494

95-
If no signals are detected and no default is provided, the behavior depends on the environment:
96-
97-
#### CI environment
98-
99-
Checks for common CI environment variables:
100-
101-
- `CI`, `CONTINUOUS_INTEGRATION`, `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, `TRAVIS`, `JENKINS_URL`, `BUILDKITE`, `DRONE`, `CODEBUILD_BUILD_ID` (AWS CodeBuild), `TF_BUILD` (Azure Pipelines)
102-
103-
**Result**: Auto-selects `pnpm` without prompting.
104-
105-
#### Non-interactive terminal
106-
107-
If stdin is not a TTY (piped input, non-interactive shell):
108-
109-
**Result**: Auto-selects `pnpm` without prompting.
110-
111-
#### Interactive terminal
112-
113-
Displays a keyboard-navigable menu:
114-
115-
```
116-
No package manager detected. Please select one:
117-
Use ↑↓ arrows to navigate, Enter to select, 1-4 for quick selection
118-
119-
▶ [1] pnpm (recommended) ←
120-
[2] npm
121-
[3] yarn
122-
[4] bun
123-
```
124-
125-
If the interactive menu fails (terminal compatibility issues), falls back to a simple text prompt:
126-
127-
```
128-
No package manager detected. Please select one:
129-
────────────────────────────────────────────────
130-
[1] pnpm (recommended)
131-
[2] npm
132-
[3] yarn
133-
[4] bun
134-
135-
Enter your choice (1-4) [default: 1]:
136-
```
95+
`vp create` and `vp migrate` retain their TypeScript package-manager selector when no manager is detected. In non-interactive mode, it defaults to pnpm.
13796

13897
## CLI Flag: `--package-manager`
13998

@@ -154,7 +113,7 @@ This ensures monorepo consistency while allowing standalone projects to override
154113

155114
## Non-Mutating Resolution
156115

157-
Detection and download never rewrite `package.json`. A `devEngines.packageManager` range remains the source of truth, while lockfile, config, and interactive detection resolve a managed package manager for the current command without adding a manifest field.
116+
Detection and download never rewrite `package.json`. A `devEngines.packageManager` range remains the source of truth, while lockfile, config, and default detection resolve a managed package manager for the current command without adding a manifest field.
158117

159118
Projects that require a deterministic declaration can pin it explicitly with `vp env pin <package-manager>@<version>`. Commands that modify dependencies, including `vp install` and `vp add`, require an existing `package.json` instead of creating one automatically.
160119

@@ -246,7 +205,6 @@ Each package manager has specific files that trigger cache invalidation when cha
246205

247206
- **File**: `crates/vp_pm_cli/src/package_manager.rs`
248207
- **Function**: `get_package_manager_type_and_version()` — priority-ordered detection
249-
- **Function**: `prompt_package_manager_selection()` — CI/TTY/interactive fallback
250208
- **Function**: `download_package_manager()` — download, hash, and record the verified pin
251209
- **Function**: `ensure_package_manager_bin()` — resolve the executable, shared with the global shim
252210
- **Function**: `verify_cached_cli_hash()` — compare a pin against the recorded pin
@@ -255,7 +213,7 @@ Each package manager has specific files that trigger cache invalidation when cha
255213
### TypeScript (CLI integration)
256214

257215
- **File**: `packages/cli/src/utils/workspace.ts``detectWorkspace()` wraps NAPI binding
258-
- **File**: `packages/cli/src/utils/prompts.ts``selectPackageManager()` for non-interactive default
216+
- **File**: `packages/cli/src/utils/prompts.ts``selectPackageManager()` for create/migrate prompts and the non-interactive default
259217
- **File**: `packages/cli/src/create/bin.ts``--package-manager` flag handling
260218

261219
### NAPI binding (bridge)

0 commit comments

Comments
 (0)