sampo_core/
adapters.rs

1/// Ecosystem-specific adapters (Cargo, npm, etc.) for all package operations.
2pub mod cargo;
3pub mod hex;
4pub mod npm;
5
6pub use cargo::ManifestMetadata;
7
8use crate::errors::{Result, WorkspaceError};
9use crate::types::PackageInfo;
10use std::collections::BTreeMap;
11use std::path::Path;
12
13/// Package ecosystem adapter (Cargo, npm, etc.).
14#[derive(Debug, Clone, Copy)]
15pub enum PackageAdapter {
16    Cargo,
17    Npm,
18    Hex,
19}
20
21impl PackageAdapter {
22    /// All registered adapters, checked in order during workspace discovery.
23    /// TODO: it's fine for now, but eventually we could using strum or enum-iterators here.
24    pub fn all() -> &'static [PackageAdapter] {
25        &[
26            PackageAdapter::Cargo,
27            PackageAdapter::Npm,
28            PackageAdapter::Hex,
29        ]
30    }
31
32    /// Check if this adapter can handle the given directory.
33    pub fn can_discover(&self, root: &Path) -> bool {
34        match self {
35            Self::Cargo => cargo::CargoAdapter.can_discover(root),
36            Self::Npm => npm::NpmAdapter.can_discover(root),
37            Self::Hex => hex::HexAdapter.can_discover(root),
38        }
39    }
40
41    /// Discover all packages in the workspace.
42    pub fn discover(&self, root: &Path) -> std::result::Result<Vec<PackageInfo>, WorkspaceError> {
43        match self {
44            Self::Cargo => cargo::CargoAdapter.discover(root),
45            Self::Npm => npm::NpmAdapter.discover(root),
46            Self::Hex => hex::HexAdapter.discover(root),
47        }
48    }
49
50    /// Get the path to the manifest file for a package directory.
51    pub fn manifest_path(&self, package_dir: &Path) -> std::path::PathBuf {
52        match self {
53            Self::Cargo => cargo::CargoAdapter.manifest_path(package_dir),
54            Self::Npm => npm::NpmAdapter.manifest_path(package_dir),
55            Self::Hex => hex::HexAdapter.manifest_path(package_dir),
56        }
57    }
58
59    /// Check if a package is publishable to its primary registry.
60    pub fn is_publishable(&self, manifest_path: &Path) -> Result<bool> {
61        match self {
62            Self::Cargo => cargo::CargoAdapter.is_publishable(manifest_path),
63            Self::Npm => npm::NpmAdapter.is_publishable(manifest_path),
64            Self::Hex => hex::HexAdapter.is_publishable(manifest_path),
65        }
66    }
67
68    /// Check if a specific version already exists on the registry.
69    pub fn version_exists(
70        &self,
71        package_name: &str,
72        version: &str,
73        manifest_path: Option<&Path>,
74    ) -> Result<bool> {
75        match self {
76            Self::Cargo => cargo::CargoAdapter.version_exists(package_name, version),
77            Self::Npm => npm::NpmAdapter.version_exists(package_name, version, manifest_path),
78            Self::Hex => hex::HexAdapter.version_exists(package_name, version, manifest_path),
79        }
80    }
81
82    /// Execute the publish command for a package.
83    pub fn publish(
84        &self,
85        manifest_path: &Path,
86        dry_run: bool,
87        extra_args: &[String],
88    ) -> Result<()> {
89        match self {
90            Self::Cargo => cargo::CargoAdapter.publish(manifest_path, dry_run, extra_args),
91            Self::Npm => npm::NpmAdapter.publish(manifest_path, dry_run, extra_args),
92            Self::Hex => hex::HexAdapter.publish(manifest_path, dry_run, extra_args),
93        }
94    }
95
96    /// Regenerate the workspace lockfile after version updates.
97    pub fn regenerate_lockfile(&self, workspace_root: &Path) -> Result<()> {
98        match self {
99            Self::Cargo => cargo::CargoAdapter.regenerate_lockfile(workspace_root),
100            Self::Npm => npm::NpmAdapter.regenerate_lockfile(workspace_root),
101            Self::Hex => hex::HexAdapter.regenerate_lockfile(workspace_root),
102        }
103    }
104
105    /// Update the manifest and dependency versions for a package.
106    pub fn update_manifest_versions(
107        &self,
108        manifest_path: &Path,
109        input: &str,
110        new_pkg_version: Option<&str>,
111        new_version_by_name: &BTreeMap<String, String>,
112        metadata: Option<&ManifestMetadata>,
113    ) -> Result<(String, Vec<(String, String)>)> {
114        match self {
115            Self::Cargo => cargo::update_manifest_versions(
116                manifest_path,
117                input,
118                new_pkg_version,
119                new_version_by_name,
120                metadata,
121            ),
122            Self::Npm => {
123                debug_assert!(
124                    metadata.is_none(),
125                    "npm adapter does not use Cargo metadata"
126                );
127                npm::update_manifest_versions(
128                    manifest_path,
129                    input,
130                    new_pkg_version,
131                    new_version_by_name,
132                )
133            }
134            Self::Hex => {
135                debug_assert!(
136                    metadata.is_none(),
137                    "hex adapter does not use Cargo metadata"
138                );
139                hex::update_manifest_versions(
140                    manifest_path,
141                    input,
142                    new_pkg_version,
143                    new_version_by_name,
144                )
145            }
146        }
147    }
148}