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 "kappa/tools/format.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
using namespace kappa;
|
using namespace kappa;
|
||||||
@@ -59,6 +63,8 @@ Subcommands:
|
|||||||
--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
|
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:
|
Options:
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
@@ -246,7 +252,9 @@ int main(int argc, char* argv[]) {
|
|||||||
|| (subcommand == "search")
|
|| (subcommand == "search")
|
||||||
|| (subcommand == "add")
|
|| (subcommand == "add")
|
||||||
|| (subcommand == "remove")
|
|| (subcommand == "remove")
|
||||||
|| (subcommand == "upgrade");
|
|| (subcommand == "upgrade")
|
||||||
|
|| (subcommand == "bump")
|
||||||
|
|| (subcommand == "bump-all");
|
||||||
|
|
||||||
if (!valid_subcommand) {
|
if (!valid_subcommand) {
|
||||||
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
std::cerr << "error: unknown subcommand '" << subcommand << "'\n\n";
|
||||||
@@ -663,6 +671,216 @@ int main(int argc, char* argv[]) {
|
|||||||
return 0;
|
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 (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";
|
||||||
|
|||||||
Reference in New Issue
Block a user