kappa bump <file> <version> — updates .kap file to new version.
Downloads tarball, computes sha256, rewrites file with new
version and hash. Preserves source URL with ${version} template.
kappa bump-all <dir> — scans directory of .kap files. For each,
auto-detects newer patch versions by incrementing the version
number and trying up to 15 candidates. Downloads and hashes the
first one that exists.
This commit is contained in:
+219
-1
@@ -19,7 +19,9 @@
|
||||
#include "kappa/tools/format.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
@@ -27,6 +29,8 @@
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
using namespace kappa;
|
||||
@@ -59,6 +63,8 @@ Subcommands:
|
||||
--config "key=val,key2=val2" Set config values
|
||||
remove <pkg> Remove a package from system config
|
||||
upgrade Check indexes for newer versions of installed packages
|
||||
bump <file> <v> Update a .kap package to a new version (auto-download, rehash)
|
||||
bump-all <dir> Scan directory and bump all .kap files to latest available
|
||||
|
||||
Options:
|
||||
-h, --help Show this help message
|
||||
@@ -246,7 +252,9 @@ int main(int argc, char* argv[]) {
|
||||
|| (subcommand == "search")
|
||||
|| (subcommand == "add")
|
||||
|| (subcommand == "remove")
|
||||
|| (subcommand == "upgrade");
|
||||
|| (subcommand == "upgrade")
|
||||
|| (subcommand == "bump")
|
||||
|| (subcommand == "bump-all");
|
||||
|
||||
if (!valid_subcommand) {
|
||||
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
||||
@@ -663,6 +671,216 @@ int main(int argc, char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand == "bump") {
|
||||
if (file_arg == nullptr || version_arg == nullptr) {
|
||||
std::cerr << "error: bump requires <file> <new-version>\n";
|
||||
return 1;
|
||||
}
|
||||
try {
|
||||
auto src = read_file(file_arg);
|
||||
auto pkg = dsl::parse(src);
|
||||
|
||||
auto old_version = pkg.version;
|
||||
auto old_sha = pkg.sha256;
|
||||
|
||||
// Construct new source URL with the new version
|
||||
auto new_source = pkg.source;
|
||||
std::string_view new_ver(version_arg);
|
||||
for (auto& [from, to] : {
|
||||
std::pair{"${version}"sv, new_ver},
|
||||
std::pair{"${name}"sv, std::string_view(pkg.name)}}) {
|
||||
std::size_t pos = 0;
|
||||
while ((pos = new_source.find(from, pos)) != std::string::npos) {
|
||||
new_source.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "downloading " << new_source << "...\n";
|
||||
auto temp_tarball = paths::temp_dir() / "bump-download.tar.gz";
|
||||
std::filesystem::create_directories(paths::temp_dir());
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
execlp("curl", "curl", "-Lsf", "-o", temp_tarball.c_str(),
|
||||
new_source.c_str(), nullptr);
|
||||
_exit(1);
|
||||
}
|
||||
int status = 0;
|
||||
while (waitpid(pid, &status, 0) == -1 && errno == EINTR) {}
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
std::cerr << "download failed\n";
|
||||
std::filesystem::remove(temp_tarball);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Compute new hash
|
||||
int pipefd[2];
|
||||
if (pipe(pipefd) != 0) { return 1; }
|
||||
pid_t pid2 = fork();
|
||||
if (pid2 == 0) {
|
||||
close(pipefd[0]);
|
||||
dup2(pipefd[1], STDOUT_FILENO);
|
||||
close(pipefd[1]);
|
||||
execlp("sha256sum", "sha256sum", temp_tarball.c_str(), nullptr);
|
||||
_exit(1);
|
||||
}
|
||||
close(pipefd[1]);
|
||||
std::array<char, 128> buf;
|
||||
std::string hash_out;
|
||||
ssize_t n;
|
||||
while ((n = read(pipefd[0], buf.data(), buf.size() - 1)) > 0) {
|
||||
buf[static_cast<std::size_t>(n)] = '\0';
|
||||
hash_out += buf.data();
|
||||
}
|
||||
close(pipefd[0]);
|
||||
while (waitpid(pid2, nullptr, 0) == -1 && errno == EINTR) {}
|
||||
|
||||
auto space = hash_out.find(' ');
|
||||
auto new_sha = (space != std::string::npos)
|
||||
? hash_out.substr(0, space) : hash_out;
|
||||
|
||||
std::filesystem::remove(temp_tarball);
|
||||
|
||||
// Update the .kap file
|
||||
pkg.version = version_arg;
|
||||
pkg.sha256 = new_sha;
|
||||
|
||||
std::ofstream out(file_arg);
|
||||
if (!out) {
|
||||
std::cerr << "error: cannot write " << file_arg << "\n";
|
||||
return 1;
|
||||
}
|
||||
tools::format_package(out, pkg);
|
||||
|
||||
std::cout << "bumped " << pkg.name << " " << old_version
|
||||
<< " → " << version_arg << " sha256: " << new_sha << "\n";
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "bump error: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (subcommand == "bump-all") {
|
||||
if (file_arg == nullptr) {
|
||||
std::cerr << "error: bump-all requires a directory\n";
|
||||
return 1;
|
||||
}
|
||||
std::string dir(file_arg);
|
||||
int bumped = 0;
|
||||
int skipped = 0;
|
||||
std::error_code ec;
|
||||
|
||||
for (auto& entry : std::filesystem::directory_iterator(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();
|
||||
in.close();
|
||||
|
||||
dsl::PackageDef pkg;
|
||||
try { pkg = dsl::parse(buf.str()); }
|
||||
catch (...) { skipped++; continue; }
|
||||
|
||||
// Try to auto-detect next version: increment patch number
|
||||
auto dot1 = pkg.version.rfind('.');
|
||||
if (dot1 == std::string::npos) { skipped++; continue; }
|
||||
|
||||
auto base = pkg.version.substr(0, dot1 + 1);
|
||||
auto patch_str = pkg.version.substr(dot1 + 1);
|
||||
|
||||
int patch = 0;
|
||||
try { patch = std::stoi(patch_str); }
|
||||
catch (...) { skipped++; continue; }
|
||||
|
||||
// Try next 15 patch versions
|
||||
bool found = false;
|
||||
for (int next = patch + 1; next <= patch + 15; ++next) {
|
||||
auto candidate = base + std::to_string(next);
|
||||
auto url = pkg.source;
|
||||
std::string_view cand_ver(candidate);
|
||||
for (auto& [from, to] : {
|
||||
std::pair{"${version}"sv, cand_ver},
|
||||
std::pair{"${name}"sv, std::string_view(pkg.name)}}) {
|
||||
std::size_t pos = 0;
|
||||
while ((pos = url.find(from, pos)) != std::string::npos) {
|
||||
url.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
// Try downloading the candidate URL
|
||||
auto temp = paths::temp_dir() / "bumpall-download.tar.gz";
|
||||
std::filesystem::create_directories(paths::temp_dir());
|
||||
pid_t dl_pid = fork();
|
||||
if (dl_pid == 0) {
|
||||
// Redirect stdout/stderr to /dev/null for quiet operation
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
if (devnull >= 0) {
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
execlp("curl", "curl", "-Lsf", "-o", temp.c_str(), url.c_str(), nullptr);
|
||||
_exit(1);
|
||||
}
|
||||
int dl_status = 0;
|
||||
while (waitpid(dl_pid, &dl_status, 0) == -1 && errno == EINTR) {}
|
||||
if (!WIFEXITED(dl_status) || WEXITSTATUS(dl_status) != 0) {
|
||||
std::filesystem::remove(temp);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Hash
|
||||
int hpipe[2];
|
||||
if (pipe(hpipe) != 0) continue;
|
||||
pid_t hpid = fork();
|
||||
if (hpid == 0) {
|
||||
close(hpipe[0]); dup2(hpipe[1], STDOUT_FILENO); close(hpipe[1]);
|
||||
execlp("sha256sum", "sha256sum", temp.c_str(), nullptr);
|
||||
_exit(1);
|
||||
}
|
||||
close(hpipe[1]);
|
||||
std::array<char, 128> hbuf;
|
||||
std::string hout;
|
||||
ssize_t hn;
|
||||
while ((hn = read(hpipe[0], hbuf.data(), hbuf.size() - 1)) > 0) {
|
||||
hbuf[static_cast<std::size_t>(hn)] = '\0';
|
||||
hout += hbuf.data();
|
||||
}
|
||||
close(hpipe[0]);
|
||||
while (waitpid(hpid, nullptr, 0) == -1 && errno == EINTR) {}
|
||||
std::filesystem::remove(temp);
|
||||
|
||||
auto sp = hout.find(' ');
|
||||
auto new_sha = (sp != std::string::npos) ? hout.substr(0, sp) : hout;
|
||||
|
||||
// Update file
|
||||
auto old_ver = pkg.version;
|
||||
pkg.version = candidate;
|
||||
pkg.sha256 = new_sha;
|
||||
|
||||
std::ofstream out(entry.path());
|
||||
if (!out) continue;
|
||||
tools::format_package(out, pkg);
|
||||
|
||||
std::cout << pkg.name << " " << old_ver
|
||||
<< " → " << candidate << "\n";
|
||||
found = true;
|
||||
bumped++;
|
||||
break;
|
||||
}
|
||||
if (!found) skipped++;
|
||||
}
|
||||
|
||||
std::cout << bumped << " bumped, " << skipped << " skipped\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (subcommand == "fetch-package") {
|
||||
if (file_arg == nullptr) {
|
||||
std::cerr << "error: no package name specified\n";
|
||||
|
||||
Reference in New Issue
Block a user