feat: Update 2 files
All checks were successful
Build-Test-Publish / build (linux/amd64) (push) Successful in 30s
Build-Test-Publish / build (linux/arm64) (push) Successful in 1m13s

This commit is contained in:
j
2025-12-28 11:32:09 +13:00
parent 754b4de29b
commit 7763807445
2 changed files with 199 additions and 97 deletions

View File

@@ -13,6 +13,7 @@
#include <ctime>
#include <iomanip>
#include <cstdlib>
#include <algorithm>
namespace dropshell {
@@ -31,9 +32,9 @@ struct PublishTemplateCommandRegister {
false, // hidden
true, // requires_config
true, // requires_install
1, // min_args (directory required)
2, // max_args (optional registry name + directory)
"publish-template [REGISTRY] DIRECTORY",
1, // min_args
3, // max_args (--all + optional registry + directory)
"publish-template [--all] [REGISTRY] DIRECTORY",
"Publish a template to a template registry.",
R"HELP(
Publishes a template directory to a template registry.
@@ -41,11 +42,15 @@ Publishes a template directory to a template registry.
Usage:
ds publish-template DIRECTORY
ds publish-template REGISTRY_NAME DIRECTORY
ds publish-template --all DIRECTORY
ds publish-template --all REGISTRY_NAME DIRECTORY
Arguments:
--all Publish all templates in subdirectories of DIRECTORY.
Only publishes subdirectories containing config/.template_info.env
REGISTRY_NAME Optional. Name of the template registry to publish to.
If not specified, uses the first registry with a token.
DIRECTORY Path to the template directory to publish.
DIRECTORY Path to the template directory (or parent directory with --all).
The template is validated before publishing. Two tags are created:
- YYYYMMDD (e.g., 20251228)
@@ -64,6 +69,7 @@ Requirements:
Example:
ds publish-template ./my-template
ds publish-template main ./my-template
ds publish-template --all ./templates-dir
SOS_WRITE_TOKEN=xxx ds publish-template ./my-template
)HELP"
});
@@ -73,6 +79,7 @@ Example:
void publish_template_autocomplete(const CommandContext& ctx) {
if (ctx.args.size() == 0) {
rawout << "--all" << std::endl;
// List registry names
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
for (const auto& reg : registries) {
@@ -262,43 +269,140 @@ static bool publish_to_registry(const std::string& server_url, const std::string
}
}
int publish_template_handler(const CommandContext& ctx) {
std::string registry_name;
std::string template_dir;
// Check if a directory is a valid template (has config/.template_info.env)
static bool is_valid_template_dir(const std::filesystem::path& dir_path) {
return std::filesystem::exists(dir_path / "config" / ".template_info.env");
}
// Parse arguments
if (ctx.args.size() == 1) {
// Only directory provided
template_dir = ctx.args[0];
} else if (ctx.args.size() == 2) {
// Registry name and directory provided
registry_name = ctx.args[0];
template_dir = ctx.args[1];
} else {
error << "Usage: ds publish-template [REGISTRY] DIRECTORY" << std::endl;
// Publish a single template directory
// Returns: 0 = success, 1 = error, 2 = skipped (not a template)
static int publish_single_template(const std::string& template_dir, const std::string& server_url,
const std::string& token, bool quiet = false) {
std::filesystem::path dir_path(template_dir);
std::string template_name = dir_path.filename().string();
if (!quiet) {
maketitle("Publishing template: " + template_name);
info << "Directory: " << template_dir << std::endl;
std::cout << std::endl;
}
// Validate template
if (!quiet) info << "=== Validating Template ===" << std::endl;
if (!template_manager::test_template(template_dir)) {
error << "Template '" << template_name << "' validation failed." << std::endl;
if (!quiet) info << "Run: ds validate-template " << template_dir << std::endl;
return 1;
}
if (!quiet) info << " ✓ Template is valid" << std::endl;
if (!quiet) std::cout << std::endl;
// Calculate directory hash
if (!quiet) info << "=== Calculating Hash ===" << std::endl;
std::string unpacked_hash = hash_directory_recursive(template_dir);
if (unpacked_hash.empty()) {
error << "Failed to calculate directory hash for " << template_name << std::endl;
return 1;
}
if (!quiet) info << " Hash: " << unpacked_hash << std::endl;
if (!quiet) std::cout << std::endl;
// Create temp directory and tarball
std::string temp_dir = std::filesystem::temp_directory_path().string() + "/dropshell-publish-" + std::to_string(getpid());
std::filesystem::create_directories(temp_dir);
// Cleanup on exit
struct TempDirCleaner {
std::string path;
~TempDirCleaner() {
std::filesystem::remove_all(path);
}
} cleaner{temp_dir};
if (!quiet) info << "=== Creating Package ===" << std::endl;
std::string tarball_path = temp_dir + "/" + template_name + ".tgz";
if (!create_tarball(template_dir, tarball_path)) {
error << "Failed to create tarball for " << template_name << std::endl;
return 1;
}
// Resolve template directory to absolute path
std::filesystem::path dir_path(template_dir);
auto file_size = std::filesystem::file_size(tarball_path);
if (!quiet) info << " Created " << template_name << ".tgz (" << (file_size / 1024) << " KB)" << std::endl;
if (!quiet) std::cout << std::endl;
// Publish with tags
if (!quiet) info << "=== Publishing to Registry ===" << std::endl;
std::string date_tag = get_date_tag();
std::vector<std::string> labeltags = {
template_name + ":" + date_tag,
template_name + ":latest"
};
if (!quiet) info << " Tags: " << date_tag << ", latest" << std::endl;
if (!quiet) info << " Uploading..." << std::endl;
if (!publish_to_registry(server_url, token, tarball_path, labeltags, unpacked_hash)) {
error << "Failed to publish template " << template_name << std::endl;
return 1;
}
if (!quiet) {
info << " ✓ Published successfully" << std::endl;
std::cout << std::endl;
}
return 0;
}
int publish_template_handler(const CommandContext& ctx) {
bool publish_all = false;
std::string registry_name;
std::string target_dir;
// Parse arguments - check for --all flag
std::vector<std::string> args;
for (const auto& arg : ctx.args) {
if (arg == "--all" || arg == "-a" || arg == "all") {
publish_all = true;
} else {
args.push_back(arg);
}
}
// Parse remaining arguments
if (args.size() == 1) {
target_dir = args[0];
} else if (args.size() == 2) {
registry_name = args[0];
target_dir = args[1];
} else if (args.empty()) {
error << "Usage: ds publish-template [--all] [REGISTRY] DIRECTORY" << std::endl;
return 1;
} else {
error << "Too many arguments" << std::endl;
error << "Usage: ds publish-template [--all] [REGISTRY] DIRECTORY" << std::endl;
return 1;
}
// Resolve directory to absolute path
std::filesystem::path dir_path(target_dir);
if (dir_path.is_relative()) {
dir_path = std::filesystem::current_path() / dir_path;
}
dir_path = std::filesystem::canonical(dir_path);
template_dir = dir_path.string();
target_dir = dir_path.string();
// Check directory exists
if (!std::filesystem::exists(dir_path) || !std::filesystem::is_directory(dir_path)) {
error << "Directory not found: " << template_dir << std::endl;
error << "Directory not found: " << target_dir << std::endl;
return 1;
}
std::string template_name = dir_path.filename().string();
// Find registry and token
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
tRegistryEntry* selected_registry = nullptr;
std::string effective_token; // Token to use (may come from config or env var)
std::string effective_token;
if (registry_name.empty()) {
// Find first registry with a token
@@ -367,81 +471,79 @@ int publish_template_handler(const CommandContext& ctx) {
server_url.pop_back();
}
maketitle("Publishing template: " + template_name);
info << "Directory: " << template_dir << std::endl;
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << std::endl;
std::cout << std::endl;
if (publish_all) {
// Publish all templates in subdirectories
maketitle("Publishing all templates");
info << "Parent directory: " << target_dir << std::endl;
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << std::endl;
std::cout << std::endl;
// Step 1: Validate template
info << "=== Validating Template ===" << std::endl;
if (!template_manager::test_template(template_dir)) {
error << "Template validation failed. Please fix issues before publishing." << std::endl;
info << "Run: ds validate-template " << template_dir << std::endl;
return 1;
}
info << " ✓ Template is valid" << std::endl;
std::cout << std::endl;
// Step 2: Calculate directory hash (for unpacked content verification)
info << "=== Calculating Hash ===" << std::endl;
std::string unpacked_hash = hash_directory_recursive(template_dir);
if (unpacked_hash.empty()) {
error << "Failed to calculate directory hash" << std::endl;
return 1;
}
info << " Hash: " << unpacked_hash << std::endl;
std::cout << std::endl;
// Step 3: Create temp directory and tarball
std::string temp_dir = std::filesystem::temp_directory_path().string() + "/dropshell-publish-" + std::to_string(getpid());
std::filesystem::create_directories(temp_dir);
// Cleanup on exit
struct TempDirCleaner {
std::string path;
~TempDirCleaner() {
std::filesystem::remove_all(path);
// Find all subdirectories that are valid templates
std::vector<std::filesystem::path> template_dirs;
for (const auto& entry : std::filesystem::directory_iterator(dir_path)) {
if (entry.is_directory() && is_valid_template_dir(entry.path())) {
template_dirs.push_back(entry.path());
}
}
} cleaner{temp_dir};
info << "=== Creating Package ===" << std::endl;
std::string tarball_path = temp_dir + "/" + template_name + ".tgz";
if (!create_tarball(template_dir, tarball_path)) {
error << "Failed to create tarball" << std::endl;
return 1;
// Sort alphabetically
std::sort(template_dirs.begin(), template_dirs.end());
if (template_dirs.empty()) {
error << "No valid templates found in " << target_dir << std::endl;
info << "Templates must have config/.template_info.env" << std::endl;
return 1;
}
info << "Found " << template_dirs.size() << " template(s) to publish:" << std::endl;
for (const auto& tdir : template_dirs) {
info << " - " << tdir.filename().string() << std::endl;
}
std::cout << std::endl;
int success_count = 0;
int fail_count = 0;
std::vector<std::string> failed_templates;
for (const auto& tdir : template_dirs) {
int result = publish_single_template(tdir.string(), server_url, effective_token, false);
if (result == 0) {
success_count++;
} else {
fail_count++;
failed_templates.push_back(tdir.filename().string());
}
}
// Summary
std::cout << std::endl;
maketitle("Publish Summary");
info << "Successfully published: " << success_count << " template(s)" << std::endl;
if (fail_count > 0) {
error << "Failed to publish: " << fail_count << " template(s)" << std::endl;
for (const auto& name : failed_templates) {
error << " - " << name << std::endl;
}
return 1;
}
return 0;
} else {
// Single template publish
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << std::endl;
int result = publish_single_template(target_dir, server_url, effective_token, false);
if (result == 0) {
std::string template_name = dir_path.filename().string();
maketitle("Publish Complete");
info << "Template '" << template_name << "' published successfully!" << std::endl;
info << "Tags: " << get_date_tag() << ", latest" << std::endl;
info << "URL: " << server_url << "/" << template_name << ":latest" << std::endl;
}
return result;
}
auto file_size = std::filesystem::file_size(tarball_path);
info << " Created " << template_name << ".tgz (" << (file_size / 1024) << " KB)" << std::endl;
std::cout << std::endl;
// Step 4: Publish with tags
info << "=== Publishing to Registry ===" << std::endl;
std::string date_tag = get_date_tag();
std::vector<std::string> labeltags = {
template_name + ":" + date_tag,
template_name + ":latest"
};
info << " Tags: " << date_tag << ", latest" << std::endl;
info << " Uploading..." << std::endl;
if (!publish_to_registry(server_url, effective_token, tarball_path, labeltags, unpacked_hash)) {
error << "Failed to publish template" << std::endl;
return 1;
}
info << " ✓ Published successfully" << std::endl;
std::cout << std::endl;
// Summary
maketitle("Publish Complete");
info << "Template '" << template_name << "' published successfully!" << std::endl;
info << "Tags: " << date_tag << ", latest" << std::endl;
info << "URL: " << server_url << "/" << template_name << ":latest" << std::endl;
return 0;
}
} // namespace dropshell

View File

@@ -254,8 +254,8 @@ int validate_handler(const CommandContext& ctx) {
info << "✓ Template '" << template_name << "' passed all checks!" << std::endl;
return 0;
} else if (errors == 0) {
warning << "Template '" << template_name << "' has " << warnings << " warning(s)" << std::endl;
return 0; // Warnings don't fail validation
error << "Template '" << template_name << "' has " << warnings << " warning(s)" << std::endl;
return 1; // Warnings fail validation
} else {
error << "Template '" << template_name << "' has " << errors << " error(s) and " << warnings << " warning(s)" << std::endl;
return 1;