diff --git a/Cargo.lock b/Cargo.lock index 08fcebd..f1d4e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1299,19 +1299,6 @@ dependencies = [ "hashbrown 0.15.2", ] -[[package]] -name = "indicatif" -version = "0.17.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" -dependencies = [ - "console", - "number_prefix", - "portable-atomic", - "unicode-width", - "web-time", -] - [[package]] name = "inout" version = "0.1.3" @@ -1486,7 +1473,6 @@ dependencies = [ "config", "dialoguer", "env_logger", - "indicatif", "log", "nostr-sdk", "reqwest", @@ -1656,12 +1642,6 @@ dependencies = [ "libm", ] -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.36.7" @@ -1897,12 +1877,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -3081,16 +3055,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-roots" version = "0.26.8" diff --git a/Cargo.toml b/Cargo.toml index ecfa29f..efed6cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread serde = { version = "1.0.217", features = ["derive"] } async-trait = "0.1.86" semver = "1.0.25" -indicatif = "0.17.11" dialoguer = "0.11.0" env_logger = "0.11.6" sha2 = "0.10.8" diff --git a/src/main.rs b/src/main.rs index 36b49b5..30b9c2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,10 +9,11 @@ use config::{Config, File}; use log::info; use nostr_sdk::prelude::Coordinate; use nostr_sdk::{Client, EventBuilder, Keys, Kind, Tag}; +use semver::Version; use std::path::PathBuf; #[derive(clap::Parser)] -#[command(version, about)] +#[command(about)] struct Args { /// User specified config path #[arg(long, short)] @@ -21,6 +22,10 @@ struct Args { /// Relay to publish events to #[arg(long)] pub relay: Vec, + + /// Use specific version + #[arg(long)] + pub version: Option, } #[tokio::main] @@ -41,16 +46,19 @@ async fn main() -> Result<()> { let repo: Box = (&manifest).try_into()?; - let releases = repo.get_releases().await?; + let mut releases = repo.get_releases().await?; info!("Found {} release(s)", releases.len()); - if let Some(release) = releases.first() { + releases.sort_by(|a, b| b.version.partial_cmp(&a.version).unwrap()); + if let Some(release) = releases.iter_mut().find(|r| { + args.version + .as_ref() + .map(|v| v == &r.version) + .unwrap_or(true) + }) { info!("Starting publish of release {}", release.version); - info!("Artifacts: "); - for a in &release.artifacts { - info!(" - {}", a); - } + if !dialoguer::Confirm::new() .default(false) .with_prompt(format!("Publish v{}?", release.version)) @@ -59,6 +67,20 @@ async fn main() -> Result<()> { return Ok(()); } + release.load_artifacts().await?; + info!("Artifacts: "); + for a in &release.artifacts { + info!(" - {}", a); + } + + if !dialoguer::Confirm::new() + .default(false) + .with_prompt("Continue?") + .interact()? + { + return Ok(()); + } + let key = dialoguer::Password::new() .with_prompt("Enter nsec:") .interact()?; diff --git a/src/repo/github.rs b/src/repo/github.rs index 8012c13..9492ceb 100644 --- a/src/repo/github.rs +++ b/src/repo/github.rs @@ -1,6 +1,7 @@ -use crate::repo::{load_artifact_url, Repo, RepoRelease}; +use crate::repo::{ArtifactMetadata, Platform, Repo, RepoArtifact, RepoRelease, RepoResource}; use anyhow::{anyhow, Result}; use log::{info, warn}; +use nostr_sdk::prelude::hex; use nostr_sdk::Url; use reqwest::header::{HeaderMap, ACCEPT, USER_AGENT}; use reqwest::Client; @@ -60,6 +61,19 @@ struct GithubReleaseArtifact { pub size: u64, pub content_type: String, pub browser_download_url: String, + pub digest: Option, +} + +fn decode_digest(digest: &Option) -> Result> { + if let Some(s) = digest { + if s.starts_with("sha256:") { + Ok(hex::decode(&s[7..])?) + } else { + Ok(vec![]) + } + } else { + Ok(vec![]) + } } #[async_trait::async_trait] @@ -83,31 +97,40 @@ impl Repo for GithubRepo { for release in gh_release { let mut artifacts = vec![]; for gh_artifact in release.assets { - match load_artifact_url(&gh_artifact.browser_download_url).await { - Ok(a) => artifacts.push(a), - Err(e) => warn!( - "Failed to load artifact {}: {}", - gh_artifact.browser_download_url, e - ), - } + artifacts.push(RepoArtifact { + name: gh_artifact.name, + size: gh_artifact.size, + location: RepoResource::Remote(gh_artifact.browser_download_url), + content_type: gh_artifact.content_type, + platform: Platform::Unknown, + metadata: ArtifactMetadata::Empty, + hash: decode_digest(&gh_artifact.digest)?, + }) } if artifacts.is_empty() { warn!("No artifacts found for {}", release.tag_name); continue; } + let version = match Version::parse(if release.tag_name.starts_with("v") { + &release.tag_name[1..] + } else { + &release.tag_name + }) { + Ok(v) => v, + Err(e) => { + warn!( + "Could not load version for release {} {}", + release.tag_name, e + ); + continue; + } + }; releases.push(RepoRelease { - version: Version::parse(if release.tag_name.starts_with("v") { - &release.tag_name[1..] - } else { - &release.tag_name - })?, + version, description: Some(release.body), url: Some(release.url), artifacts, }); - - //TODO: handle more than one release - break; } Ok(releases) } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 0c647b3..8c7c6b1 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -115,6 +115,7 @@ impl TryInto for RepoArtifact { ])?); } } + ArtifactMetadata::Empty => {} } Ok(b) } @@ -122,6 +123,7 @@ impl TryInto for RepoArtifact { #[derive(Debug, Clone)] pub enum ArtifactMetadata { + Empty, APK { manifest: AndroidManifest, signature_blocks: Vec, @@ -148,6 +150,7 @@ impl Display for ArtifactMetadata { .join(", ") ) } + ArtifactMetadata::Empty => write!(f, "empty"), } } } @@ -160,6 +163,7 @@ pub enum Platform { Windows { arch: Architecture }, Linux { arch: Architecture }, Web, + Unknown, } impl Display for Platform { @@ -215,6 +219,7 @@ impl Display for Platform { } ), Platform::Web => write!(f, "web"), + Platform::Unknown => write!(f, "unknown"), } } } @@ -314,6 +319,21 @@ impl RepoRelease { ret.push(b.sign(signer).await?); Ok(ret) } + + /// Download and prepare release artifacts + pub async fn load_artifacts(&mut self) -> Result<()> { + let mut new_artifacts = vec![]; + for a in &self.artifacts { + if let RepoResource::Remote(url) = &a.location { + match load_artifact_url(&url).await { + Ok(artifact) => new_artifacts.push(artifact), + Err(e) => warn!("Failed to load artifact {}: {}", &url, e), + } + } + } + self.artifacts = new_artifacts; + Ok(()) + } } /// Generic artifact repository @@ -344,7 +364,6 @@ impl TryInto> for &Manifest { async fn load_artifact_url(url: &str) -> Result { info!("Downloading artifact {}", url); let u = Url::parse(url)?; - let rsp = reqwest::get(u.clone()).await?; let id = hex::encode(Sha256::digest(url.as_bytes())); let mut tmp = temp_dir().join(id); tmp.set_extension( @@ -355,6 +374,7 @@ async fn load_artifact_url(url: &str) -> Result { .unwrap(), ); if !tmp.exists() { + let rsp = reqwest::get(u.clone()).await?; let mut tmp_file = tokio::fs::File::create(&tmp).await?; let mut rsp_stream = rsp.bytes_stream(); while let Some(data) = rsp_stream.next().await {