upstream-rs 1.11.1

Fetch package updates directly from the source.
Documentation
use anyhow::{Result, anyhow, bail};
use console::style;

use crate::application::operations::install_operation::InstallOperation;
use crate::models::{
    common::enums::{Channel, Filetype, Provider},
    upstream::{InstallType, Package},
};
use crate::providers::discovery::{SourceKind, infer_source};
use crate::providers::provider_manager::ProviderManager;
use crate::services::builder::{BuildProfile, BuildRequest, worker::BuildWorker};
use crate::services::storage::package_storage::PackageStorage;
use crate::services::trust::MinisignPublicKey;
use crate::utils::static_paths::UpstreamPaths;

pub struct BuildOperation<'a> {
    provider_manager: &'a ProviderManager,
    package_storage: &'a mut PackageStorage,
    paths: &'a UpstreamPaths,
    trusted_keys: Vec<MinisignPublicKey>,
}

pub struct BuildCommandInput {
    pub name: String,
    pub repo_slug: String,
    pub tag: Option<String>,
    pub branch: Option<String>,
    pub provider: Option<Provider>,
    pub base_url: Option<String>,
    pub channel: Channel,
    pub desktop: bool,
    pub build_profile: Option<BuildProfile>,
    pub build_output: Option<String>,
}

impl<'a> BuildOperation<'a> {
    pub fn new(
        provider_manager: &'a ProviderManager,
        package_storage: &'a mut PackageStorage,
        paths: &'a UpstreamPaths,
        trusted_keys: Vec<MinisignPublicKey>,
    ) -> Self {
        Self {
            provider_manager,
            package_storage,
            paths,
            trusted_keys,
        }
    }

    pub async fn build_and_install(&mut self, input: BuildCommandInput) -> Result<()> {
        let (resolved_repo_slug, resolved_provider, resolved_base_url) = if let Some(selected) =
            input.provider
        {
            (input.repo_slug.clone(), selected, input.base_url.clone())
        } else {
            let mut discovered = infer_source(&input.repo_slug)?;
            if let Some(base) = input.base_url.clone() {
                discovered.base_url = Some(base);
            }

            match discovered.kind {
                SourceKind::Repository | SourceKind::ForgeUrl => {}
                SourceKind::DirectAsset | SourceKind::DownloadPage => {
                    return Err(anyhow!(
                        "Build requires a forge repository source (github/gitlab/gitea), got '{}'",
                        input.repo_slug
                    ));
                }
            }

            (
                discovered.repo_slug,
                discovered.provider,
                discovered.base_url,
            )
        };

        if !matches!(
            resolved_provider,
            Provider::Github | Provider::Gitlab | Provider::Gitea
        ) {
            bail!("Build supports forge providers only (github/gitlab/gitea)");
        }

        println!(
            "{}",
            style(format!(
                "Building {} from {} ...",
                &input.name, &resolved_provider
            ))
            .cyan()
        );

        let worker = BuildWorker::new(self.provider_manager);
        let output = worker
            .build(
                BuildRequest {
                    name: input.name.clone(),
                    repo_slug: resolved_repo_slug.clone(),
                    provider: resolved_provider.clone(),
                    base_url: resolved_base_url.clone(),
                    version_tag: input.tag,
                    branch: input.branch,
                    requested_profile: input.build_profile,
                    build_output: input.build_output.map(std::path::PathBuf::from),
                },
                input.channel.clone(),
            )
            .await?;

        println!(
            "{}",
            style(format!(
                "Built artifact: {} ({:?})",
                output.artifact_path.display(),
                output.profile
            ))
            .cyan()
        );

        let mut package = Package::with_defaults(
            input.name,
            resolved_repo_slug,
            Filetype::Binary,
            None,
            None,
            input.channel,
            resolved_provider,
            resolved_base_url,
        );
        package.install_type = InstallType::Build;
        package.build_branch = output.branch.clone();
        package.build_commit = output.commit.clone();

        let mut install_operation = InstallOperation::new(
            self.provider_manager,
            self.package_storage,
            self.paths,
            self.trusted_keys.clone(),
        )?;
        let mut msg = Some(|line: &str| println!("{line}"));
        let installed = install_operation
            .install_local_artifact(
                package,
                &output.artifact_path,
                output.version,
                &input.desktop,
                &mut msg,
            )
            .await?;

        println!(
            "{}",
            style(format!("Build install complete for '{}'.", installed.name)).green()
        );

        Ok(())
    }
}