publish.rs
1 use std::ffi::OsString; 2 3 use anyhow::{anyhow, Context as _}; 4 5 use radicle::identity::{Identity, Visibility}; 6 use radicle::node::Handle as _; 7 use radicle::prelude::Id; 8 use radicle::storage::{ReadRepository, SignRepository, WriteRepository, WriteStorage}; 9 10 use crate::terminal as term; 11 use crate::terminal::args::{Args, Error, Help}; 12 13 pub const HELP: Help = Help { 14 name: "publish", 15 description: "Publish a repository to the network", 16 version: env!("CARGO_PKG_VERSION"), 17 usage: r#" 18 Usage 19 20 rad publish [<rid>] [<option>...] 21 22 Publishing a private repository makes it public and discoverable 23 on the network. 24 25 By default, this command will publish the current repository. 26 If an `<rid>` is specified, that repository will be published instead. 27 28 Note that this command can only be run for repositories with a 29 single delegate. The delegate must be the currently authenticated 30 user. For repositories with more than one delegate, the `rad id` 31 command must be used. 32 33 Options 34 35 --help Print help 36 "#, 37 }; 38 39 #[derive(Default, Debug)] 40 pub struct Options { 41 pub rid: Option<Id>, 42 } 43 44 impl Args for Options { 45 fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> { 46 use lexopt::prelude::*; 47 48 let mut parser = lexopt::Parser::from_args(args); 49 let mut rid = None; 50 51 while let Some(arg) = parser.next()? { 52 match arg { 53 Long("help") | Short('h') => { 54 return Err(Error::Help.into()); 55 } 56 Value(val) if rid.is_none() => { 57 rid = Some(term::args::rid(&val)?); 58 } 59 arg => { 60 return Err(anyhow!(arg.unexpected())); 61 } 62 } 63 } 64 65 Ok((Options { rid }, vec![])) 66 } 67 } 68 69 pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { 70 let profile = ctx.profile()?; 71 let rid = match options.rid { 72 Some(rid) => rid, 73 None => radicle::rad::cwd() 74 .map(|(_, rid)| rid) 75 .context("Current directory is not a radicle project")?, 76 }; 77 78 let repo = profile.storage.repository_mut(rid)?; 79 let mut identity = Identity::load_mut(&repo)?; 80 let mut doc = identity.doc().clone(); 81 82 if doc.visibility.is_public() { 83 return Err(Error::WithHint { 84 err: anyhow!("repository is already public"), 85 hint: "to announce the repository to the network, run `rad sync --inventory`", 86 } 87 .into()); 88 } 89 if !doc.is_delegate(profile.id()) { 90 return Err(anyhow!("only the repository delegate can publish it")); 91 } 92 if doc.delegates.len() > 1 { 93 return Err(Error::WithHint { 94 err: anyhow!( 95 "only repositories with a single delegate can be published with this command" 96 ), 97 hint: "see `rad id --help` to publish repositories with more than one delegate", 98 } 99 .into()); 100 } 101 let signer = profile.signer()?; 102 103 // Update identity document. 104 doc.visibility = Visibility::Public; 105 106 identity.update("Publish repository", "", &doc, &signer)?; 107 repo.sign_refs(&signer)?; 108 repo.set_identity_head()?; 109 repo.validate()?; 110 111 term::success!( 112 "Repository is now {}", 113 term::format::visibility(&doc.visibility) 114 ); 115 116 let mut node = radicle::Node::new(profile.socket()); 117 if node.is_running() { 118 let spinner = term::spinner("Announcing to network.."); 119 node.announce_inventory()?; 120 spinner.finish(); 121 } else { 122 term::warning(format!( 123 "Your node is not running. Start your node with {} to announce your repository \ 124 to the network", 125 term::format::command("rad node start") 126 )); 127 } 128 129 Ok(()) 130 }