Skip to main content

changepacks_core/
package.rs

1use std::{collections::HashSet, path::Path};
2
3use crate::{Language, update_type::UpdateType};
4use anyhow::Result;
5use async_trait::async_trait;
6
7/// Interface for single versioned packages.
8///
9/// Implemented by language-specific package types for reading versions, updating files,
10/// detecting changes, and publishing. All I/O operations are async.
11#[async_trait]
12pub trait Package: std::fmt::Debug + Send + Sync {
13    fn name(&self) -> Option<&str>;
14    fn version(&self) -> Option<&str>;
15    fn path(&self) -> &Path;
16    fn relative_path(&self) -> &Path;
17    /// # Errors
18    /// Returns error if the version update operation fails.
19    async fn update_version(&mut self, update_type: UpdateType) -> Result<()>;
20    fn is_changed(&self) -> bool;
21    fn language(&self) -> Language;
22
23    fn dependencies(&self) -> &HashSet<String>;
24    fn add_dependency(&mut self, dependency: &str);
25
26    fn set_changed(&mut self, changed: bool);
27
28    /// Set the package name (used for fallback when name is not found in manifest).
29    /// Implementors typically get this via `impl_basic_accessors!()`.
30    fn set_name(&mut self, name: String);
31
32    /// Get the default publish command for this package type
33    fn default_publish_command(&self) -> String;
34
35    /// Get the default dry-run publish command for this package type.
36    ///
37    /// Returns `None` for ecosystems whose default publish tool does not
38    /// support a built-in dry-run mode (e.g. `dotnet nuget push`). Callers
39    /// should treat `None` as "dry-run not supported; skip with a warning"
40    /// rather than as a failure. Users may still provide an override via
41    /// `config.publish_dry_run`.
42    fn default_dry_run_publish_command(&self) -> Option<String>;
43
44    crate::impl_shared_project_defaults!();
45
46    /// Whether this package inherits its version from the workspace root via `version.workspace = true`
47    fn inherits_workspace_version(&self) -> bool {
48        false
49    }
50
51    /// Path to the workspace root Cargo.toml, if this package inherits its version from workspace
52    fn workspace_root_path(&self) -> Option<&Path> {
53        None
54    }
55
56    crate::impl_publish_flows!(crate::publish::PACKAGE_DIR_NOT_FOUND);
57
58    crate::impl_publish_command_resolvers!();
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::test_support::MockPackage;
65
66    // The eighteen tests pinning the shared trait defaults and the shared
67    // `UnsupportedDryRunProject` fixture are generated from one surface shared
68    // with `workspace.rs`; only the `Package`-only defaults below stay
69    // hand-written here.
70    crate::test_support::shared_project_default_tests!(
71        mock: MockPackage,
72        trait_name: Package,
73        kind: "package",
74        dir_not_found: "Package directory not found",
75        publishable_test: test_package_is_publishable_by_default,
76    );
77
78    #[test]
79    fn test_inherits_workspace_version_default() {
80        let package =
81            MockPackage::with_paths(Some("test"), "/project/package.json", "package.json");
82        assert!(!package.inherits_workspace_version());
83    }
84
85    #[test]
86    fn test_workspace_root_path_default() {
87        let package =
88            MockPackage::with_paths(Some("test"), "/project/package.json", "package.json");
89        assert!(package.workspace_root_path().is_none());
90    }
91}