feat: add kappa upgrade subcommand
CI / build-and-test (push) Successful in 39s

Compares installed packages against cached indexes to find newer
versions. kappa upgrade shows available upgrades and updates the
system config with new version constraints. --dry-run shows what
would change without applying.
This commit is contained in:
2026-08-05 07:37:35 -04:00
parent b0b54be70e
commit 33ff453cd1
+106 -1
View File
@@ -58,6 +58,7 @@ Subcommands:
--features "key=on,key2=off" Set feature flags --features "key=on,key2=off" Set feature flags
--config "key=val,key2=val2" Set config values --config "key=val,key2=val2" Set config values
remove <pkg> Remove a package from system config remove <pkg> Remove a package from system config
upgrade Check indexes for newer versions of installed packages
Options: Options:
-h, --help Show this help message -h, --help Show this help message
@@ -226,7 +227,8 @@ int main(int argc, char* argv[]) {
|| (subcommand == "index") || (subcommand == "index")
|| (subcommand == "search") || (subcommand == "search")
|| (subcommand == "add") || (subcommand == "add")
|| (subcommand == "remove"); || (subcommand == "remove")
|| (subcommand == "upgrade");
if (!valid_subcommand) { if (!valid_subcommand) {
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n"; std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
@@ -540,6 +542,109 @@ int main(int argc, char* argv[]) {
} }
} }
if (subcommand == "upgrade") {
auto installed = install::read_installed();
if (installed.empty()) {
std::cout << "no packages installed\n";
return 0;
}
// Build a map of latest versions from cached indexes
std::unordered_map<std::string, std::string> latest;
auto idx_dir = paths::cache_dir() / "indexes";
std::error_code ec;
if (std::filesystem::exists(idx_dir)) {
for (auto& entry : std::filesystem::directory_iterator(idx_dir, ec)) {
if (ec) break;
if (entry.path().extension() != ".kap") continue;
std::ifstream in(entry.path());
if (!in) continue;
std::ostringstream buf;
buf << in.rdbuf();
try {
auto idx = dsl::parse_index(buf.str());
for (auto& pkg : idx.packages) {
auto it = latest.find(pkg.name);
if (it == latest.end() || pkg.version > it->second) {
latest[pkg.name] = pkg.version;
}
}
} catch (...) { continue; }
}
}
if (latest.empty()) {
std::cout << "no cached indexes — run kappa fetch-package <name> first\n";
return 0;
}
std::vector<std::pair<std::string, std::string>> upgrades;
for (auto& e : installed) {
auto it = latest.find(e.name);
if (it != latest.end() && it->second > e.version) {
upgrades.emplace_back(e.name, it->second);
}
}
if (upgrades.empty()) {
std::cout << "all " << installed.size() << " packages up to date\n";
return 0;
}
std::cout << upgrades.size() << " upgrade(s) available:\n";
for (auto& [name, ver] : upgrades) {
// Find old version
std::string old_ver;
for (auto& e : installed) {
if (e.name == name) { old_ver = e.version; break; }
}
std::cout << " " << name << " " << old_ver << " → " << ver << "\n";
}
if (dry_run) {
std::cout << "run kappa add <pkg> <version> for each to apply\n";
return 0;
}
// Apply: update system config with new versions
auto config_path = std::filesystem::path(paths::system_dir()) / "config.kap";
dsl::SystemConfig cfg;
if (std::filesystem::exists(config_path)) {
auto src = read_file(config_path.c_str());
try {
cfg = dsl::parse_system_config(src);
} catch (...) { cfg = {}; }
}
for (auto& [name, ver] : upgrades) {
bool found = false;
for (auto& p : cfg.packages) {
if (p.name == name) {
p.version = ver;
found = true;
break;
}
}
if (!found) {
dsl::PackageRef pref;
pref.name = name;
pref.version = ver;
cfg.packages.push_back(std::move(pref));
}
}
std::filesystem::create_directories(paths::system_dir(), ec);
std::ofstream out(config_path);
if (!out) {
std::cerr << "error: cannot write config\n";
return 1;
}
tools::format_config(out, cfg);
std::cout << "config updated — run kappa rebuild to apply\n";
return 0;
}
if (subcommand == "fetch-package") { if (subcommand == "fetch-package") {
if (file_arg == nullptr) { if (file_arg == nullptr) {
std::cerr << "error: no package name specified\n"; std::cerr << "error: no package name specified\n";