Skip to content
Draft
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
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
VITE+ - The Unified Toolchain for the Web

info: Installing 2 global packages with Node.js <version>
⠿ Installing global packages (<n>/2) npm error code E404
⠿ Installing global packages (<n>/2) <duration> npm error code E404
npm error 404 Not Found - GET http://127.0.0.1:<port>/voidzero-nonexistent-pkg-xyz-23456 - <message>
npm error 404
npm error 404 The requested resource 'voidzero-nonexistent-pkg-xyz-23456@*' could not be found or you do not have permission to access it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Install non-existent package
VITE+ - The Unified Toolchain for the Web

info: Installing 1 global package with Node.js <version>
⠿ Installing global packages (<n>/1) npm error code E404
⠿ Installing global packages (<n>/1) <duration> npm error code E404
npm error 404 Not Found - GET http://127.0.0.1:<port>/voidzero-nonexistent-pkg-xyz-12345 - <message>
npm error 404
npm error 404 The requested resource 'voidzero-nonexistent-pkg-xyz-12345@*' could not be found or you do not have permission to access it.
Expand Down
1 change: 0 additions & 1 deletion crates/vp_global_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ vt_pty_terminal_test_client = { workspace = true, features = ["testing"] }
rustc-hash = { workspace = true }
crossterm = { workspace = true }
indexmap = { workspace = true }
indicatif = { workspace = true }
indoc = { workspace = true }
js-semver = { workspace = true }
vp_error = { workspace = true }
Expand Down
27 changes: 11 additions & 16 deletions crates/vp_global_cli/src/commands/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ use std::{
fs::{File, OpenOptions, TryLockError},
io::{Read, Write},
process::Stdio,
time::Duration,
};

use console::style;
use futures::{StreamExt, stream::FuturesUnordered};
use indexmap::IndexMap;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use tokio::process::Command;
use uuid::Uuid;
use vp_js_runtime::NodeProvider;
use vp_shared::{PrependOptions, ToolPathEnv, output};
use vp_shared::{PrependOptions, ToolPathEnv, output, progress::Progress};
use vt_path::{AbsolutePath, AbsolutePathBuf, current_dir};

use crate::{
Expand Down Expand Up @@ -212,17 +210,10 @@ pub async fn install(
node_version
));

let progress = ProgressBar::new(packages_count as u64);
if vp_shared::is_stderr_terminal() && std::env::var_os("CI").is_none() {
let style = ProgressStyle::with_template("{spinner:.cyan} {msg} ({pos}/{len})")
.unwrap_or_else(|_| ProgressStyle::default_spinner())
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]);
progress.set_style(style);
progress.set_message(format!("{} global packages", operation_progress));
progress.enable_steady_tick(Duration::from_millis(80));
} else {
progress.set_draw_target(ProgressDrawTarget::hidden());
}
let progress = Progress::counter(
&format!("{} global packages", operation_progress),
packages_count as u64,
);

