import { readFileSync } from "node:fs";
import { Command } from "commander";
import chalk from "chalk";
import { checkPlugin, infoTarget, metadataTarget, packPlugin, submitDraft } from "./core.js";
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
const program = new Command()
.name("hf-pack")
.description("Validate, build, and bundle HaloForge plugins into .hfpkg archives")
.version(packageJson.version)
.showSuggestionAfterError();
program
.command("check")
.argument("<plugin-dir>", "path to the plugin directory")
.action(wrap(async (pluginDir) => {
await checkPlugin(pluginDir);
}));
program
.command("info")
.argument("<path>", "path to a plugin directory or .hfpkg archive")
.action(wrap(async (targetPath) => {
await infoTarget(targetPath);
}));
program
.command("pack")
.argument("<plugin-dir>", "path to the plugin directory")
.option("-o, --out <dir>", "output directory for the .hfpkg file")
.option("-r, --release", "build backend and frontend in release mode", false)
.option("--no-backend", "skip building the Rust backend and package prebuilt native files")
.option("--no-frontend", "skip building the frontend and package prebuilt frontend files")
.option("--target <triple>", "Rust compilation target triple")
.action(wrap(async (pluginDir, options) => {
await packPlugin(pluginDir, {
out: options.out,
release: options.release,
noBackend: options.backend === false,
noFrontend: options.frontend === false,
target: options.target,
});
}));
program
.command("metadata")
.argument("<path>", "path to a .hfpkg artifact")
.option("--artifact-url <url>", "public HTTPS download URL for the packaged plugin artifact")
.option("--source <source>", "catalog source label", "official")
.option("--slug <slug>", "catalog item slug override")
.option("--homepage-url <url>", "homepage URL override")
.option("--repository-url <url>", "repository URL override")
.option("--signing-key-id <id>", "signing key id to embed in generated metadata")
.option("--signing-key-base64 <key>", "base64-encoded signing key")
.option("--signing-key-env <name>", "environment variable containing the signing key", "HF_PLUGIN_SIGNING_PRIVATE_KEY")
.option("--signature <signature>", "precomputed signature to embed")
.option("--signature-algorithm <algorithm>", "signature algorithm label", "ed25519")
.option("-o, --output <path>", "output path for the JSON draft")
.option("--pretty", "pretty-print JSON output", false)
.action(wrap(async (targetPath, options) => {
await metadataTarget(targetPath, options);
}));
program
.command("submit")
.argument("<path>", "path to catalog draft JSON generated by `hf-pack metadata`")
.option("--api-base-url <url>", "HaloForge admin API base URL", "https://admin.haloforge.link")
.option("--token <token>", "bearer token value")
.option("--token-env <name>", "environment variable containing the admin bearer token", "HF_ADMIN_TOKEN")
.action(wrap(async (draftPath, options) => {
await submitDraft(draftPath, options);
}));
await program.parseAsync(process.argv);
function wrap(action) {
return async (...args) => {
try {
await action(...args);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`${chalk.red.bold("error:")} ${message}`);
process.exitCode = 1;
}
};
}