Skip to main content

zoi_cli/cmd/
registry.rs

1//! Logic for the `registry` command.
2//!
3//! This module provides commands for managing Zoi registries, including
4//! initialization, metadata generation, and package/advisory management.
5
6use anyhow::Result;
7use clap::{Parser, Subcommand};
8use colored::Colorize;
9
10/// The root registry management command.
11#[derive(Parser, Debug)]
12pub struct RegistryCommand {
13    /// The specific registry subcommand to execute.
14    #[command(subcommand)]
15    pub command: RegistryCommands
16}
17
18/// Available registry subcommands.
19#[derive(Subcommand, Debug)]
20pub enum RegistryCommands {
21    /// Initialize a new Zoi registry
22    Init {
23        /// Path where the registry should be initialized
24        #[arg(default_value = ".")]
25        path: std::path::PathBuf
26    },
27    /// Generate metadata files (packages.json and advisories.json)
28    #[command(alias = "gen-meta")]
29    GenerateMetadata,
30    /// Check registry integrity and validate packages
31    #[command(aliases = ["lint", "audit"])]
32    Check,
33    /// Add a new package to the registry
34    #[command(alias = "add-pkg")]
35    AddPackage {
36        /// Name of the package to add
37        name: Option<String>,
38        /// Repository tier (e.g. community, main)
39        #[arg(long, short)]
40        repo: Option<String>
41    },
42    /// Add a new security advisory for a package
43    #[command(alias = "sec")]
44    AddAdvisory {
45        /// Package name to add an advisory for
46        package: Option<String>,
47        /// Repository tier (e.g. community, main)
48        #[arg(long, short)]
49        repo: Option<String>
50    },
51    /// Build a .zrepo snapshot of this registry, plus its zsync control file
52    #[command(alias = "snapshot")]
53    Zrepo {
54        /// Registry directory to snapshot. Defaults to the current directory
55        #[arg(default_value = ".")]
56        path: std::path::PathBuf,
57        /// Where to write the .zrepo. Defaults to <handle>.zrepo
58        #[arg(long, short = 'o')]
59        output: Option<std::path::PathBuf>,
60        /// Sign the snapshot with a PGP key from the Zoi keyring
61        #[arg(long)]
62        sign: Option<String>
63    }
64}
65
66/// Run the registry management command.
67///
68/// # Errors
69///
70/// This function returns an error if any of the underlying registry operations
71/// (initialization, metadata generation, checking, adding packages/advisories,
72/// or building a snapshot and its update patch) fail.
73pub fn run(args: RegistryCommand) -> Result<()> {
74    let registry_root = std::path::Path::new(".");
75    match args.command {
76        RegistryCommands::Init { path } => crate::pkg::registry::init(&path),
77        RegistryCommands::GenerateMetadata => {
78            crate::pkg::registry::generate_metadata(registry_root)
79        }
80        RegistryCommands::Check => crate::pkg::registry::check(registry_root),
81        RegistryCommands::AddPackage { name, repo } => {
82            crate::pkg::registry::add_package(
83                registry_root,
84                name.as_deref(),
85                repo.as_deref()
86            )
87        }
88        RegistryCommands::AddAdvisory { package, repo } => {
89            crate::pkg::registry::add_advisory(
90                registry_root,
91                package.as_deref(),
92                repo.as_deref()
93            )
94        }
95        RegistryCommands::Zrepo { path, output, sign } => {
96            build_zrepo(&path, output.as_deref(), sign.as_deref())
97        }
98    }
99}
100
101/// Builds a `.zrepo` snapshot of a registry directory.
102///
103/// # Errors
104///
105/// Returns an error if the directory is not a registry or the snapshot cannot
106/// be written.
107fn build_zrepo(
108    path: &std::path::Path,
109    output: Option<&std::path::Path>,
110    sign: Option<&str>
111) -> Result<()> {
112    let output = match output {
113        Some(explicit) => explicit.to_path_buf(),
114        None => default_snapshot_output(path)?
115    };
116
117    let stats = crate::pkg::sync::zrepo::create_zrepo(path, &output, sign)?;
118
119    println!(
120        "{} Created snapshot: {}",
121        "::".bold().green(),
122        output.display()
123    );
124    println!("  Files:       {}", stats.file_count);
125    println!(
126        "  Size:        {} (uncompressed {})",
127        zoi_core::utils::format_bytes(stats.compressed_size),
128        zoi_core::utils::format_bytes(stats.tar_size)
129    );
130    println!("  SHA-256:     {}", stats.sha256);
131    println!(
132        "  Sidecars:    .hash, .size{}",
133        if sign.is_some() { ", .sig" } else { "" }
134    );
135    println!(
136        "{} Publish the snapshot and its .zsync control file together at the \
137         'url' in the registry's 'zrepo' section, and point 'hash' at the \
138         .hash sidecar. Clients use the control file to transfer only the \
139         frames that changed.",
140        "::".bold().blue()
141    );
142    Ok(())
143}
144
145/// Derives a default snapshot filename from the registry's declared handle.
146fn default_snapshot_output(
147    path: &std::path::Path
148) -> Result<std::path::PathBuf> {
149    let handle = zoi_core::config::read_repo_config(path)?.name;
150    let handle = if handle.is_empty() {
151        "registry".to_string()
152    } else {
153        handle
154    };
155    Ok(path.join(format!("{handle}.zrepo")))
156}