changepacks_core/
package.rs

1use std::{collections::HashSet, path::Path};
2
3use crate::{Config, Language, update_type::UpdateType};
4use anyhow::{Context, Result};
5use async_trait::async_trait;
6
7#[async_trait]
8pub trait Package: std::fmt::Debug + Send + Sync {
9    fn name(&self) -> Option<&str>;
10    fn version(&self) -> Option<&str>;
11    fn path(&self) -> &Path;
12    fn relative_path(&self) -> &Path;
13    async fn update_version(&mut self, update_type: UpdateType) -> Result<()>;
14    fn check_changed(&mut self, path: &Path) -> Result<()> {
15        if self.is_changed() {
16            return Ok(());
17        }
18        if !path.to_string_lossy().contains(".changepacks")
19            && path.starts_with(self.path().parent().context("Parent not found")?)
20        {
21            self.set_changed(true);
22        }
23        Ok(())
24    }
25    fn is_changed(&self) -> bool;
26    fn language(&self) -> Language;
27
28    fn dependencies(&self) -> &HashSet<String>;
29    fn add_dependency(&mut self, dependency: &str);
30
31    fn set_changed(&mut self, changed: bool);
32
33    /// Get the default publish command for this package type
34    fn default_publish_command(&self) -> String;
35
36    /// Publish the package using the configured command or default
37    async fn publish(&self, config: &Config) -> Result<()> {
38        let command = self.get_publish_command(config);
39        // Get the directory containing the package file
40        let package_dir = self
41            .path()
42            .parent()
43            .context("Package directory not found")?;
44
45        let mut cmd = if cfg!(target_os = "windows") {
46            let mut c = tokio::process::Command::new("cmd");
47            c.arg("/C");
48            c.arg(command);
49            c
50        } else {
51            let mut c = tokio::process::Command::new("sh");
52            c.arg("-c");
53            c.arg(command);
54            c
55        };
56
57        cmd.current_dir(package_dir);
58        let output = cmd.output().await?;
59
60        if !output.status.success() {
61            anyhow::bail!(
62                "Publish command failed: {}",
63                String::from_utf8_lossy(&output.stderr)
64            );
65        } else {
66            Ok(())
67        }
68    }
69
70    /// Get the publish command for this package, checking config first
71    fn get_publish_command(&self, config: &Config) -> String {
72        // Check for custom command by relative path
73        if let Some(cmd) = config
74            .publish
75            .get(self.relative_path().to_string_lossy().as_ref())
76        {
77            return cmd.clone();
78        }
79
80        // Check for custom command by language
81        let lang_key = match self.language() {
82            crate::Language::Node => "node",
83            crate::Language::Python => "python",
84            crate::Language::Rust => "rust",
85            crate::Language::Dart => "dart",
86        };
87        if let Some(cmd) = config.publish.get(lang_key) {
88            return cmd.clone();
89        }
90
91        // Use default command
92        self.default_publish_command()
93    }
94}