1use anyhow::Result;
7use clap::{Parser, Subcommand};
8use colored::Colorize;
9
10#[derive(Parser, Debug)]
12pub struct RegistryCommand {
13 #[command(subcommand)]
15 pub command: RegistryCommands
16}
17
18#[derive(Subcommand, Debug)]
20pub enum RegistryCommands {
21 Init {
23 #[arg(default_value = ".")]
25 path: std::path::PathBuf
26 },
27 #[command(alias = "gen-meta")]
29 GenerateMetadata,
30 #[command(aliases = ["lint", "audit"])]
32 Check,
33 #[command(alias = "add-pkg")]
35 AddPackage {
36 name: Option<String>,
38 #[arg(long, short)]
40 repo: Option<String>
41 },
42 #[command(alias = "sec")]
44 AddAdvisory {
45 package: Option<String>,
47 #[arg(long, short)]
49 repo: Option<String>
50 },
51 #[command(alias = "snapshot")]
53 Zrepo {
54 #[arg(default_value = ".")]
56 path: std::path::PathBuf,
57 #[arg(long, short = 'o')]
59 output: Option<std::path::PathBuf>,
60 #[arg(long)]
62 sign: Option<String>
63 }
64}
65
66pub 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
101fn 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
145fn 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}