Skip to main content

kotoba_package_manager/
lib.rs

1//! Kotoba Package Manager
2//!
3//! Deno/npm/cargoライクなパッケージ管理システムを提供します。
4//! 依存関係の解決、パッケージのインストール/アンインストール、
5//! レジストリ管理などの機能を備えています。
6
7use anyhow::Result;
8use kotoba_cid::CidCalculator;
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12pub mod config;
13pub mod dependency;
14pub mod registry;
15pub mod installer;
16pub mod lockfile;
17pub mod cache;
18mod resolver;
19
20pub use dependency::{DependencyInfo, Package, PackageSource, ProjectConfig};
21use lockfile::Lockfile;
22use resolver::Resolver;
23
24/// Package Managerのメイン構造体
25#[derive(Debug)]
26pub struct PackageManager {
27    config: config::Config,
28    registry: registry::Registry,
29    cache: cache::Cache,
30    installer: installer::Installer,
31}
32
33impl PackageManager {
34    /// 新しいPackage Managerを作成
35    pub async fn new() -> Result<Self> {
36        let config = config::Config::load()?;
37        let registry = registry::Registry::new(&config)?;
38        let cache = cache::Cache::new(&config)?;
39        let installer = installer::Installer::new(cache.clone());
40
41        Ok(Self {
42            config,
43            registry,
44            cache,
45            installer,
46        })
47    }
48
49    /// パッケージをインストール
50    pub async fn install(&self) -> Result<()> {
51        let calculator = CidCalculator::default();
52        let lockfile_path = PathBuf::from("kotoba.lock");
53
54        // 1. Load project config
55        let config_path = PathBuf::from("kotoba.toml");
56        if !config_path.exists() {
57            println!("kotoba.toml not found. Nothing to install.");
58            return Ok(());
59        }
60        let toml_content = tokio::fs::read_to_string(&config_path).await?;
61        let project_config: ProjectConfig = toml::from_str(&toml_content)?;
62
63        // 2. Load lockfile and verify cache integrity
64        let mut packages_to_install = project_config.dependencies.clone();
65        let mut locked_packages: HashMap<String, crate::lockfile::LockedPackage> = HashMap::new();
66
67        if let Some(lockfile) = Lockfile::read_from_disk(&lockfile_path).await? {
68            println!("Verifying lockfile...");
69            locked_packages = lockfile.packages;
70
71            for (name, _dep_info) in &project_config.dependencies {
72                if let Some(locked) = locked_packages.get(name) {
73                    let package_dir = PathBuf::from("node_modules").join(name);
74                    if self.cache.get_by_cid(&locked.cid).await?.is_some() && package_dir.exists() {
75                        // This package is cached and installed, no need to re-resolve
76                        packages_to_install.remove(name);
77                    }
78                }
79            }
80        }
81        
82        if packages_to_install.is_empty() {
83            println!("All dependencies are up to date.");
84            // A more robust implementation would still verify node_modules content here.
85            return Ok(());
86        }
87
88        // 3. Resolve and download missing/invalidated packages
89        println!("Resolving and installing {} packages...", packages_to_install.len());
90        let mut resolved_packages = self.resolver().resolve(&packages_to_install).await?;
91        
92        for package in &mut resolved_packages {
93            if let Some(url) = &package.tarball_url {
94                let tarball_bytes = reqwest::get(url).await?.bytes().await?.to_vec();
95                let cid = calculator.compute_cid(&tarball_bytes)?;
96                package.cid = Some(cid.to_string());
97                self.cache.store_by_cid(&cid.to_string(), &tarball_bytes).await?;
98            }
99        }
100
101        // 4. Install resolved packages
102        self.installer.install(resolved_packages.clone()).await?;
103
104        // 5. Update lockfile with all packages
105        let mut final_packages_map: HashMap<String, Package> = HashMap::new();
106
107        // Add packages from the old lockfile that are still relevant
108        for (name, _dep_info) in &project_config.dependencies {
109             if let Some(locked) = locked_packages.get(name) {
110                if packages_to_install.get(name).is_none() { // If it wasn't re-installed
111                    final_packages_map.insert(name.clone(), Package {
112                         name: name.clone(),
113                         version: locked.version.clone(),
114                         source: locked.source.clone(),
115                         cid: Some(locked.cid.clone()),
116                         tarball_url: None, 
117                         description: None,
118                         authors: vec![],
119                         dependencies: HashMap::new(), // This information is lost
120                         dev_dependencies: HashMap::new(),
121                         repository: None,
122                         license: None,
123                         keywords: vec![],
124                    });
125                }
126            }
127        }
128        
129        // Add newly resolved packages
130        for pkg in resolved_packages {
131            final_packages_map.insert(pkg.name.clone(), pkg);
132        }
133
134        let lockfile = Lockfile::from_packages(&final_packages_map.values().cloned().collect::<Vec<_>>());
135        lockfile.write_to_disk(&lockfile_path).await?;
136        
137        println!("Installation completed!");
138        Ok(())
139    }
140
141    /// パッケージをアンインストール
142    pub async fn uninstall(&self, packages: Vec<String>) -> Result<()> {
143        println!("Uninstalling packages: {:?}", packages);
144        // TODO: 実装
145        Ok(())
146    }
147
148    /// 利用可能なパッケージを検索
149    pub async fn search(&self, query: &str) -> Result<Vec<Package>> {
150        println!("Searching for packages: {}", query);
151        self.registry.search(query).await.map_err(Into::into)
152    }
153
154    /// プロジェクトを初期化
155    pub async fn init(&self, name: Option<String>) -> Result<()> {
156        let project_name = name.unwrap_or_else(|| "my-kotoba-project".to_string());
157
158        let config = ProjectConfig {
159            name: project_name.clone(),
160            version: "0.1.0".to_string(),
161            description: Some("A Kotoba project".to_string()),
162            dependencies: HashMap::new(),
163            dev_dependencies: HashMap::new(),
164            scripts: [
165                ("test".to_string(), "kotoba test".to_string()),
166                ("fmt".to_string(), "kotoba fmt".to_string()),
167                ("lint".to_string(), "kotoba lint".to_string()),
168            ].into_iter().collect(),
169        };
170
171        // kotoba.tomlを作成
172        let config_path = PathBuf::from("kotoba.toml");
173        let toml_content = toml::to_string(&config)?;
174        tokio::fs::write(&config_path, toml_content).await?;
175
176        // srcディレクトリを作成
177        tokio::fs::create_dir_all("src").await?;
178
179        // 基本的なmain.kotobaファイルを作成
180        let main_content = format!(r#"// {} - Kotoba Project
181// This is your main entry point
182
183fn main() {{
184    println("Hello, {}!");
185}}
186
187// Export your public API
188pub fn greet(name: String) -> String {{
189    format("Hello, {{}}!", name)
190}}
191"#, project_name, project_name);
192
193        tokio::fs::write("src/main.kotoba", main_content).await?;
194
195        println!("✅ Initialized Kotoba project: {}", project_name);
196        println!("📁 Created kotoba.toml and src/main.kotoba");
197        println!("🚀 Run 'kotoba run src/main.kotoba' to get started!");
198
199        Ok(())
200    }
201
202    /// キャッシュをクリア
203    pub async fn clear_cache(&self) -> Result<()> {
204        self.cache.clear().await?;
205        println!("✅ Cache cleared!");
206        Ok(())
207    }
208
209    /// 依存関係を解決
210    fn resolver(&self) -> resolver::Resolver {
211        resolver::Resolver::new()
212    }
213
214    /// パッケージインストーラー
215    fn installer(&self) -> &installer::Installer {
216        &self.installer
217    }
218}
219
220/// 便利関数
221pub async fn init_project(name: Option<String>) -> Result<()> {
222    let pm = PackageManager::new().await?;
223    pm.init(name).await
224}
225
226pub async fn install_packages() -> Result<()> {
227    let pm = PackageManager::new().await?;
228    pm.install().await
229}
230
231pub async fn search_packages(query: &str) -> Result<Vec<Package>> {
232    let pm = PackageManager::new().await?;
233    pm.search(query).await
234}