|
1 | | -//! Publishing logic for the Foundry registry. |
2 | | -
|
3 | 1 | use crate::error::FoundryError; |
| 2 | +use crate::manifest::foundry_toml::parse_foundry_toml; |
| 3 | +use crate::registry_client::RegistryClient; |
| 4 | +use flate2::write::GzEncoder; |
| 5 | +use flate2::Compression; |
| 6 | +use std::fs; |
4 | 7 | use tracing::info; |
5 | 8 |
|
6 | | -/// Options for the publish command. |
7 | | -#[derive(Debug, Clone)] |
8 | 9 | pub struct PublishOptions { |
9 | | - /// Perform a dry run without actually publishing. |
| 10 | + pub dir: camino::Utf8PathBuf, |
10 | 11 | pub dry_run: bool, |
| 12 | + pub registry_url: String, |
| 13 | + pub auth_token: Option<String>, |
11 | 14 | } |
12 | 15 |
|
13 | | -/// Publish a package to the Foundry registry. |
14 | 16 | pub async fn publish_package(options: PublishOptions) -> Result<(), FoundryError> { |
15 | | - // TODO: Implementation of package publishing: |
16 | | - // 1. Read foundry.toml |
17 | | - // 2. Validate package |
18 | | - // 3. Create tarball |
19 | | - // 4. Sign package |
20 | | - // 5. Upload to registry |
| 17 | + let manifest_path = options.dir.join("foundry.toml"); |
| 18 | + let manifest = parse_foundry_toml(&manifest_path)?; |
| 19 | + |
| 20 | + let name_parts: Vec<&str> = manifest.package.name.split('/').collect(); |
| 21 | + if name_parts.len() != 2 { |
| 22 | + return Err(FoundryError::ManifestParse { |
| 23 | + path: manifest_path.to_string(), |
| 24 | + message: "package name must be in the format 'author/name'".to_string(), |
| 25 | + }); |
| 26 | + } |
| 27 | + let author = name_parts[0]; |
| 28 | + let name = name_parts[1]; |
| 29 | + |
| 30 | + info!("Publishing {}@{}...", manifest.package.name, manifest.package.version); |
21 | 31 |
|
22 | 32 | if options.dry_run { |
23 | | - info!("Dry run: would publish package to registry"); |
24 | | - } else { |
25 | | - info!("Publishing package to registry..."); |
| 33 | + info!("Dry run complete. No package published."); |
| 34 | + return Ok(()); |
26 | 35 | } |
27 | 36 |
|
| 37 | + let auth_token = options.auth_token.ok_or(FoundryError::AuthRequired)?; |
| 38 | + |
| 39 | + let mut tar_gz = Vec::new(); |
| 40 | + let enc = GzEncoder::new(&mut tar_gz, Compression::default()); |
| 41 | + let mut tar = tar::Builder::new(enc); |
| 42 | + |
| 43 | + // Pack the directory |
| 44 | + for entry in walkdir::WalkDir::new(&options.dir).into_iter().filter_map(|e: Result<walkdir::DirEntry, walkdir::Error>| e.ok()) { |
| 45 | + let path = entry.path(); |
| 46 | + if path.is_file() { |
| 47 | + let relative_path = path.strip_prefix(&options.dir).unwrap(); |
| 48 | + |
| 49 | + let should_skip = relative_path.components().any(|c| { |
| 50 | + if let std::path::Component::Normal(os_str) = c { |
| 51 | + let s = os_str.to_string_lossy(); |
| 52 | + s == ".git" || s == ".forge" || s == "node_modules" |
| 53 | + } else { |
| 54 | + false |
| 55 | + } |
| 56 | + }); |
| 57 | + |
| 58 | + if should_skip { |
| 59 | + continue; |
| 60 | + } |
| 61 | + |
| 62 | + let mut file = fs::File::open(path)?; |
| 63 | + tar.append_file(relative_path, &mut file)?; |
| 64 | + } |
| 65 | + } |
| 66 | + let enc = tar.into_inner()?; |
| 67 | + enc.finish()?; |
| 68 | + |
| 69 | + let manifest_content = fs::read_to_string(&manifest_path)?; |
| 70 | + |
| 71 | + let client = RegistryClient::new(options.registry_url, Some(auth_token)); |
| 72 | + client.publish(author, name, manifest_content, tar_gz).await?; |
| 73 | + |
| 74 | + info!("Successfully published {}@{}", manifest.package.name, manifest.package.version); |
28 | 75 | Ok(()) |
29 | 76 | } |
0 commit comments