// We have to clone it because we will modify `packages` to storage package names
let package_names = packages.keys().cloned().collect::<Vec<_>>();
Expand Down Expand Up @@ -258,7 +249,9 @@ pub async fn install(

match installs.next().await {
Some((package_name, Ok((installed_package, lock_file)))) => {
progress.inc(1);
if let Some(progress) = &progress {
progress.bar().inc(1);
}
packages.get_mut(&package_name).unwrap().install = Some(installed_package);
install_locks.insert(package_name, lock_file);
}
Expand All @@ -271,7 +264,7 @@ pub async fn install(
None => break,
}
}
progress.finish_and_clear();
drop(progress);

// 5. Finalize installed packages.
for (index, (package_name, Package { spec, install })) in packages.into_iter().enumerate() {
Expand Down Expand Up @@ -1396,6 +1389,8 @@ async fn remove_package_shim(bin_dir: &vt_path::AbsolutePath, bin_name: &str) ->

#[cfg(test)]
mod tests {
use std::time::Duration;

use vp_shared::env_vars;

use super::*;
Expand Down
23 changes: 7 additions & 16 deletions crates/vp_global_cli/src/commands/global/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
//! Managed global package utilities.

use std::{collections::HashMap, fs::File, io::Read, process::Stdio, time::Duration};
use std::{collections::HashMap, fs::File, io::Read, process::Stdio};

use flate2::read::GzDecoder;
use futures::{StreamExt, stream::FuturesUnordered};
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
use tar::Archive;
use tokio::process::Command;
use vp_shared::{PrependOptions, ToolPathEnv};
use vp_shared::{PrependOptions, ToolPathEnv, progress::Progress};
use vt_path::{AbsolutePathBuf, current_dir};

use crate::{commands::env::config::resolve_version, error::Error};
Expand Down Expand Up @@ -92,17 +91,7 @@ pub(crate) async fn latest_package_versions(
let mut package_specs = specs.iter();
let mut versions = HashMap::with_capacity(specs.len());

let progress = ProgressBar::new(specs.len() as u64);
if vp_shared::is_stderr_terminal() && std::env::var_os("CI").is_none() {
let style = ProgressStyle::with_template("{spinner:.cyan} {msg} ({pos}/{len})")
.unwrap_or_else(|_| ProgressStyle::default_spinner())
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]);
progress.set_style(style);
progress.set_message("Checking latest package versions");
progress.enable_steady_tick(Duration::from_millis(80));
} else {
progress.set_draw_target(ProgressDrawTarget::hidden());
}
let progress = Progress::counter("Checking latest package versions", specs.len() as u64);

let mut queries = FuturesUnordered::new();

Expand All @@ -121,11 +110,13 @@ pub(crate) async fn latest_package_versions(
}

if let Some(version) = queries.next().await {
progress.inc(1);
if let Some(progress) = &progress {
progress.bar().inc(1);
}
versions.insert(version.package_spec, version.version);
}
}
progress.finish_and_clear();
drop(progress);

Ok(versions)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/vp_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test-utils = ["dep:temp-env", "dep:tempfile"]

[dependencies]
directories = { workspace = true }
nix = { workspace = true, features = ["fs", "poll", "term"] }
nix = { workspace = true, features = ["fs", "poll", "signal", "term"] }
console = { workspace = true }
indicatif = { workspace = true }
cow-utils = { workspace = true }
Expand Down
52 changes: 39 additions & 13 deletions crates/vp_shared/src/progress.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! A single progress row for an operation and its sequential downloads.

mod input;
mod style;

use std::{future::Future, time::Duration};

use indicatif::{ProgressBar, ProgressStyle};

use self::style::Style;
use self::{input::InputBlock, style::Style};

tokio::task_local! {
static ACTIVE_PROGRESS: ProgressBar;
Expand All @@ -28,29 +29,48 @@ pub async fn with_spinner<F: Future>(message: Option<&str>, future: F) -> F::Out
return future.await;
}

let progress =
Progress::new(ProgressBar::new_spinner(), Style::Spinner.for_stderr(message), None);
progress.run(future).await
Progress::start(Style::Spinner.for_stderr(message)).run(future).await
}

/// Clears a download on completion or cancellation, or restores its enclosing
/// operation's spinner. Keep this guard alive until the download finishes.
/// A progress row, cleared on completion or cancellation. A download nested in a
/// spinner restores that spinner instead. A row that is not nested blocks
/// keyboard input until it is dropped. Keep this guard alive until the work finishes.
pub struct Progress {
bar: ProgressBar,
restore: Option<(ProgressStyle, Duration)>,
_input: Option<InputBlock>,
}

impl Progress {
/// Animate a status with a completed count and elapsed time. Advance it
/// through [`Self::bar`]. Returns `None` when progress is hidden.
pub fn counter(message: &str, len: u64) -> Option<Self> {
if !enabled() {
return None;
}
let progress = Self::start(Style::Counter.for_stderr(message));
progress.bar.set_length(len);
progress.bar.force_draw();
Some(progress)
}

pub fn download(message: &str) -> Option<Self> {
let (bar, restore) = match ACTIVE_PROGRESS.try_with(Clone::clone) {
let style = Style::Download.for_stderr(message);
match ACTIVE_PROGRESS.try_with(Clone::clone) {
Ok(bar) => {
let restore = Some((bar.style(), bar.elapsed()));
(bar, restore)
Some(Self::new(bar, style, restore))
}
Err(_) if enabled() => (ProgressBar::new_spinner(), None),
Err(_) => return None,
};
Some(Self::new(bar, Style::Download.for_stderr(message), restore))
Err(_) if enabled() => Some(Self::start(style)),
Err(_) => None,
}
}

/// Show a new row and block keyboard input until it is cleared.
fn start(style: ProgressStyle) -> Self {
let mut progress = Self::new(ProgressBar::new_spinner(), style, None);
progress._input = input::block();
progress
}

fn new(
Expand All @@ -63,7 +83,7 @@ impl Progress {
bar.unset_length();
bar.enable_steady_tick(Duration::from_millis(100));
bar.force_draw();
Self { bar, restore }
Self { bar, restore, _input: None }
}

pub fn bar(&self) -> &ProgressBar {
Expand Down Expand Up @@ -100,6 +120,12 @@ mod tests {

use super::*;

// napi futures hold `&Progress` across awaits, so it must stay `Send` and `Sync`.
const _: fn() = || {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Progress>();
};

fn spinner(term: &InMemoryTerm) -> Progress {
let draw_term = term.clone();
Progress::new(
Expand Down
Loading
Loading