aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/mozart-archiver/Cargo.toml2
-rw-r--r--crates/mozart-archiver/src/lib.rs3
-rw-r--r--crates/mozart-archiver/src/manager.rs300
-rw-r--r--crates/mozart/src/commands/archive.rs367
4 files changed, 421 insertions, 251 deletions
diff --git a/crates/mozart-archiver/Cargo.toml b/crates/mozart-archiver/Cargo.toml
index 6d96024..6e2dbfd 100644
--- a/crates/mozart-archiver/Cargo.toml
+++ b/crates/mozart-archiver/Cargo.toml
@@ -4,10 +4,12 @@ version.workspace = true
edition.workspace = true
[dependencies]
+mozart-registry.workspace = true
anyhow.workspace = true
bzip2.workspace = true
flate2.workspace = true
regex.workspace = true
+serde_json.workspace = true
sha1.workspace = true
tar.workspace = true
zip.workspace = true
diff --git a/crates/mozart-archiver/src/lib.rs b/crates/mozart-archiver/src/lib.rs
index 575f024..30c678a 100644
--- a/crates/mozart-archiver/src/lib.rs
+++ b/crates/mozart-archiver/src/lib.rs
@@ -5,6 +5,9 @@ use std::fs;
use std::io::Write as IoWrite;
use std::path::{Path, PathBuf};
+pub mod manager;
+pub use manager::{ArchiveManager, ArchivePackage};
+
/// A compiled exclude pattern derived from a gitignore-style rule.
pub struct ExcludePattern {
regex: Regex,
diff --git a/crates/mozart-archiver/src/manager.rs b/crates/mozart-archiver/src/manager.rs
new file mode 100644
index 0000000..bd3cafa
--- /dev/null
+++ b/crates/mozart-archiver/src/manager.rs
@@ -0,0 +1,300 @@
+use std::path::{Path, PathBuf};
+
+use crate::{
+ ArchiveFormat, collect_archivable_files, create_archive, generate_archive_filename,
+ parse_composer_excludes, parse_gitattributes, parse_gitignore_pattern, self_exclusion_patterns,
+};
+
+/// A package to be archived.
+///
+/// Mirrors the role of Composer's `CompletePackageInterface` as input to
+/// `ArchiveManager::archive()`. The `Root` variant points at an already-checked-out
+/// source tree; the `Remote` variant carries dist metadata that the manager will
+/// download and extract to a temporary directory.
+pub enum ArchivePackage {
+ Root {
+ name: String,
+ version: Option<String>,
+ source_dir: PathBuf,
+ },
+ Remote {
+ name: String,
+ version: String,
+ dist_url: String,
+ dist_type: String,
+ dist_shasum: Option<String>,
+ dist_reference: Option<String>,
+ source_reference: Option<String>,
+ },
+}
+
+impl ArchivePackage {
+ fn name(&self) -> &str {
+ match self {
+ Self::Root { name, .. } | Self::Remote { name, .. } => name,
+ }
+ }
+
+ fn version(&self) -> Option<&str> {
+ match self {
+ Self::Root { version, .. } => version.as_deref(),
+ Self::Remote { version, .. } => Some(version),
+ }
+ }
+
+ fn dist_reference(&self) -> Option<&str> {
+ match self {
+ Self::Root { .. } => None,
+ Self::Remote { dist_reference, .. } => dist_reference.as_deref(),
+ }
+ }
+
+ fn dist_type(&self) -> Option<&str> {
+ match self {
+ Self::Root { .. } => None,
+ Self::Remote { dist_type, .. } => Some(dist_type),
+ }
+ }
+
+ fn source_reference(&self) -> Option<&str> {
+ match self {
+ Self::Root { .. } => None,
+ Self::Remote {
+ source_reference, ..
+ } => source_reference.as_deref(),
+ }
+ }
+}
+
+/// Holds an extracted source directory plus, for remote packages, a tempdir
+/// that must outlive `source_dir`. Drop removes the tempdir.
+struct AcquiredSource {
+ source_dir: PathBuf,
+ archive_name: Option<String>,
+ archive_excludes: Vec<String>,
+ _temp_dir: Option<PathBuf>,
+}
+
+impl Drop for AcquiredSource {
+ fn drop(&mut self) {
+ if let Some(ref dir) = self._temp_dir {
+ let _ = std::fs::remove_dir_all(dir);
+ }
+ }
+}
+
+/// Read `archive.name` and `archive.exclude` from a composer.json file.
+fn read_archive_config(composer_json_path: &Path) -> anyhow::Result<(Option<String>, Vec<String>)> {
+ let content = std::fs::read_to_string(composer_json_path)?;
+ let value: serde_json::Value = serde_json::from_str(&content)?;
+
+ let name = value
+ .get("archive")
+ .and_then(|a| a.get("name"))
+ .and_then(|n| n.as_str())
+ .map(|s| s.to_string());
+
+ let excludes = value
+ .get("archive")
+ .and_then(|a| a.get("exclude"))
+ .and_then(|e| e.as_array())
+ .map(|arr| {
+ arr.iter()
+ .filter_map(|v| v.as_str())
+ .map(|s| s.to_string())
+ .collect()
+ })
+ .unwrap_or_default();
+
+ Ok((name, excludes))
+}
+
+/// Manages the creation of package archives.
+///
+/// Mirrors Composer's `Composer\Package\Archiver\ArchiveManager`.
+pub struct ArchiveManager;
+
+impl Default for ArchiveManager {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl ArchiveManager {
+ pub fn new() -> Self {
+ ArchiveManager
+ }
+
+ /// Build the parts that make up a package archive's filename.
+ fn package_filename_parts(package: &ArchivePackage, archive_name: Option<&str>) -> String {
+ generate_archive_filename(
+ package.name(),
+ archive_name,
+ package.version(),
+ package.dist_reference(),
+ package.dist_type(),
+ package.source_reference(),
+ )
+ }
+
+ /// Generate the archive filename (without extension) for a package, using
+ /// any `archive.name` override from the package's source composer.json.
+ pub fn package_filename(package: &ArchivePackage) -> String {
+ let archive_name = match package {
+ ArchivePackage::Root { source_dir, .. } => {
+ read_archive_config(&source_dir.join("composer.json"))
+ .ok()
+ .and_then(|(n, _)| n)
+ }
+ ArchivePackage::Remote { .. } => None,
+ };
+ Self::package_filename_parts(package, archive_name.as_deref())
+ }
+
+ /// Join filename parts with `-`, mirroring Composer's
+ /// `getPackageFilenameFromParts`.
+ pub fn package_filename_from_parts(parts: &[&str]) -> String {
+ parts.join("-")
+ }
+
+ /// Create an archive of the given package.
+ ///
+ /// For a `Remote` package, the dist is downloaded into a tempdir and
+ /// extracted before archiving; the tempdir is removed afterward. For
+ /// `Root`, the package's `source_dir` is archived in place.
+ ///
+ /// Returns the absolute path to the created archive.
+ pub async fn archive(
+ &self,
+ package: &ArchivePackage,
+ format: &str,
+ target_dir: &Path,
+ file_name: Option<&str>,
+ ignore_filters: bool,
+ files_cache: &mozart_registry::cache::Cache,
+ ) -> anyhow::Result<PathBuf> {
+ let archive_format = ArchiveFormat::parse(format).ok_or_else(|| {
+ anyhow::anyhow!(
+ "Unsupported archive format \"{}\". Supported formats: tar, tar.gz, tar.bz2, zip",
+ format
+ )
+ })?;
+
+ let source = acquire_source(package, files_cache).await?;
+
+ let filename_base = if let Some(file_name) = file_name {
+ file_name.to_string()
+ } else {
+ Self::package_filename_parts(package, source.archive_name.as_deref())
+ };
+
+ // Self-exclusion: prevent the archive from including itself
+ let has_extra_parts = file_name.is_none()
+ && (package.version().is_some()
+ || package.dist_reference().is_some()
+ || package.source_reference().is_some());
+ let self_exclusion_strs = self_exclusion_patterns(&filename_base, has_extra_parts);
+
+ let mut all_patterns = Vec::new();
+ for rule in &self_exclusion_strs {
+ if let Some(p) = parse_gitignore_pattern(rule) {
+ all_patterns.push(p);
+ }
+ }
+
+ if !ignore_filters {
+ let git_patterns = parse_gitattributes(&source.source_dir);
+ all_patterns.extend(git_patterns);
+
+ let composer_patterns = parse_composer_excludes(&source.archive_excludes);
+ all_patterns.extend(composer_patterns);
+ }
+
+ let files = collect_archivable_files(&source.source_dir, &all_patterns)?;
+
+ std::fs::create_dir_all(target_dir)?;
+ let target_dir = target_dir
+ .canonicalize()
+ .unwrap_or_else(|_| target_dir.to_path_buf());
+ let target = target_dir.join(format!("{}.{}", filename_base, archive_format.extension()));
+ create_archive(&source.source_dir, &files, &target, &archive_format)?;
+
+ Ok(target)
+ }
+}
+
+/// Acquire the source tree of a package — either by reusing the root
+/// directory or by downloading and extracting the dist into a tempdir.
+/// Also reads `archive.name` / `archive.exclude` from the package's
+/// composer.json.
+async fn acquire_source(
+ package: &ArchivePackage,
+ files_cache: &mozart_registry::cache::Cache,
+) -> anyhow::Result<AcquiredSource> {
+ match package {
+ ArchivePackage::Root { source_dir, .. } => {
+ let composer_json_path = source_dir.join("composer.json");
+ let (archive_name, archive_excludes) = if composer_json_path.exists() {
+ read_archive_config(&composer_json_path).unwrap_or((None, vec![]))
+ } else {
+ (None, vec![])
+ };
+ Ok(AcquiredSource {
+ source_dir: source_dir.clone(),
+ archive_name,
+ archive_excludes,
+ _temp_dir: None,
+ })
+ }
+ ArchivePackage::Remote {
+ dist_url,
+ dist_type,
+ dist_shasum,
+ ..
+ } => {
+ let temp_base = std::env::temp_dir();
+ let unique = format!(
+ "mozart-archive-{}",
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|d| d.as_nanos())
+ .unwrap_or(0)
+ );
+ let temp_dir = temp_base.join(&unique);
+ std::fs::create_dir_all(&temp_dir)?;
+
+ let bytes = mozart_registry::downloader::download_dist(
+ dist_url,
+ dist_shasum.as_deref(),
+ None,
+ files_cache,
+ )
+ .await?;
+
+ match dist_type.as_str() {
+ "zip" => mozart_registry::downloader::extract_zip(&bytes, &temp_dir)?,
+ "tar" | "tar.gz" | "tgz" => {
+ mozart_registry::downloader::extract_tar_gz(&bytes, &temp_dir)?
+ }
+ other => {
+ let _ = std::fs::remove_dir_all(&temp_dir);
+ anyhow::bail!("Unsupported dist type: {}", other);
+ }
+ }
+
+ let extracted_composer = temp_dir.join("composer.json");
+ let (archive_name, archive_excludes) = if extracted_composer.exists() {
+ read_archive_config(&extracted_composer).unwrap_or((None, vec![]))
+ } else {
+ (None, vec![])
+ };
+
+ Ok(AcquiredSource {
+ source_dir: temp_dir.clone(),
+ archive_name,
+ archive_excludes,
+ _temp_dir: Some(temp_dir),
+ })
+ }
+ }
+}
diff --git a/crates/mozart/src/commands/archive.rs b/crates/mozart/src/commands/archive.rs
index 4655edc..82af052 100644
--- a/crates/mozart/src/commands/archive.rs
+++ b/crates/mozart/src/commands/archive.rs
@@ -1,9 +1,10 @@
use clap::Args;
+use mozart_archiver::{ArchiveManager, ArchivePackage};
use mozart_core::composer::Composer;
use mozart_core::console_writeln;
use mozart_core::factory::create_config;
use std::borrow::Cow;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
#[derive(Args)]
pub struct ArchiveArgs {
@@ -30,72 +31,11 @@ pub struct ArchiveArgs {
pub ignore_filters: bool,
}
-/// Read `archive.name` and `archive.exclude` from a composer.json file.
-fn read_archive_config(
- composer_json_path: &std::path::Path,
-) -> anyhow::Result<(Option<String>, Vec<String>)> {
- let content = std::fs::read_to_string(composer_json_path)?;
- let value: serde_json::Value = serde_json::from_str(&content)?;
-
- let name = value
- .get("archive")
- .and_then(|a| a.get("name"))
- .and_then(|n| n.as_str())
- .map(|s| s.to_string());
-
- let excludes = value
- .get("archive")
- .and_then(|a| a.get("exclude"))
- .and_then(|e| e.as_array())
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_str())
- .map(|s| s.to_string())
- .collect()
- })
- .unwrap_or_default();
-
- Ok((name, excludes))
-}
-
-struct PackageMeta {
- source_dir: PathBuf,
- package_name: String,
- archive_name: Option<String>,
- archive_excludes: Vec<String>,
- version: Option<String>,
- dist_reference: Option<String>,
- dist_type: Option<String>,
- source_reference: Option<String>,
- /// Holds an optional temp directory that must outlive `source_dir`.
- _temp_dir: Option<PathBuf>,
-}
-
-impl Drop for PackageMeta {
- fn drop(&mut self) {
- // Clean up temporary directory used for remote packages
- if let Some(ref dir) = self._temp_dir {
- let _ = std::fs::remove_dir_all(dir);
- }
- }
-}
-
pub async fn execute(
args: &ArchiveArgs,
cli: &super::Cli,
- console: &mozart_core::console::Console,
+ io: &mozart_core::console::Console,
) -> anyhow::Result<()> {
- use mozart_archiver::{
- ArchiveFormat, collect_archivable_files, create_archive, generate_archive_filename,
- parse_composer_excludes, parse_gitattributes, parse_gitignore_pattern,
- self_exclusion_patterns,
- };
-
- let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache);
- let repo_cache = mozart_registry::cache::Cache::repo(&cache_config);
- let files_cache = mozart_registry::cache::Cache::files(&cache_config);
-
- // 1. Determine working directory
let working_dir = cli.working_dir()?;
let composer = Composer::try_load(&working_dir)?;
@@ -105,254 +45,179 @@ pub async fn execute(
Cow::Owned(create_config()?)
};
- let format_str = args.format.as_deref().unwrap_or(&config.archive_format);
- let format = ArchiveFormat::parse(format_str).ok_or_else(|| {
- anyhow::anyhow!(
- "Unsupported archive format \"{}\". Supported formats: tar, tar.gz, tar.bz2, zip",
- format_str
- )
- })?;
+ let format = args.format.as_deref().unwrap_or(&config.archive_format);
+ let dir = args.dir.as_deref().unwrap_or(&config.archive_dir);
- let output_dir_str = args.dir.as_deref().unwrap_or(&config.archive_dir);
- let output_dir = if std::path::Path::new(output_dir_str).is_absolute() {
- PathBuf::from(output_dir_str)
- } else {
- working_dir.join(output_dir_str)
- };
- std::fs::create_dir_all(&output_dir)?;
+ archive(
+ io,
+ args.package.as_deref(),
+ args.version.as_deref(),
+ format,
+ dir,
+ args.file.as_deref(),
+ args.ignore_filters,
+ &working_dir,
+ cli.no_cache,
+ )
+ .await
+}
- // 5. Determine source directory and package metadata
- let meta: PackageMeta = if let Some(ref pkg_name) = args.package {
- // Remote package mode
- console.info("Searching for the specified package.");
- resolve_remote_package(
- pkg_name,
- args.version.as_deref(),
- &repo_cache,
- &files_cache,
- console,
- )
- .await?
+#[allow(clippy::too_many_arguments)]
+async fn archive(
+ io: &mozart_core::console::Console,
+ package_name: Option<&str>,
+ version: Option<&str>,
+ format: &str,
+ dest: &str,
+ file_name: Option<&str>,
+ ignore_filters: bool,
+ working_dir: &Path,
+ no_cache: bool,
+) -> anyhow::Result<()> {
+ let cache_config = mozart_registry::cache::build_cache_config(no_cache);
+ let repo_cache = mozart_registry::cache::Cache::repo(&cache_config);
+ let files_cache = mozart_registry::cache::Cache::files(&cache_config);
+
+ let archive_manager = ArchiveManager::new();
+
+ let package = if let Some(package_name) = package_name {
+ select_package(io, package_name, version, &repo_cache).await?
} else {
- let composer_json_path = working_dir.join("composer.json");
- // Root package mode
- if !composer_json_path.exists() {
- anyhow::bail!("No composer.json found in {}", working_dir.display());
- }
- let root = mozart_core::package::read_from_file(&composer_json_path)?;
- let (archive_name, archive_excludes) = read_archive_config(&composer_json_path)?;
- let version = root
- .extra_fields
- .get("version")
- .and_then(|v| v.as_str())
- .map(|s| s.to_string());
- PackageMeta {
- source_dir: working_dir.clone(),
- package_name: root.name.clone(),
- archive_name,
- archive_excludes,
- version,
- dist_reference: None,
- dist_type: None,
- source_reference: None,
- _temp_dir: None,
- }
+ load_root_package(working_dir)?
};
- // 6. Generate output filename
- let filename_base = if let Some(ref f) = args.file {
- f.clone()
+ let dest_dir = if Path::new(dest).is_absolute() {
+ PathBuf::from(dest)
} else {
- generate_archive_filename(
- &meta.package_name,
- meta.archive_name.as_deref(),
- meta.version.as_deref(),
- meta.dist_reference.as_deref(),
- meta.dist_type.as_deref(),
- meta.source_reference.as_deref(),
- )
+ working_dir.join(dest)
};
- // 7. Build exclude patterns
- // Self-exclusion: prevent the archive from including itself
- let has_extra_parts = args.file.is_none()
- && (meta.version.is_some()
- || meta.dist_reference.is_some()
- || meta.source_reference.is_some());
- let self_exclusion_strs = self_exclusion_patterns(&filename_base, has_extra_parts);
-
- let mut all_patterns = Vec::new();
+ io.info(&format!("Creating the archive into \"{}\".", dest));
+ let package_path = archive_manager
+ .archive(
+ &package,
+ format,
+ &dest_dir,
+ file_name,
+ ignore_filters,
+ &files_cache,
+ )
+ .await?;
- // Self-exclusion always applies
- for rule in &self_exclusion_strs {
- if let Some(p) = parse_gitignore_pattern(rule) {
- all_patterns.push(p);
- }
- }
+ let absolute = package_path.display().to_string();
+ let short_path = package_path
+ .strip_prefix(working_dir)
+ .ok()
+ .map(|rel| rel.display().to_string())
+ .filter(|rel| rel.len() < absolute.len())
+ .unwrap_or(absolute);
+ console_writeln!(io, &format!("Created: {}", short_path));
- if !args.ignore_filters {
- // Parse .gitattributes export-ignore rules
- let git_patterns = parse_gitattributes(&meta.source_dir);
- all_patterns.extend(git_patterns);
+ Ok(())
+}
- // Parse composer.json archive.exclude rules
- let composer_patterns = parse_composer_excludes(&meta.archive_excludes);
- all_patterns.extend(composer_patterns);
+fn load_root_package(working_dir: &Path) -> anyhow::Result<ArchivePackage> {
+ let composer_json_path = working_dir.join("composer.json");
+ if !composer_json_path.exists() {
+ anyhow::bail!("No composer.json found in {}", working_dir.display());
}
-
- // 8. Collect files
- let files = collect_archivable_files(&meta.source_dir, &all_patterns)?;
-
- // 9. Create archive
- let target_path = output_dir.join(format!("{}.{}", filename_base, format.extension()));
- console.info(&format!(
- "Creating the archive into \"{}\".",
- output_dir.display()
- ));
- create_archive(&meta.source_dir, &files, &target_path, &format)?;
-
- // Print relative path if possible
- let display_path = if let Ok(rel) = target_path.strip_prefix(&working_dir) {
- rel.display().to_string()
- } else {
- target_path.display().to_string()
- };
- console_writeln!(console, &format!("Created: {}", display_path),);
-
- Ok(())
+ let root = mozart_core::package::read_from_file(&composer_json_path)?;
+ let version = root
+ .extra_fields
+ .get("version")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string());
+ Ok(ArchivePackage::Root {
+ name: root.name.clone(),
+ version,
+ source_dir: working_dir.to_path_buf(),
+ })
}
-async fn resolve_remote_package(
+async fn select_package(
+ io: &mozart_core::console::Console,
package_name: &str,
- version_constraint: Option<&str>,
+ version: Option<&str>,
repo_cache: &mozart_registry::cache::Cache,
- files_cache: &mozart_registry::cache::Cache,
- console: &mozart_core::console::Console,
-) -> anyhow::Result<PackageMeta> {
+) -> anyhow::Result<ArchivePackage> {
use mozart_core::package::Stability;
use mozart_registry::version::find_best_candidate;
- // Parse @stability suffix from version constraint (e.g. "^1.0@beta" → "^1.0", Stability::Beta)
- let (constraint_stripped, stability) = if let Some(raw) = version_constraint {
+ io.info("Searching for the specified package.");
+
+ // Strip @stability suffix from the version constraint (e.g. "^1.0@beta" → "^1.0", Stability::Beta)
+ let (version, min_stability) = if let Some(raw) = version {
if let Some(at_pos) = raw.find('@') {
- let ver_part = raw[..at_pos].trim();
+ let ver_part = raw[..at_pos].trim().to_string();
let stab_part = raw[at_pos + 1..].trim();
- let stab = Stability::parse(stab_part);
- (Some(ver_part.to_string()), stab)
+ (Some(ver_part), Stability::parse(stab_part))
} else {
(Some(raw.to_string()), Stability::Stable)
}
} else {
(None, Stability::Stable)
};
- let version_constraint = constraint_stripped.as_deref();
+ let version = version.as_deref();
- // Fetch versions from Packagist
- let versions =
+ let packages =
mozart_registry::packagist::fetch_package_versions(package_name, repo_cache).await?;
- if versions.is_empty() {
+ if packages.is_empty() {
anyhow::bail!("No versions found for package \"{}\"", package_name);
}
- // Apply version constraint filtering if given
- let candidate = if let Some(constraint) = version_constraint {
- let matches: Vec<_> = versions
+ let package = if let Some(version) = version {
+ let matches: Vec<_> = packages
.iter()
- .filter(|v| v.version == constraint || v.version_normalized.starts_with(constraint))
+ .filter(|v| v.version == version || v.version_normalized.starts_with(version))
.collect();
if matches.is_empty() {
anyhow::bail!(
"Could not find version \"{}\" for package \"{}\"",
- constraint,
+ version,
package_name
);
}
- let best = matches[0];
+ let package = matches[0];
if matches.len() > 1 {
- console.info(&format!(
+ io.info(&format!(
"Found multiple matches, selected {} {}.",
- package_name, best.version
+ package_name, package.version
));
} else {
- console.info(&format!(
+ io.info(&format!(
"Found an exact match {} {}.",
- package_name, best.version
+ package_name, package.version
));
}
- best
+ package
} else {
- let best = find_best_candidate(&versions, stability)
- .or_else(|| find_best_candidate(&versions, Stability::Dev))
+ let package = find_best_candidate(&packages, min_stability)
+ .or_else(|| find_best_candidate(&packages, Stability::Dev))
.ok_or_else(|| {
anyhow::anyhow!("No suitable version found for package \"{}\"", package_name)
})?;
- console.info(&format!(
+ io.info(&format!(
"Found an exact match {} {}.",
- package_name, best.version
+ package_name, package.version
));
- best
+ package
};
- let dist = candidate.dist.as_ref().ok_or_else(|| {
+ let dist = package.dist.as_ref().ok_or_else(|| {
anyhow::anyhow!(
"Package \"{}\" version \"{}\" has no dist available",
package_name,
- candidate.version
+ package.version
)
})?;
- // Create a temp directory using std (not tempfile crate, which is dev-only)
- let temp_base = std::env::temp_dir();
- let unique = format!(
- "mozart-archive-{}",
- std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)
- .map(|d| d.as_nanos())
- .unwrap_or(0)
- );
- let temp_dir = temp_base.join(&unique);
- std::fs::create_dir_all(&temp_dir)?;
-
- let bytes = mozart_registry::downloader::download_dist(
- &dist.url,
- dist.shasum.as_deref(),
- None,
- files_cache,
- )
- .await?;
-
- match dist.dist_type.as_str() {
- "zip" => mozart_registry::downloader::extract_zip(&bytes, &temp_dir)?,
- "tar" | "tar.gz" | "tgz" => mozart_registry::downloader::extract_tar_gz(&bytes, &temp_dir)?,
- other => {
- let _ = std::fs::remove_dir_all(&temp_dir);
- anyhow::bail!("Unsupported dist type: {}", other);
- }
- }
-
- // Try to read composer.json from the extracted source for archive.name / archive.exclude
- let extracted_composer = temp_dir.join("composer.json");
- let (archive_name, archive_excludes) = if extracted_composer.exists() {
- read_archive_config(&extracted_composer).unwrap_or((None, vec![]))
- } else {
- (None, vec![])
- };
-
- let version: Option<String> = Some(candidate.version.clone());
- let dist_reference: Option<String> = dist.reference.clone();
- let dist_type: Option<String> = Some(dist.dist_type.clone());
- let source_reference: Option<String> =
- candidate.source.as_ref().and_then(|s| s.reference.clone());
-
- Ok(PackageMeta {
- source_dir: temp_dir.clone(),
- package_name: package_name.to_string(),
- archive_name,
- archive_excludes,
- version,
- dist_reference,
- dist_type,
- source_reference,
- _temp_dir: Some(temp_dir),
+ Ok(ArchivePackage::Remote {
+ name: package_name.to_string(),
+ version: package.version.clone(),
+ dist_url: dist.url.clone(),
+ dist_type: dist.dist_type.clone(),
+ dist_shasum: dist.shasum.clone(),
+ dist_reference: dist.reference.clone(),
+ source_reference: package.source.as_ref().and_then(|s| s.reference.clone()),
})
}