use std::fs;
use std::io::{self, Write};
use crate::commands::require_project_root;
use crate::project::credentials::{get_token, Credentials};
use crate::project::manifest::Manifest;
use crate::project::registry::{
create_tarball, is_git_dirty, PublishMetadata, RegistryClient,
};
pub(crate) fn cmd_publish(
registry: Option<&str>,
dry_run: bool,
allow_dirty: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let project_root = require_project_root()?;
let manifest = Manifest::load(&project_root)?;
let name = &manifest.package.name;
let version = &manifest.package.version;
println!("Packaging {} v{}", name, version);
let registry_url = registry.unwrap_or(RegistryClient::default_url());
let token = get_token(registry_url).ok_or_else(|| {
format!(
"No authentication token found for {}.\n\
Run 'largo login' or set LOGOS_TOKEN environment variable.",
registry_url
)
})?;
let entry_path = project_root.join(&manifest.package.entry);
if !entry_path.exists() {
return Err(format!(
"Entry point '{}' not found",
manifest.package.entry
).into());
}
if !allow_dirty && is_git_dirty(&project_root) {
return Err(
"Working directory has uncommitted changes.\n\
Use --allow-dirty to publish anyway.".into()
);
}
println!("Creating package tarball...");
let tarball = create_tarball(&project_root)?;
println!(" Package size: {} bytes", tarball.len());
let readme = project_root.join("README.md");
let readme_content = if readme.exists() {
fs::read_to_string(&readme).ok()
} else {
None
};
let metadata = PublishMetadata {
name: name.clone(),
version: version.clone(),
description: manifest.package.description.clone(),
repository: None, homepage: None,
license: None,
keywords: vec![],
entry_point: manifest.package.entry.clone(),
dependencies: manifest
.dependencies
.iter()
.map(|(k, v)| (k.clone(), v.to_string()))
.collect(),
readme: readme_content,
};
if dry_run {
println!("\n[dry-run] Would publish to {}", registry_url);
println!("[dry-run] Package validated successfully");
return Ok(());
}
println!("Uploading to {}...", registry_url);
let client = RegistryClient::new(registry_url, &token);
let result = client.publish(name, version, &tarball, &metadata)?;
println!(
"\nPublished {} v{} to {}",
result.package, result.version, registry_url
);
println!(" SHA256: {}", result.sha256);
Ok(())
}
pub(crate) fn cmd_login(
registry: Option<&str>,
token: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let registry_url = registry.unwrap_or(RegistryClient::default_url());
let token = match token {
Some(t) => t,
None => {
println!("To get a token, visit: {}/auth/github", registry_url);
println!("Then generate an API token from your profile.");
println!();
print!("Enter token for {}: ", registry_url);
io::stdout().flush()?;
let mut line = String::new();
io::stdin().read_line(&mut line)?;
line.trim().to_string()
}
};
if token.is_empty() {
return Err("Token cannot be empty".into());
}
println!("Validating token...");
let client = RegistryClient::new(registry_url, &token);
let user_info = client.validate_token()?;
let mut creds = Credentials::load().unwrap_or_default();
creds.set_token(registry_url, &token);
creds.save()?;
println!("Logged in as {} to {}", user_info.login, registry_url);
Ok(())
}
pub(crate) fn cmd_logout(registry: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let registry_url = registry.unwrap_or(RegistryClient::default_url());
let mut creds = Credentials::load().unwrap_or_default();
if creds.get_token(registry_url).is_none() {
println!("Not logged in to {}", registry_url);
return Ok(());
}
creds.remove_token(registry_url);
creds.save()?;
println!("Logged out from {}", registry_url);
Ok(())
}