Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f3fb8e1
build: require compak 0.2 for single-file compression
QaidVoid Jul 28, 2026
2798d45
feat(registry): read the declarative index format
QaidVoid Jul 28, 2026
1d3e9ac
feat(db): store binaries and side files
QaidVoid Jul 28, 2026
6a0ad6b
fix(dl): extract by content, not declared type
QaidVoid Jul 28, 2026
5d96b13
feat(core): install side files and fix extraction
QaidVoid Jul 28, 2026
fa89bd4
fix(operations): resolve binaries by path before name
QaidVoid Jul 28, 2026
04d44b1
fix(cli): report packages rejected during import
QaidVoid Jul 28, 2026
6557122
feat: select variants by family, deprecate pkg_id
QaidVoid Jul 28, 2026
38ef337
fix(cli): surface log events instead of dropping them
QaidVoid Jul 28, 2026
90f2981
feat: track package family for installed packages
QaidVoid Jul 29, 2026
2b589ee
feat: allow packages without an id
QaidVoid Jul 29, 2026
ef48f06
feat(db): stop inventing package ids on import
QaidVoid Jul 29, 2026
66c3529
fix(remove): restore write bits before deleting a package
QaidVoid Jul 29, 2026
fa73bdb
fix: match installed packages by name, not by id
QaidVoid Jul 29, 2026
450b8da
fix(update): order versions by segment, not by string
QaidVoid Jul 29, 2026
9bd5d5f
feat: declare an index format version
QaidVoid Jul 29, 2026
a1af52b
docs(install): explain why licences carry no hash
QaidVoid Jul 29, 2026
4715fb3
fmt
QaidVoid Jul 29, 2026
a72ee19
refactor(db): collapse the new migrations into one per database
QaidVoid Jul 29, 2026
b048a17
fix(url): keep the derived id when building a package
QaidVoid Jul 29, 2026
61bf815
fix: repair tests and lints left by the pkg_id removal
QaidVoid Jul 29, 2026
203b5c0
feat(config): deprecate install_patterns
QaidVoid Jul 29, 2026
1ad8b75
fix: scope package lookups by identity, not by a missing id
QaidVoid Jul 29, 2026
614cb70
fix(db): refuse a downgrade instead of deleting id-less rows
QaidVoid Jul 29, 2026
cd0513e
fix(db): let the metadata cache clear on downgrade
QaidVoid Jul 29, 2026
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
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ blake3 = { version = "1.8.5", features = ["mmap"] }
clap = { version = "4.6.1", features = ["cargo", "derive"] }
clap_complete = "4.6.5"
chrono = "0.4"
compak = "0.1.2"
compak = "0.2.0"
diesel = { version = "2.3.10", features = [
"64-column-tables",
"returning_clauses_for_sqlite_3_35",
Expand Down
18 changes: 3 additions & 15 deletions crates/soar-cli/src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,7 @@ fn display_diff(diff: &ApplyDiff, prune: bool) {
let pkg = &target.package;
builder.push_record([
format!("{}", Colored(Green, icon_or("+", "+"))),
format!(
"{}#{}",
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id)
),
format!("{}", Colored(Blue, &pkg.pkg_name),),
format!("{}", Colored(Green, &pkg.version)),
format!("{}", Colored(Magenta, &pkg.repo_name)),
]);
Expand All @@ -105,11 +101,7 @@ fn display_diff(diff: &ApplyDiff, prune: bool) {
.map_or("?".to_string(), |e| e.version.clone());
builder.push_record([
format!("{}", Colored(Yellow, icon_or("~", "~"))),
format!(
"{}#{}",
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id)
),
format!("{}", Colored(Blue, &pkg.pkg_name),),
format!(
"{} -> {}",
Colored(Red, &old_version),
Expand All @@ -123,11 +115,7 @@ fn display_diff(diff: &ApplyDiff, prune: bool) {
for pkg in &diff.to_remove {
builder.push_record([
format!("{}", Colored(Red, icon_or("-", "-"))),
format!(
"{}#{}",
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id)
),
format!("{}", Colored(Blue, &pkg.pkg_name),),
format!("{}", Colored(Yellow, &pkg.version)),
format!("{}", Colored(Magenta, &pkg.repo_name)),
]);
Expand Down
7 changes: 3 additions & 4 deletions crates/soar-cli/src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub async fn handle_direct_downloads(
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
None,
Expand All @@ -173,6 +174,7 @@ pub async fn handle_direct_downloads(
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
None,
Expand Down Expand Up @@ -210,10 +212,7 @@ pub async fn handle_direct_downloads(

let package = package.resolve(query.version.as_deref());

info!(
"Downloading package: {}#{}",
package.pkg_name, package.pkg_id
);
info!("Downloading package: {}", package.pkg_name);
if let Some(ref url) = package.ghcr_blob {
let mut dl = OciDownload::new(url.as_str()).overwrite(ctx.get_overwrite_mode());

Expand Down
16 changes: 5 additions & 11 deletions crates/soar-cli/src/health.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use nu_ansi_term::Color::{Blue, Cyan, Green, Red, Yellow};
use nu_ansi_term::Color::{Blue, Green, Red, Yellow};
use soar_core::SoarResult;
use soar_operations::{health, SoarContext};
use tabled::{
Expand Down Expand Up @@ -61,10 +61,9 @@ pub async fn display_health(ctx: &SoarContext) -> SoarResult<()> {
info!("\nBroken packages:");
for pkg in &report.broken_packages {
info!(
" {} {}#{}: {}",
" {} {}: {}",
Icons::ARROW,
Colored(Blue, &pkg.pkg_name),
Colored(Cyan, &pkg.pkg_id),
Colored(Yellow, &pkg.installed_path)
);
}
Expand Down Expand Up @@ -94,16 +93,11 @@ pub async fn remove_broken_packages(ctx: &SoarContext) -> SoarResult<()> {
}

for removed in &report.removed {
info!("Removed {}#{}", removed.pkg_name, removed.pkg_id);
info!("Removed {}", removed.pkg_name);
}

for failed in &report.failed {
tracing::error!(
"Failed to remove {}#{}: {}",
failed.pkg_name,
failed.pkg_id,
failed.error
);
tracing::error!("Failed to remove {}: {}", failed.pkg_name, failed.error);
}

if !report.removed.is_empty() && report.failed.is_empty() {
Expand All @@ -114,7 +108,7 @@ pub async fn remove_broken_packages(ctx: &SoarContext) -> SoarResult<()> {
report
.failed
.iter()
.map(|f| format!("{}#{}", f.pkg_name, f.pkg_id))
.map(|f| f.pkg_name.to_string())
.collect::<Vec<_>>()
.join(", ")
);
Expand Down
4 changes: 3 additions & 1 deletion crates/soar-cli/src/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn get_installed_path(
conn,
&package.repo_name,
&package.pkg_name,
&package.pkg_id,
package.pkg_id.as_deref(),
&package.version,
)
})?;
Expand All @@ -71,6 +71,7 @@ pub async fn inspect_log(package: &str, inspect_type: InspectType) -> SoarResult
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand All @@ -90,6 +91,7 @@ pub async fn inspect_log(package: &str, inspect_type: InspectType) -> SoarResult
conn,
query.name.as_deref(),
query.pkg_id.as_deref(),
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand Down
60 changes: 36 additions & 24 deletions crates/soar-cli/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,15 @@ pub async fn install_packages(
};

if let Some(pkg) = pkg {
// Re-resolve with the specific selected package
let specific_query =
format!("{}#{}:{}", pkg.pkg_name, pkg.pkg_id, pkg.repo_name);
// Re-resolve with the specific selected package. The
// family has to come along, or a name ambiguous within one
// repository resolves right back to the same choice.
let specific_query = match &pkg.pkg_family {
Some(family) => {
format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
}
None => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
};
Comment on lines +78 to +86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retain legacy pkg_id when no family is available.

For older indexes, a selected package may have pkg_family = None but a populated pkg_id. The None branch converts it to name:repo, which can become ambiguous or resolve to a different legacy variant despite the retained name#pkg_id syntax. Prefer family/name:repo, otherwise name#pkg_id:repo, and only use name:repo when both are absent.

Proposed identity fallback
-                    let specific_query = match &pkg.pkg_family {
-                        Some(family) => {
-                            format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
-                        }
-                        None => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
+                    let specific_query = match (&pkg.pkg_family, &pkg.pkg_id) {
+                        (Some(family), _) => {
+                            format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
+                        }
+                        (None, Some(pkg_id)) => {
+                            format!("{}#{}:{}", pkg.pkg_name, pkg_id, pkg.repo_name)
+                        }
+                        (None, None) => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
                     };

Also applies to: 209-214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/soar-cli/src/install.rs` around lines 78 - 86, Update the
specific_query construction for the selected package: prefer the
family/name:repo form, otherwise retain pkg_id using name#pkg_id:repo, and fall
back to name:repo only when both pkg_family and pkg_id are absent. Apply the
same fallback logic to the corresponding alternate block noted in the comment.

let re_results =
install::resolve_packages(ctx, &[specific_query], &options).await?;
for r in re_results {
Expand All @@ -97,13 +103,13 @@ pub async fn install_packages(
}
ResolveResult::AlreadyInstalled {
pkg_name,
pkg_id,
repo_name,
version,
..
} => {
warn!(
"{}#{}:{} ({}) is already installed - skipping",
pkg_name, pkg_id, repo_name, version,
"{}:{} ({}) is already installed - skipping",
pkg_name, repo_name, version,
);
if !force {
info!("Hint: Use --force to reinstall, or --show to see other variants");
Expand Down Expand Up @@ -161,13 +167,13 @@ async fn install_with_show(
ResolveResult::Resolved(targets) => install_targets.extend(targets),
ResolveResult::AlreadyInstalled {
pkg_name,
pkg_id,
repo_name,
version,
..
} => {
warn!(
"{}#{}:{} ({}) is already installed - skipping",
pkg_name, pkg_id, repo_name, version,
"{}:{} ({}) is already installed - skipping",
pkg_name, repo_name, version,
);
if !force {
info!("Hint: Use --force to reinstall");
Expand Down Expand Up @@ -200,8 +206,12 @@ async fn install_with_show(
};

if let Some(pkg) = pkg {
let specific_query =
format!("{}#{}:{}", pkg.pkg_name, pkg.pkg_id, pkg.repo_name);
let specific_query = match &pkg.pkg_family {
Some(family) => {
format!("{}/{}:{}", family, pkg.pkg_name, pkg.repo_name)
}
None => format!("{}:{}", pkg.pkg_name, pkg.repo_name),
};
let re_results =
install::resolve_packages(ctx, &[specific_query], options).await?;
for r in re_results {
Expand All @@ -221,13 +231,13 @@ async fn install_with_show(
}
ResolveResult::AlreadyInstalled {
pkg_name,
pkg_id,
repo_name,
version,
..
} => {
warn!(
"{}#{}:{} ({}) is already installed - skipping",
pkg_name, pkg_id, repo_name, version,
"{}:{} ({}) is already installed - skipping",
pkg_name, repo_name, version,
);
if !force {
info!(
Expand All @@ -247,6 +257,7 @@ async fn install_with_show(
conn,
query.name.as_deref(),
None,
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand All @@ -266,6 +277,7 @@ async fn install_with_show(
conn,
query.name.as_deref(),
None,
query.family.as_deref(),
None,
None,
Some(SortDirection::Asc),
Expand Down Expand Up @@ -302,7 +314,7 @@ async fn install_with_show(
}

// Get installed packages to show [installed] marker
let installed_packages: Vec<(String, String, String)> = diesel_db
let installed_packages: Vec<(String, Option<String>, String)> = diesel_db
.with_conn(|conn| {
CoreRepository::list_filtered(
conn,
Expand All @@ -317,7 +329,7 @@ async fn install_with_show(
)
})?
.into_iter()
.map(|p| (p.pkg_id, p.repo_name, p.version))
.map(|p| (p.pkg_name, p.pkg_family, p.repo_name))
.collect();

let pkg = select_package_interactively_with_installed(
Expand All @@ -337,7 +349,7 @@ async fn install_with_show(
conn,
Some(&pkg.repo_name),
Some(&pkg.pkg_name),
Some(&pkg.pkg_id),
pkg.pkg_id.as_deref(),
None,
None,
None,
Expand All @@ -352,9 +364,8 @@ async fn install_with_show(
if let Some(ref existing) = existing_install {
if existing.is_installed {
warn!(
"{}#{}:{} ({}) is already installed - {}",
"{}:{} ({}) is already installed - {}",
existing.pkg_name,
existing.pkg_id,
existing.repo_name,
existing.version,
if force { "reinstalling" } else { "skipping" }
Expand Down Expand Up @@ -402,10 +413,9 @@ fn display_install_report(report: &InstallReport, no_notes: bool) {

for info in &report.installed {
info!(
"\n{} {}#{}:{} [{}]",
"\n{} {}:{} [{}]",
icon_or(Icons::CHECK, "*"),
Colored(Blue, &info.pkg_name),
Colored(Cyan, &info.pkg_id),
Colored(Green, &info.repo_name),
Colored(Magenta, info.install_dir.display())
);
Expand All @@ -424,7 +434,9 @@ fn display_install_report(report: &InstallReport, no_notes: bool) {
}

if !no_notes {
if let Some(ref notes) = info.notes {
// Most packages have nothing to say, and an empty list would
// otherwise print a heading with no content under it.
if let Some(notes) = info.notes.as_ref().filter(|n| !n.is_empty()) {
info!(
" {} Notes:\n {}",
icon_or("📝", "-"),
Expand All @@ -436,8 +448,8 @@ fn display_install_report(report: &InstallReport, no_notes: bool) {

for err_info in &report.failed {
error!(
"Failed to install {}#{}: {}",
err_info.pkg_name, err_info.pkg_id, err_info.error
"Failed to install {}: {}",
err_info.pkg_name, err_info.error
);
}

Expand Down
Loading
Loading