Skip to content
Open
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
12 changes: 1 addition & 11 deletions crates/lpm-cli/src/commands/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub async fn run(
crate::install_ui::terminal_line!(
" {} {}",
install_ui::dim(&format!("{:<16}", "size:")),
format_bytes(size)
lpm_common::format_bytes(size as u64)
)
);
eprintln!();
Expand Down Expand Up @@ -263,13 +263,3 @@ fn short_integrity(integrity: &str) -> String {
.collect();
format!("{prefix}…{suffix}")
}

fn format_bytes(bytes: usize) -> String {
if bytes >= 1_048_576 {
format!("{:.1} MB", bytes as f64 / 1_048_576.0)
} else if bytes >= 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
} else {
format!("{bytes} B")
}
}
17 changes: 1 addition & 16 deletions crates/lpm-cli/src/commands/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub async fn run(
install_ui::done_line(crate::install_ui::terminal_line!(
"Generated {} ({})",
out_path.display().to_string(),
format_byte_size(html_size),
lpm_common::format_bytes(html_size),
));

// Open in browser unless suppressed (headless / CI).
Expand Down Expand Up @@ -531,21 +531,6 @@ fn prune_unreachable(graph: &mut DepGraph) {
graph_render::recompute_stats(graph);
}

/// Format a byte count as a short human-readable string. Uses 1024-based
/// units to match `du -h` / file managers; switches unit at the natural
/// boundary so a 900-byte HTML never reads as "0 KB".
fn format_byte_size(bytes: u64) -> String {
const KIB: u64 = 1024;
const MIB: u64 = 1024 * 1024;
if bytes >= MIB {
format!("{:.1} MB", bytes as f64 / MIB as f64)
} else if bytes >= KIB {
format!("{:.1} KB", bytes as f64 / KIB as f64)
} else {
format!("{bytes} B")
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/lpm-cli/src/commands/publish/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ fn publish_result_display() {
fn dry_run_files_value_uses_slim_value_roles() {
assert_eq!(
console::strip_ansi_codes(&format_dry_run_files_value(3, 2048)).into_owned(),
"3 files (2.0 KB)"
"3 files (2.0 KiB)"
);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/lpm-cli/src/commands/run/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub(super) fn truncate_output(output: String) -> String {
let truncated = &output[..limit];
let end = truncated.rfind('\n').unwrap_or(limit);
format!(
"{}...\n\n[output truncated at {}MB]",
"{}...\n\n[output truncated at {} MiB]",
&output[..end],
MAX_CAPTURED_OUTPUT / (1024 * 1024),
)
Expand Down
4 changes: 2 additions & 2 deletions crates/lpm-cli/src/commands/run/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ fn truncate_output_large_truncated() {
let large = "x".repeat(11 * 1024 * 1024);
let result = truncate_output(large);
assert!(result.len() <= MAX_CAPTURED_OUTPUT + 100); // +100 for the message
assert!(result.contains("[output truncated at 10MB]"));
assert!(result.contains("[output truncated at 10 MiB]"));
}

#[test]
Expand All @@ -167,7 +167,7 @@ fn truncate_output_cuts_at_newline() {
s.push_str(&line);
}
let result = truncate_output(s);
assert!(result.contains("[output truncated at 10MB]"));
assert!(result.contains("[output truncated at 10 MiB]"));
// The truncated content (before "...") should end at a newline boundary
let before_ellipsis = result.split("...\n").next().unwrap();
assert!(before_ellipsis.ends_with('\n') || before_ellipsis.ends_with('a'));
Expand Down
21 changes: 1 addition & 20 deletions crates/lpm-cli/src/commands/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2992,7 +2992,7 @@ async fn verify_and_stage_for_standalone(
install_ui::done_untrusted(&format!(
"Verified Sigstore attestation for {} ({}, integratedTime {})",
binary_name,
format_bytes(staged.byte_len),
lpm_common::format_bytes(staged.byte_len as u64),
audit.integrated_time
));
}
Expand Down Expand Up @@ -3299,16 +3299,6 @@ fn humanize_seconds(secs: u64) -> String {
}
}

fn format_bytes(bytes: usize) -> String {
if bytes >= 1_048_576 {
format!("{:.1} MB", bytes as f64 / 1_048_576.0)
} else if bytes >= 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
} else {
format!("{bytes} B")
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -5482,15 +5472,6 @@ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb lpm-linux-x64
);
}

#[test]
fn format_bytes_units() {
assert_eq!(format_bytes(500), "500 B");
assert_eq!(format_bytes(1024), "1.0 KB");
assert_eq!(format_bytes(1536), "1.5 KB");
assert_eq!(format_bytes(1_048_576), "1.0 MB");
assert_eq!(format_bytes(2_621_440), "2.5 MB");
}

/// Rate-limit lands on `SelfUpdateRateLimited`, not `Forbidden`.
/// `Forbidden` is the user-permission category and reads to users
/// as "you're banned" — wrong category for a quota-reset wait.
Expand Down
8 changes: 4 additions & 4 deletions crates/lpm-cli/src/commands/skills/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fn validate_open_directory(skills_dir: &cap_std::fs::Dir) -> Result<ValidationRe
}
if report.total_size_bytes > MAX_TOTAL_SIZE {
report.errors.push(format!(
"total package skills size {} bytes exceeds 100KB limit",
"total package skills size {} bytes exceeds 100 KiB limit",
report.total_size_bytes
));
}
Expand Down Expand Up @@ -203,7 +203,7 @@ fn validate_opened_file(
if size > MAX_SKILL_SIZE {
report
.errors
.push(format!("{display}: exceeds 15KB limit ({size} bytes)"));
.push(format!("{display}: exceeds 15 KiB limit ({size} bytes)"));
return Ok(());
}

Expand All @@ -217,7 +217,7 @@ fn validate_opened_file(
.saturating_sub(size)
.saturating_add(bytes.len() as u64);
report.errors.push(format!(
"{display}: exceeds 15KB limit ({} bytes)",
"{display}: exceeds 15 KiB limit ({} bytes)",
bytes.len()
));
return Ok(());
Expand Down Expand Up @@ -597,7 +597,7 @@ mod tests {
report
.errors
.iter()
.any(|error| error.contains("exceeds 100KB limit"))
.any(|error| error.contains("exceeds 100 KiB limit"))
);
assert!(retained_bytes <= MAX_TOTAL_SIZE, "{retained_bytes}");
}
Expand Down
6 changes: 3 additions & 3 deletions crates/lpm-cli/src/commands/tool_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::process::{Command, Stdio};
/// Maximum size for captured workspace stdout/stderr before truncation.
/// Mirrors the `MAX_CAPTURED_OUTPUT` constant in `commands::run` so chatty
/// failing members don't unbound the JSON envelope.
const MAX_CAPTURED_OUTPUT: usize = 10 * 1024 * 1024; // 10 MB
const MAX_CAPTURED_OUTPUT: usize = 10 * 1024 * 1024; // 10 MiB

/// Truncate captured output if it exceeds `MAX_CAPTURED_OUTPUT`, cutting at
/// the last newline boundary to avoid splitting a line.
Expand All @@ -20,7 +20,7 @@ fn truncate_output(text: &str) -> String {
}
let end = text[..end].rfind('\n').unwrap_or(end);
format!(
"{}...\n\n[output truncated at {}MB]",
"{}...\n\n[output truncated at {} MiB]",
&text[..end],
MAX_CAPTURED_OUTPUT / (1024 * 1024),
)
Expand Down Expand Up @@ -343,7 +343,7 @@ mod tests {
let huge = "x".repeat(MAX_CAPTURED_OUTPUT + 2_000);
let result = truncate_output(&huge);
assert!(
result.ends_with("[output truncated at 10MB]"),
result.ends_with("[output truncated at 10 MiB]"),
"expected truncation marker, got tail: {:?}",
&result[result.len().saturating_sub(60)..]
);
Expand Down
4 changes: 2 additions & 2 deletions crates/lpm-cli/src/commands/tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2292,7 +2292,7 @@ mod tests {

assert_eq!(
tunnel_limit_summary(Some(&limits)).as_deref(),
Some("1 tunnel · 4000/min · 10.0 MB body · 0 custom domains · 600/min/IP")
Some("1 tunnel · 4000/min · 10.0 MiB body · 0 custom domains · 600/min/IP")
);
}

Expand All @@ -2309,7 +2309,7 @@ mod tests {

assert_eq!(
tunnel_limit_summary(Some(&limits)).as_deref(),
Some("3 tunnels · 20000/min · 100.0 MB body · 3 custom domains · 600/min/IP")
Some("3 tunnels · 20000/min · 100.0 MiB body · 3 custom domains · 600/min/IP")
);
}

Expand Down
6 changes: 3 additions & 3 deletions crates/lpm-cli/src/commands/whoami.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ pub async fn run(client: &RegistryClient, json_output: bool) -> Result<(), LpmEr
if let Some(limit_bytes) = limits.storage_bytes {
let limit_mb = limit_bytes as f64 / (1024.0 * 1024.0);
let storage_msg = with_usage_bar(
format!("{:.2}MB / {:.0}MB", storage_mb, limit_mb),
format!("{:.2} MiB / {:.0} MiB", storage_mb, limit_mb),
usage.storage_bytes,
limit_bytes,
);
Expand All @@ -120,7 +120,7 @@ pub async fn run(client: &RegistryClient, json_output: bool) -> Result<(), LpmEr
whoami_ui::detail("storage", storage_msg);
}
} else {
whoami_ui::detail("storage", format!("{:.2}MB", storage_mb));
whoami_ui::detail("storage", format!("{:.2} MiB", storage_mb));
}

// Package count
Expand Down Expand Up @@ -163,7 +163,7 @@ pub async fn run(client: &RegistryClient, json_output: bool) -> Result<(), LpmEr
whoami_ui::warn("Upgrade your plan: https://lpm.dev/dashboard/settings/billing");
}
} else {
whoami_ui::detail("storage", format!("{:.2}MB", storage_mb));
whoami_ui::detail("storage", format!("{:.2} MiB", storage_mb));
whoami_ui::detail("private pkgs", format!("{}", usage.private_packages));
}
}
Expand Down
20 changes: 16 additions & 4 deletions crates/lpm-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,23 +309,35 @@ pub fn is_loopback_host(host: &str) -> bool {
false
}

/// Format bytes into a human-readable string (e.g., "1.2 KB", "3.4 MB").
/// Format bytes into a human-readable string in binary units, as they're
/// counted (e.g., "1.2 KiB", "3.4 MiB"). Switches unit at each 1024, so a
/// 900-byte file never reads as "0.9 KiB" or "0 KiB".
pub fn format_bytes(bytes: u64) -> String {
if bytes < 1024 {
format!("{bytes} B")
} else if bytes < 1024 * 1024 {
format!("{:.1} KB", bytes as f64 / 1024.0)
format!("{:.1} KiB", bytes as f64 / 1024.0)
} else if bytes < 1024 * 1024 * 1024 {
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
format!("{:.1} MiB", bytes as f64 / (1024.0 * 1024.0))
} else {
format!("{:.1} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
format!("{:.1} GiB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn format_bytes_labels_binary_units() {
assert_eq!(format_bytes(500), "500 B");
assert_eq!(format_bytes(1024), "1.0 KiB");
assert_eq!(format_bytes(1536), "1.5 KiB");
assert_eq!(format_bytes(1_048_576), "1.0 MiB");
assert_eq!(format_bytes(2_621_440), "2.5 MiB");
assert_eq!(format_bytes(5 * 1024 * 1024 * 1024), "5.0 GiB");
}

// ── read_capped_state_file ────────────────────────────────────────

/// Files under the cap round-trip transparently.
Expand Down
4 changes: 2 additions & 2 deletions crates/lpm-runtime/src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ async fn read_download_body(
fn checked_download_size(size: u64) -> Result<usize, LpmError> {
let size = usize::try_from(size).map_err(|_| {
LpmError::Script(format!(
"download size {size} bytes exceeds maximum allowed size of {MAX_DOWNLOAD_SIZE} bytes ({}MB)",
"download size {size} bytes exceeds maximum allowed size of {MAX_DOWNLOAD_SIZE} bytes ({} MiB)",
MAX_DOWNLOAD_SIZE / (1024 * 1024)
))
})?;
Expand Down Expand Up @@ -763,7 +763,7 @@ const MAX_DOWNLOAD_SIZE: usize = 200 * 1024 * 1024;
fn validate_download_size(size: usize) -> Result<(), LpmError> {
if size > MAX_DOWNLOAD_SIZE {
return Err(LpmError::Network(format!(
"download size {size} bytes exceeds maximum allowed size of {MAX_DOWNLOAD_SIZE} bytes ({}MB)",
"download size {size} bytes exceeds maximum allowed size of {MAX_DOWNLOAD_SIZE} bytes ({} MiB)",
MAX_DOWNLOAD_SIZE / (1024 * 1024)
)));
}
Expand Down
12 changes: 6 additions & 6 deletions crates/lpm-tunnel/src/webhook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ fn format_size(bytes: usize) -> String {
if bytes < 1024 {
format!("{bytes}B")
} else if bytes < 1024 * 1024 {
format!("{:.1}KB", bytes as f64 / 1024.0)
format!("{:.1}KiB", bytes as f64 / 1024.0)
} else {
format!("{:.1}MB", bytes as f64 / (1024.0 * 1024.0))
format!("{:.1}MiB", bytes as f64 / (1024.0 * 1024.0))
}
}

Expand Down Expand Up @@ -414,7 +414,7 @@ mod tests {
w.path = "/api/hook".to_string();
w.request_body = vec![0u8; 2500];
});
assert_eq!(summarize_webhook(&wh), "POST /api/hook (2.4KB)");
assert_eq!(summarize_webhook(&wh), "POST /api/hook (2.4KiB)");
}

// -- CapturedWebhook methods --
Expand Down Expand Up @@ -535,12 +535,12 @@ mod tests {

#[test]
fn format_size_kilobytes() {
assert_eq!(format_size(1024), "1.0KB");
assert_eq!(format_size(2560), "2.5KB");
assert_eq!(format_size(1024), "1.0KiB");
assert_eq!(format_size(2560), "2.5KiB");
}

#[test]
fn format_size_megabytes() {
assert_eq!(format_size(1024 * 1024), "1.0MB");
assert_eq!(format_size(1024 * 1024), "1.0MiB");
}
}
2 changes: 1 addition & 1 deletion tests/workflows/tests/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ async fn cache_status_human_renders_tables_and_slim_completion() {
"cache status must render local and remote sections, got:\n{stdout}"
);
assert!(
stdout.contains("1.0 KB / 2.0 KB █████░░░░░"),
stdout.contains("1.0 KiB / 2.0 KiB █████░░░░░"),
"cache status must render remote usage with a quota bar, got:\n{stdout}"
);

Expand Down
10 changes: 5 additions & 5 deletions tests/workflows/tests/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,16 +349,16 @@ fn graph_html_writes_human_readable_size() {
String::from_utf8_lossy(&output.stderr)
);

// The size suffix must read as KB or MB — never "0 KB" via integer
// division. The fixture renders well over 1 KB of HTML, so KB is the
// The size suffix must read as KiB or MiB — never "0 KiB" via integer
// division. The fixture renders well over 1 KiB of HTML, so KiB is the
// expected unit.
assert!(
combined.contains(" KB)") || combined.contains(" MB)"),
combined.contains(" KiB)") || combined.contains(" MiB)"),
"size in success message should use a human-readable unit: {combined}"
);
assert!(
!combined.contains("(0 KB)"),
"size must never collapse to 0 KB via integer truncation: {combined}"
!combined.contains("(0 KiB)"),
"size must never collapse to 0 KiB via integer truncation: {combined}"
);
}

Expand Down
4 changes: 2 additions & 2 deletions tests/workflows/tests/skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2887,7 +2887,7 @@ fn skills_validate_rejects_skill_exceeding_size_limit() {
String::from_utf8_lossy(&output.stderr),
);
assert!(
combined.contains("15KB") || combined.contains("exceed") || combined.contains("limit"),
combined.contains("15 KiB") || combined.contains("exceed") || combined.contains("limit"),
"validate output must surface the per-skill size-limit warning, got:\n{combined}",
);
let stderr = String::from_utf8_lossy(&output.stderr);
Expand Down Expand Up @@ -2940,7 +2940,7 @@ fn skills_validate_json_reports_success_false_and_exits_non_zero_on_error() {
.as_str()
.expect("each error entry must be a string");
assert!(
error_text.contains("15KB") || error_text.contains("limit"),
error_text.contains("15 KiB") || error_text.contains("limit"),
"error text must name the violated limit, got {error_text:?}",
);
}
Expand Down
3 changes: 2 additions & 1 deletion tests/workflows/tests/whoami.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ async fn whoami_human_output_uses_slim_account_summary() {
"authenticated whoami should warn when auth storage uses the encrypted file fallback, got:\n{stderr}"
);
assert!(
stderr.contains("usage") && stderr.contains("storage 50.00MB / 500MB █░░░░░░░░░"),
stderr.contains("usage")
&& stderr.contains("storage 50.00 MiB / 500 MiB █░░░░░░░░░"),
"authenticated whoami should show storage usage with a quota bar, got:\n{stderr}"
);
assert!(
Expand Down
Loading