pray-cli 1.7.0

Package manager for the language placed before inference — Prayfile CLI (binary: pray)
use crate::auth_client::{
    current_signer as current_signer_from_session,
    current_signer_fingerprint as current_signer_fingerprint_from_session,
};
use crate::project_paths::workspace_root;
#[cfg(feature = "auth")]
use pray_core::auth::RegistryAuthStore;
use pray_core::registry::{RegistryIndex, RegistryPackageMetadata};
use pray_core::ssh_identity::active_ssh_user_fingerprint;
use pray_core::{PrayError, PrayResult};
use pray_transport::{TorrentConfig, TorrentTransport};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

pub(crate) fn current_signer() -> PrayResult<String> {
    let session_root = workspace_root();
    if let Some(email) = current_signer_from_session(&session_root) {
        return Ok(email);
    }

    if let Ok(token) = std::env::var("PRAY_SESSION_TOKEN") {
        #[cfg(feature = "auth")]
        {
            let auth_root = std::env::var("PRAY_AUTH_ROOT")
                .map(PathBuf::from)
                .unwrap_or_else(|_| session_root.clone());
            if let Ok(store) = RegistryAuthStore::open(&auth_root) {
                if let Ok(Some(session)) = store.resolve_session(&token) {
                    return Ok(session.email);
                }
            }
        }

        #[cfg(not(feature = "auth"))]
        {
            let _ = token;
            return Err(PrayError::Unsupported(
                "this build was compiled without auth support".to_string(),
            ));
        }
    }

    Ok(std::env::var("PRAY_SIGNER")
        .or_else(|_| std::env::var("USER"))
        .or_else(|_| std::env::var("USERNAME"))
        .unwrap_or_else(|_| "unknown".to_string()))
}

pub(crate) fn current_signer_fingerprint() -> Option<String> {
    if let Some(fingerprint) = active_ssh_user_fingerprint() {
        return Some(fingerprint);
    }
    current_signer_fingerprint_from_session(&workspace_root())
}

pub(crate) fn current_timestamp() -> PrayResult<String> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|error| PrayError::Resolution(error.to_string()))
        .map(|duration| duration.as_secs().to_string())
}

pub(crate) fn load_registry_index(root: &Path) -> PrayResult<RegistryIndex> {
    let path = root.join("v1/index.json");
    let Ok(text) = fs::read_to_string(&path) else {
        return Ok(RegistryIndex {
            spec: "prayfile-distribution-1".to_string(),
            packages: Vec::new(),
        });
    };
    let index: RegistryIndex = serde_json::from_str(&text).map_err(|error| PrayError::Parse {
        kind: "registry index",
        message: error.to_string(),
    })?;
    if index.spec != "prayfile-distribution-1" {
        return Err(PrayError::Resolution(format!(
            "unsupported registry index spec: {}",
            index.spec
        )));
    }
    Ok(index)
}

pub(crate) fn load_registry_package_metadata(
    path: &Path,
    package_name: &str,
) -> PrayResult<RegistryPackageMetadata> {
    if path.exists() {
        let text = fs::read_to_string(path)?;
        let metadata: RegistryPackageMetadata =
            serde_json::from_str(&text).map_err(|error| PrayError::Parse {
                kind: "registry metadata",
                message: error.to_string(),
            })?;
        if metadata.name != package_name {
            return Err(PrayError::Resolution(format!(
                "registry metadata name mismatch: expected {}, found {}",
                package_name, metadata.name
            )));
        }
        Ok(metadata)
    } else {
        Ok(RegistryPackageMetadata {
            name: package_name.to_string(),
            versions: Vec::new(),
        })
    }
}

pub(crate) fn write_registry_index(root: &Path, index: &RegistryIndex) -> PrayResult<()> {
    let path = root.join("v1/index.json");
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(
        path,
        serde_json::to_string_pretty(index)
            .map_err(|error| PrayError::Manifest(error.to_string()))?,
    )?;
    Ok(())
}

pub(crate) fn write_registry_package_metadata(
    path: &Path,
    metadata: &RegistryPackageMetadata,
) -> PrayResult<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(
        path,
        serde_json::to_string_pretty(metadata)
            .map_err(|error| PrayError::Manifest(error.to_string()))?,
    )?;
    Ok(())
}

pub(crate) fn registry_metadata_path(root: &Path, package_name: &str) -> PathBuf {
    root.join("v1/packages")
        .join(package_name)
        .with_extension("json")
}

pub(crate) fn registry_artifact_path(package_name: &str, version: &str) -> String {
    let artifact_name = format!("{}-{}.praypkg", package_name.replace('/', "-"), version);
    format!("v1/artifacts/{package_name}/{version}/{artifact_name}")
}

pub(crate) fn torrent_manifest_path(artifact_path: &str) -> String {
    format!("{artifact_path}.praytorrent.json")
}

pub(crate) fn torrent_manifest_bytes(
    package: &pray_core::resolve::ResolvedPackage,
    artifact_path: &str,
    archive_bytes: &[u8],
) -> PrayResult<Vec<u8>> {
    let torrent_config = TorrentConfig::default();
    let manifest = TorrentTransport::build_manifest(
        package.declaration.name.clone(),
        package.spec.version.clone(),
        artifact_path.to_string(),
        archive_bytes,
        torrent_config.piece_size,
        vec![artifact_path.to_string()],
        torrent_config.bootstrap_trackers,
    );
    serde_json::to_vec_pretty(&manifest).map_err(|error| PrayError::Manifest(error.to_string()))
}

pub(crate) fn write_torrent_manifest(
    root: &Path,
    package: &pray_core::resolve::ResolvedPackage,
    artifact_path: &str,
    archive_bytes: &[u8],
) -> PrayResult<()> {
    let manifest_path = root.join(torrent_manifest_path(artifact_path));
    write_output_bytes(
        &manifest_path,
        &torrent_manifest_bytes(package, artifact_path, archive_bytes)?,
    )
}

pub(crate) fn write_output_bytes(path: &Path, bytes: &[u8]) -> PrayResult<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(path, bytes)?;
    Ok(())
}