diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-05 19:38:29 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-05 19:38:29 +0900 |
| commit | 884d9ab32bbca7a8ec5c7ee7d42cbde0e7e6babf (patch) | |
| tree | e04e25f506f0174572b4634601ab9b317220d9e5 | |
| parent | 49b0884701a84731652fc934d428932ff6029bd4 (diff) | |
| download | php-mozart-884d9ab32bbca7a8ec5c7ee7d42cbde0e7e6babf.tar.gz php-mozart-884d9ab32bbca7a8ec5c7ee7d42cbde0e7e6babf.tar.zst php-mozart-884d9ab32bbca7a8ec5c7ee7d42cbde0e7e6babf.zip | |
refactor(core): replace ComposerConfig with typed Config struct
Config uses serde with kebab-case field mapping; known properties are
strongly-typed fields and unknown keys flow into an extra BTreeMap.
resolve_references is moved to the new config module.
| -rw-r--r-- | crates/mozart-core/src/composer.rs | 134 | ||||
| -rw-r--r-- | crates/mozart-core/src/config.rs | 329 | ||||
| -rw-r--r-- | crates/mozart-core/src/lib.rs | 1 | ||||
| -rw-r--r-- | crates/mozart/src/commands/archive.rs | 13 | ||||
| -rw-r--r-- | crates/mozart/src/commands/config.rs | 139 | ||||
| -rw-r--r-- | crates/mozart/src/commands/dump_autoload.rs | 20 | ||||
| -rw-r--r-- | crates/mozart/src/commands/exec.rs | 7 | ||||
| -rw-r--r-- | crates/mozart/src/commands/run_script.rs | 21 |
8 files changed, 408 insertions, 256 deletions
diff --git a/crates/mozart-core/src/composer.rs b/crates/mozart-core/src/composer.rs index 84d6606..76427a7 100644 --- a/crates/mozart-core/src/composer.rs +++ b/crates/mozart-core/src/composer.rs @@ -11,6 +11,8 @@ use std::collections::BTreeMap; use std::path::{Path, PathBuf}; +use crate::config::{Config, resolve_references}; + /// Return the Composer home directory, respecting `COMPOSER_HOME` and falling /// back to the platform default using Composer-compatible logic. /// @@ -69,134 +71,12 @@ fn use_xdg() -> bool { || std::path::Path::new("/etc/xdg").is_dir() } -/// Effective Composer config key/value pairs for a project. -/// Keys mirror `Composer\Config`'s defaults; values are stored as raw -/// `serde_json::Value` so callers can re-interpret them per key. -pub struct ComposerConfig { - pub values: BTreeMap<String, serde_json::Value>, -} - -impl ComposerConfig { - /// Build a `ComposerConfig` populated with Composer's built-in defaults. - pub fn defaults() -> Self { - let mut m: BTreeMap<String, serde_json::Value> = BTreeMap::new(); - - m.insert("process-timeout".to_string(), serde_json::json!(300)); - m.insert("use-include-path".to_string(), serde_json::json!(false)); - m.insert("preferred-install".to_string(), serde_json::json!("dist")); - m.insert("notify-on-install".to_string(), serde_json::json!(true)); - m.insert( - "github-protocols".to_string(), - serde_json::json!(["https", "ssh", "git"]), - ); - m.insert("vendor-dir".to_string(), serde_json::json!("vendor")); - m.insert( - "bin-dir".to_string(), - serde_json::json!("{$vendor-dir}/bin"), - ); - m.insert("bin-compat".to_string(), serde_json::json!("auto")); - m.insert("cache-dir".to_string(), serde_json::json!("{$home}/cache")); - m.insert( - "cache-files-dir".to_string(), - serde_json::json!("{$cache-dir}/files"), - ); - m.insert( - "cache-repo-dir".to_string(), - serde_json::json!("{$cache-dir}/repo"), - ); - m.insert( - "cache-vcs-dir".to_string(), - serde_json::json!("{$cache-dir}/vcs"), - ); - m.insert("cache-files-ttl".to_string(), serde_json::json!(15_552_000)); - m.insert( - "cache-files-maxsize".to_string(), - serde_json::json!("300MiB"), - ); - m.insert("cache-read-only".to_string(), serde_json::json!(false)); - m.insert("prepend-autoloader".to_string(), serde_json::json!(true)); - m.insert("autoloader-suffix".to_string(), serde_json::Value::Null); - m.insert("optimize-autoloader".to_string(), serde_json::json!(false)); - m.insert("sort-packages".to_string(), serde_json::json!(false)); - m.insert( - "classmap-authoritative".to_string(), - serde_json::json!(false), - ); - m.insert("apcu-autoloader".to_string(), serde_json::json!(false)); - m.insert("platform".to_string(), serde_json::json!({})); - m.insert("platform-check".to_string(), serde_json::json!("php-only")); - m.insert("lock".to_string(), serde_json::json!(true)); - m.insert("discard-changes".to_string(), serde_json::json!(false)); - m.insert("archive-format".to_string(), serde_json::json!("tar")); - m.insert("archive-dir".to_string(), serde_json::json!(".")); - m.insert("htaccess-protect".to_string(), serde_json::json!(true)); - m.insert("secure-http".to_string(), serde_json::json!(true)); - m.insert("allow-plugins".to_string(), serde_json::json!({})); - - Self { values: m } - } - - /// Merge `overrides` on top of the current values. - pub fn merge(&mut self, overrides: &BTreeMap<String, serde_json::Value>) { - for (k, v) in overrides { - self.values.insert(k.clone(), v.clone()); - } - } - - /// Return the effective value for a single key, or `None` if absent. - pub fn get(&self, key: &str) -> Option<&serde_json::Value> { - self.values.get(key) - } -} - -/// Resolve `{$vendor-dir}`, `{$home}`, `{$cache-dir}` placeholders inside -/// string values. Only one pass is performed (no recursive expansion). -pub fn resolve_references(config: &mut ComposerConfig) { - // Snapshot the values we need for substitution before mutating. - let vendor_dir = config - .values - .get("vendor-dir") - .and_then(|v| v.as_str()) - .unwrap_or("vendor") - .to_string(); - - let home = composer_home().to_string_lossy().into_owned(); - - let cache_dir = config - .values - .get("cache-dir") - .and_then(|v| v.as_str()) - .unwrap_or("{$home}/cache") - .replace("{$home}", &home); - - let replacements: &[(&str, &str)] = &[ - ("{$vendor-dir}", &vendor_dir), - ("{$home}", &home), - ("{$cache-dir}", &cache_dir), - ]; - - let keys: Vec<String> = config.values.keys().cloned().collect(); - for key in keys { - if let Some(serde_json::Value::String(s)) = config.values.get(&key).cloned() { - let mut resolved = s.clone(); - for (placeholder, replacement) in replacements { - resolved = resolved.replace(placeholder, replacement); - } - if resolved != s { - config - .values - .insert(key, serde_json::Value::String(resolved)); - } - } - } -} - /// Project-level Composer state. Currently only carries the merged -/// `ComposerConfig`; additional accessors (root package, locker, …) can be +/// [`Config`]; additional accessors (root package, locker, …) can be /// layered on as commands need them. pub struct Composer { project_dir: PathBuf, - config: ComposerConfig, + config: Config, } impl Composer { @@ -229,13 +109,13 @@ impl Composer { fn load(project_dir: PathBuf, composer_json: &Path) -> anyhow::Result<Self> { let content = std::fs::read_to_string(composer_json)?; let value: serde_json::Value = serde_json::from_str(&content)?; - let mut config = ComposerConfig::defaults(); + let mut config = Config::default(); if let Some(cfg_obj) = value.get("config").and_then(|v| v.as_object()) { let overrides: BTreeMap<String, serde_json::Value> = cfg_obj .iter() .map(|(k, v)| (k.clone(), v.clone())) .collect(); - config.merge(&overrides); + config.merge(&overrides)?; } resolve_references(&mut config); Ok(Self { @@ -248,7 +128,7 @@ impl Composer { &self.project_dir } - pub fn config(&self) -> &ComposerConfig { + pub fn config(&self) -> &Config { &self.config } } diff --git a/crates/mozart-core/src/config.rs b/crates/mozart-core/src/config.rs new file mode 100644 index 0000000..dd8a9de --- /dev/null +++ b/crates/mozart-core/src/config.rs @@ -0,0 +1,329 @@ +//! Typed Composer configuration. +//! +//! Mirrors `Composer\Config` from the PHP implementation: holds the merged +//! effective configuration for a project with strongly-typed fields for all +//! known properties. Unknown properties are captured in the `extra` map so +//! that round-tripping through serde is lossless. + +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +use crate::composer::composer_home; + +/// Effective Composer configuration for a project. +/// +/// Known properties are typed fields; anything else lands in `extra`. +/// `Default::default()` yields Composer's built-in defaults. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", default)] +pub struct Config { + pub process_timeout: u64, + pub use_include_path: bool, + /// Either a single mode string (e.g. `"dist"`) or a per-package map. + pub preferred_install: serde_json::Value, + pub notify_on_install: bool, + pub github_protocols: Vec<String>, + pub vendor_dir: String, + pub bin_dir: String, + pub bin_compat: String, + pub cache_dir: String, + pub cache_files_dir: String, + pub cache_repo_dir: String, + pub cache_vcs_dir: String, + pub cache_files_ttl: u64, + pub cache_files_maxsize: String, + pub cache_read_only: bool, + pub prepend_autoloader: bool, + pub autoloader_suffix: Option<String>, + pub optimize_autoloader: bool, + pub sort_packages: bool, + pub classmap_authoritative: bool, + pub apcu_autoloader: bool, + /// Per-platform package version overrides. + pub platform: BTreeMap<String, serde_json::Value>, + /// `true`, `false`, or `"php-only"`. + pub platform_check: serde_json::Value, + pub lock: bool, + /// `true`, `false`, or `"stash"`. + pub discard_changes: serde_json::Value, + pub archive_format: String, + pub archive_dir: String, + pub htaccess_protect: bool, + pub secure_http: bool, + /// `false` (disable all) or a `{plugin: bool}` map. + pub allow_plugins: serde_json::Value, + + /// Catch-all for properties not explicitly listed above. + #[serde(flatten)] + pub extra: BTreeMap<String, serde_json::Value>, +} + +impl Default for Config { + fn default() -> Self { + Config { + process_timeout: 300, + use_include_path: false, + preferred_install: serde_json::json!("dist"), + notify_on_install: true, + github_protocols: vec!["https".to_string(), "ssh".to_string(), "git".to_string()], + vendor_dir: "vendor".to_string(), + bin_dir: "{$vendor-dir}/bin".to_string(), + bin_compat: "auto".to_string(), + cache_dir: "{$home}/cache".to_string(), + cache_files_dir: "{$cache-dir}/files".to_string(), + cache_repo_dir: "{$cache-dir}/repo".to_string(), + cache_vcs_dir: "{$cache-dir}/vcs".to_string(), + cache_files_ttl: 15_552_000, + cache_files_maxsize: "300MiB".to_string(), + cache_read_only: false, + prepend_autoloader: true, + autoloader_suffix: None, + optimize_autoloader: false, + sort_packages: false, + classmap_authoritative: false, + apcu_autoloader: false, + platform: BTreeMap::new(), + platform_check: serde_json::json!("php-only"), + lock: true, + discard_changes: serde_json::json!(false), + archive_format: "tar".to_string(), + archive_dir: ".".to_string(), + htaccess_protect: true, + secure_http: true, + allow_plugins: serde_json::json!({}), + extra: BTreeMap::new(), + } + } +} + +impl Config { + /// Merge `overrides` on top of the current values. + /// + /// Serialises the current config to a JSON object, applies `overrides`, + /// then deserialises back. Known fields are validated by serde; unknown + /// keys flow into `extra`. + pub fn merge(&mut self, overrides: &BTreeMap<String, serde_json::Value>) -> anyhow::Result<()> { + if overrides.is_empty() { + return Ok(()); + } + let mut map = match serde_json::to_value(&*self)? { + serde_json::Value::Object(m) => m, + _ => unreachable!(), + }; + for (k, v) in overrides { + map.insert(k.clone(), v.clone()); + } + *self = serde_json::from_value(serde_json::Value::Object(map))?; + Ok(()) + } + + /// Return the effective value for a single key, or `None` if absent. + pub fn get(&self, key: &str) -> Option<serde_json::Value> { + match key { + "process-timeout" => Some(serde_json::json!(self.process_timeout)), + "use-include-path" => Some(serde_json::json!(self.use_include_path)), + "preferred-install" => Some(self.preferred_install.clone()), + "notify-on-install" => Some(serde_json::json!(self.notify_on_install)), + "github-protocols" => Some(serde_json::json!(self.github_protocols)), + "vendor-dir" => Some(serde_json::json!(self.vendor_dir)), + "bin-dir" => Some(serde_json::json!(self.bin_dir)), + "bin-compat" => Some(serde_json::json!(self.bin_compat)), + "cache-dir" => Some(serde_json::json!(self.cache_dir)), + "cache-files-dir" => Some(serde_json::json!(self.cache_files_dir)), + "cache-repo-dir" => Some(serde_json::json!(self.cache_repo_dir)), + "cache-vcs-dir" => Some(serde_json::json!(self.cache_vcs_dir)), + "cache-files-ttl" => Some(serde_json::json!(self.cache_files_ttl)), + "cache-files-maxsize" => Some(serde_json::json!(self.cache_files_maxsize)), + "cache-read-only" => Some(serde_json::json!(self.cache_read_only)), + "prepend-autoloader" => Some(serde_json::json!(self.prepend_autoloader)), + "autoloader-suffix" => Some(match &self.autoloader_suffix { + Some(s) => serde_json::json!(s), + None => serde_json::Value::Null, + }), + "optimize-autoloader" => Some(serde_json::json!(self.optimize_autoloader)), + "sort-packages" => Some(serde_json::json!(self.sort_packages)), + "classmap-authoritative" => Some(serde_json::json!(self.classmap_authoritative)), + "apcu-autoloader" => Some(serde_json::json!(self.apcu_autoloader)), + "platform" => Some(serde_json::json!(self.platform)), + "platform-check" => Some(self.platform_check.clone()), + "lock" => Some(serde_json::json!(self.lock)), + "discard-changes" => Some(self.discard_changes.clone()), + "archive-format" => Some(serde_json::json!(self.archive_format)), + "archive-dir" => Some(serde_json::json!(self.archive_dir)), + "htaccess-protect" => Some(serde_json::json!(self.htaccess_protect)), + "secure-http" => Some(serde_json::json!(self.secure_http)), + "allow-plugins" => Some(self.allow_plugins.clone()), + _ => self.extra.get(key).cloned(), + } + } + + /// Return all config entries as sorted (key, value) pairs. + pub fn entries(&self) -> Vec<(String, serde_json::Value)> { + let mut map: BTreeMap<String, serde_json::Value> = BTreeMap::new(); + map.insert("allow-plugins".to_string(), self.allow_plugins.clone()); + map.insert( + "apcu-autoloader".to_string(), + serde_json::json!(self.apcu_autoloader), + ); + map.insert( + "archive-dir".to_string(), + serde_json::json!(self.archive_dir), + ); + map.insert( + "archive-format".to_string(), + serde_json::json!(self.archive_format), + ); + map.insert( + "autoloader-suffix".to_string(), + match &self.autoloader_suffix { + Some(s) => serde_json::json!(s), + None => serde_json::Value::Null, + }, + ); + map.insert("bin-compat".to_string(), serde_json::json!(self.bin_compat)); + map.insert("bin-dir".to_string(), serde_json::json!(self.bin_dir)); + map.insert("cache-dir".to_string(), serde_json::json!(self.cache_dir)); + map.insert( + "cache-files-dir".to_string(), + serde_json::json!(self.cache_files_dir), + ); + map.insert( + "cache-files-maxsize".to_string(), + serde_json::json!(self.cache_files_maxsize), + ); + map.insert( + "cache-files-ttl".to_string(), + serde_json::json!(self.cache_files_ttl), + ); + map.insert( + "cache-read-only".to_string(), + serde_json::json!(self.cache_read_only), + ); + map.insert( + "cache-repo-dir".to_string(), + serde_json::json!(self.cache_repo_dir), + ); + map.insert( + "cache-vcs-dir".to_string(), + serde_json::json!(self.cache_vcs_dir), + ); + map.insert( + "classmap-authoritative".to_string(), + serde_json::json!(self.classmap_authoritative), + ); + map.insert("discard-changes".to_string(), self.discard_changes.clone()); + map.insert( + "github-protocols".to_string(), + serde_json::json!(self.github_protocols), + ); + map.insert( + "htaccess-protect".to_string(), + serde_json::json!(self.htaccess_protect), + ); + map.insert("lock".to_string(), serde_json::json!(self.lock)); + map.insert( + "notify-on-install".to_string(), + serde_json::json!(self.notify_on_install), + ); + map.insert( + "optimize-autoloader".to_string(), + serde_json::json!(self.optimize_autoloader), + ); + map.insert("platform".to_string(), serde_json::json!(self.platform)); + map.insert("platform-check".to_string(), self.platform_check.clone()); + map.insert( + "prepend-autoloader".to_string(), + serde_json::json!(self.prepend_autoloader), + ); + map.insert( + "preferred-install".to_string(), + self.preferred_install.clone(), + ); + map.insert( + "process-timeout".to_string(), + serde_json::json!(self.process_timeout), + ); + map.insert( + "secure-http".to_string(), + serde_json::json!(self.secure_http), + ); + map.insert( + "sort-packages".to_string(), + serde_json::json!(self.sort_packages), + ); + map.insert( + "use-include-path".to_string(), + serde_json::json!(self.use_include_path), + ); + map.insert("vendor-dir".to_string(), serde_json::json!(self.vendor_dir)); + for (k, v) in &self.extra { + map.insert(k.clone(), v.clone()); + } + map.into_iter().collect() + } + + /// Resolve relative *-dir fields to absolute paths by joining with `base`. + pub fn make_dirs_absolute(&mut self, base: &std::path::Path) { + fn resolve(base: &std::path::Path, s: &mut String) { + let p = std::path::Path::new(s.as_str()); + if p.is_relative() { + *s = base.join(p).to_string_lossy().into_owned(); + } + } + resolve(base, &mut self.vendor_dir); + resolve(base, &mut self.bin_dir); + resolve(base, &mut self.cache_dir); + resolve(base, &mut self.cache_files_dir); + resolve(base, &mut self.cache_repo_dir); + resolve(base, &mut self.cache_vcs_dir); + resolve(base, &mut self.archive_dir); + for (key, val) in &mut self.extra { + if key.ends_with("-dir") + && let serde_json::Value::String(s) = val + { + resolve(base, s); + } + } + } +} + +fn substitute(s: &str, vendor_dir: &str, home: &str, cache_dir: &str) -> String { + s.replace("{$vendor-dir}", vendor_dir) + .replace("{$home}", home) + .replace("{$cache-dir}", cache_dir) +} + +/// Resolve `{$vendor-dir}`, `{$home}`, and `{$cache-dir}` placeholders in +/// string-valued fields. Only one pass is performed (no recursive expansion). +pub fn resolve_references(config: &mut Config) { + let vendor_dir = config.vendor_dir.clone(); + let home = composer_home().to_string_lossy().into_owned(); + let cache_dir = substitute(&config.cache_dir, &vendor_dir, &home, ""); + + let resolved_bin_dir = substitute(&config.bin_dir, &vendor_dir, &home, &cache_dir); + config.bin_dir = resolved_bin_dir; + + let resolved_cache_dir = substitute(&config.cache_dir, &vendor_dir, &home, &cache_dir); + config.cache_dir = resolved_cache_dir; + + let resolved_cache_files = substitute(&config.cache_files_dir, &vendor_dir, &home, &cache_dir); + config.cache_files_dir = resolved_cache_files; + + let resolved_cache_repo = substitute(&config.cache_repo_dir, &vendor_dir, &home, &cache_dir); + config.cache_repo_dir = resolved_cache_repo; + + let resolved_cache_vcs = substitute(&config.cache_vcs_dir, &vendor_dir, &home, &cache_dir); + config.cache_vcs_dir = resolved_cache_vcs; + + let resolved_archive_dir = substitute(&config.archive_dir, &vendor_dir, &home, &cache_dir); + config.archive_dir = resolved_archive_dir; + + for val in config.extra.values_mut() { + if let serde_json::Value::String(s) = val { + let resolved = substitute(s, &vendor_dir, &home, &cache_dir); + if resolved != *s { + *s = resolved; + } + } + } +} diff --git a/crates/mozart-core/src/lib.rs b/crates/mozart-core/src/lib.rs index 5e51d63..1264292 100644 --- a/crates/mozart-core/src/lib.rs +++ b/crates/mozart-core/src/lib.rs @@ -1,4 +1,5 @@ pub mod composer; +pub mod config; pub mod console; pub mod exit_code; pub mod http; diff --git a/crates/mozart/src/commands/archive.rs b/crates/mozart/src/commands/archive.rs index a075ade..4386e16 100644 --- a/crates/mozart/src/commands/archive.rs +++ b/crates/mozart/src/commands/archive.rs @@ -104,15 +104,10 @@ pub async fn execute( let (config_archive_format, config_archive_dir) = match composer.as_ref() { Some(c) => { let cfg = c.config(); - let fmt = cfg - .get("archive-format") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()); - let dir = cfg - .get("archive-dir") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()); - (fmt, dir) + ( + Some(cfg.archive_format.clone()), + Some(cfg.archive_dir.clone()), + ) } None => (None, None), }; diff --git a/crates/mozart/src/commands/config.rs b/crates/mozart/src/commands/config.rs index 71a5981..40336f3 100644 --- a/crates/mozart/src/commands/config.rs +++ b/crates/mozart/src/commands/config.rs @@ -60,7 +60,7 @@ pub struct ConfigArgs { pub source: bool, } -pub use mozart_core::composer::{ComposerConfig, resolve_references}; +use mozart_core::config::{Config, resolve_references}; /// Classification of config key value types for validation and normalization. #[derive(Debug)] @@ -754,17 +754,17 @@ fn execute_read( console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Build the effective config for config-section keys. - let mut config = ComposerConfig::defaults(); + let mut config = Config::default(); if args.global { let global_config_path = composer_home().join("config.json"); let overrides = load_config_section(&global_config_path)?; - config.merge(&overrides); + config.merge(&overrides)?; } else { let wd = cli.working_dir()?; let composer_json = wd.join("composer.json"); let overrides = load_config_section(&composer_json)?; - config.merge(&overrides); + config.merge(&overrides)?; } resolve_references(&mut config); @@ -772,27 +772,13 @@ fn execute_read( // If --absolute is requested, resolve *-dir values to absolute paths. if args.absolute { let wd = cli.working_dir()?; - let keys: Vec<String> = config.values.keys().cloned().collect(); - for key in keys { - if key.ends_with("-dir") - && let Some(serde_json::Value::String(s)) = config.values.get(&key).cloned() - { - let p = std::path::Path::new(&s); - if p.is_relative() { - let abs = wd.join(p); - config.values.insert( - key, - serde_json::Value::String(abs.to_string_lossy().into_owned()), - ); - } - } - } + config.make_dirs_absolute(&wd); } if args.list { - for (key, value) in &config.values { + for (key, value) in config.entries() { console.write_stdout( - &format!("[{}] {}", key, render_value(value)), + &format!("[{}] {}", key, render_value(&value)), mozart_core::console::Verbosity::Quiet, ); } @@ -853,8 +839,10 @@ fn execute_read( // 4. Standard config key lookup match config.get(key) { Some(value) => { - console - .write_stdout(&render_value(value), mozart_core::console::Verbosity::Quiet); + console.write_stdout( + &render_value(&value), + mozart_core::console::Verbosity::Quiet, + ); } None => { return Err(anyhow!("Setting \"{}\" does not exist.", key)); @@ -872,7 +860,7 @@ mod tests { #[test] fn test_defaults_contain_expected_keys() { - let cfg = ComposerConfig::defaults(); + let cfg = Config::default(); let required_keys = [ "process-timeout", @@ -908,127 +896,109 @@ mod tests { ]; for key in &required_keys { - assert!(cfg.values.contains_key(*key), "defaults missing key: {key}"); + assert!(cfg.get(*key).is_some(), "defaults missing key: {key}"); } } #[test] fn test_defaults_values_correct() { - let cfg = ComposerConfig::defaults(); + let cfg = Config::default(); - assert_eq!(cfg.values["process-timeout"], serde_json::json!(300)); - assert_eq!(cfg.values["preferred-install"], serde_json::json!("dist")); - assert_eq!(cfg.values["vendor-dir"], serde_json::json!("vendor")); - assert_eq!( - cfg.values["github-protocols"], - serde_json::json!(["https", "ssh", "git"]) - ); - assert_eq!(cfg.values["secure-http"], serde_json::json!(true)); - assert_eq!(cfg.values["lock"], serde_json::json!(true)); - assert_eq!(cfg.values["autoloader-suffix"], serde_json::Value::Null); + assert_eq!(cfg.process_timeout, 300); + assert_eq!(cfg.preferred_install, serde_json::json!("dist")); + assert_eq!(cfg.vendor_dir, "vendor"); + assert_eq!(cfg.github_protocols, vec!["https", "ssh", "git"]); + assert_eq!(cfg.secure_http, true); + assert_eq!(cfg.lock, true); + assert_eq!(cfg.autoloader_suffix, None); } #[test] fn test_merge_overrides_existing_key() { - let mut cfg = ComposerConfig::defaults(); + let mut cfg = Config::default(); let mut overrides = BTreeMap::new(); overrides.insert("vendor-dir".to_string(), serde_json::json!("packages")); overrides.insert("sort-packages".to_string(), serde_json::json!(true)); - cfg.merge(&overrides); + cfg.merge(&overrides).unwrap(); - assert_eq!(cfg.values["vendor-dir"], serde_json::json!("packages")); - assert_eq!(cfg.values["sort-packages"], serde_json::json!(true)); + assert_eq!(cfg.vendor_dir, "packages"); + assert_eq!(cfg.sort_packages, true); } #[test] fn test_merge_adds_new_key() { - let mut cfg = ComposerConfig::defaults(); + let mut cfg = Config::default(); let mut overrides = BTreeMap::new(); overrides.insert("custom-key".to_string(), serde_json::json!("custom-value")); - cfg.merge(&overrides); + cfg.merge(&overrides).unwrap(); - assert_eq!(cfg.values["custom-key"], serde_json::json!("custom-value")); + assert_eq!(cfg.extra["custom-key"], serde_json::json!("custom-value")); } #[test] fn test_merge_empty_overrides_leaves_defaults_intact() { - let mut cfg = ComposerConfig::defaults(); - let original_vendor = cfg.values["vendor-dir"].clone(); + let mut cfg = Config::default(); + let original_vendor = cfg.vendor_dir.clone(); - cfg.merge(&BTreeMap::new()); + cfg.merge(&BTreeMap::new()).unwrap(); - assert_eq!(cfg.values["vendor-dir"], original_vendor); + assert_eq!(cfg.vendor_dir, original_vendor); } #[test] fn test_reference_resolution_bin_dir() { - let mut cfg = ComposerConfig::defaults(); + let mut cfg = Config::default(); // bin-dir default is "{$vendor-dir}/bin"; vendor-dir default is "vendor" resolve_references(&mut cfg); - assert_eq!(cfg.values["bin-dir"], serde_json::json!("vendor/bin")); + assert_eq!(cfg.bin_dir, "vendor/bin"); } #[test] fn test_reference_resolution_custom_vendor_dir() { - let mut cfg = ComposerConfig::defaults(); + let mut cfg = Config::default(); - // Override vendor-dir before resolving - cfg.values - .insert("vendor-dir".to_string(), serde_json::json!("lib")); + cfg.vendor_dir = "lib".to_string(); resolve_references(&mut cfg); - assert_eq!(cfg.values["bin-dir"], serde_json::json!("lib/bin")); + assert_eq!(cfg.bin_dir, "lib/bin"); } #[test] fn test_reference_resolution_cache_dirs() { - let mut cfg = ComposerConfig::defaults(); + let mut cfg = Config::default(); // Inject a predictable home so the test is environment-independent. - cfg.values.insert( - "cache-dir".to_string(), - serde_json::json!("/home/user/.cache/composer"), - ); + cfg.cache_dir = "/home/user/.cache/composer".to_string(); resolve_references(&mut cfg); - assert_eq!( - cfg.values["cache-files-dir"], - serde_json::json!("/home/user/.cache/composer/files") - ); - assert_eq!( - cfg.values["cache-repo-dir"], - serde_json::json!("/home/user/.cache/composer/repo") - ); - assert_eq!( - cfg.values["cache-vcs-dir"], - serde_json::json!("/home/user/.cache/composer/vcs") - ); + assert_eq!(cfg.cache_files_dir, "/home/user/.cache/composer/files"); + assert_eq!(cfg.cache_repo_dir, "/home/user/.cache/composer/repo"); + assert_eq!(cfg.cache_vcs_dir, "/home/user/.cache/composer/vcs"); } #[test] fn test_reference_resolution_no_change_for_non_string() { - let mut cfg = ComposerConfig::defaults(); - let before = cfg.values["process-timeout"].clone(); + let mut cfg = Config::default(); + let before = cfg.process_timeout; resolve_references(&mut cfg); - // Numeric values should be untouched. - assert_eq!(cfg.values["process-timeout"], before); + assert_eq!(cfg.process_timeout, before); } #[test] fn test_get_existing_key() { - let cfg = ComposerConfig::defaults(); + let cfg = Config::default(); let value = cfg.get("vendor-dir"); assert!(value.is_some()); - assert_eq!(value.unwrap(), &serde_json::json!("vendor")); + assert_eq!(value.unwrap(), serde_json::json!("vendor")); } #[test] fn test_get_nonexistent_key_returns_none() { - let cfg = ComposerConfig::defaults(); + let cfg = Config::default(); assert!(cfg.get("does-not-exist").is_none()); } @@ -1118,17 +1088,14 @@ mod tests { .unwrap(); let overrides = load_config_section(&composer_json).unwrap(); - let mut cfg = ComposerConfig::defaults(); - cfg.merge(&overrides); + let mut cfg = Config::default(); + cfg.merge(&overrides).unwrap(); resolve_references(&mut cfg); - assert_eq!(cfg.values["vendor-dir"], serde_json::json!("custom_vendor")); - assert_eq!(cfg.values["sort-packages"], serde_json::json!(true)); + assert_eq!(cfg.vendor_dir, "custom_vendor"); + assert_eq!(cfg.sort_packages, true); // bin-dir should have resolved against the overridden vendor-dir - assert_eq!( - cfg.values["bin-dir"], - serde_json::json!("custom_vendor/bin") - ); + assert_eq!(cfg.bin_dir, "custom_vendor/bin"); } #[test] diff --git a/crates/mozart/src/commands/dump_autoload.rs b/crates/mozart/src/commands/dump_autoload.rs index c7af429..a86cf21 100644 --- a/crates/mozart/src/commands/dump_autoload.rs +++ b/crates/mozart/src/commands/dump_autoload.rs @@ -78,23 +78,11 @@ pub async fn execute( } } - let optimize = args.optimize - || composer_config - .get("optimize-autoloader") - .and_then(|v| v.as_bool()) - .unwrap_or(false); - let classmap_authoritative = args.classmap_authoritative - || composer_config - .get("classmap-authoritative") - .and_then(|v| v.as_bool()) - .unwrap_or(false); + let optimize = args.optimize || composer_config.optimize_autoloader; + let classmap_authoritative = + args.classmap_authoritative || composer_config.classmap_authoritative; let apcu_prefix = args.apcu_prefix.clone(); - let apcu = apcu_prefix.is_some() - || args.apcu - || composer_config - .get("apcu-autoloader") - .and_then(|v| v.as_bool()) - .unwrap_or(false); + let apcu = apcu_prefix.is_some() || args.apcu || composer_config.apcu_autoloader; let do_optimize = optimize || classmap_authoritative; if args.strict_psr && !do_optimize { diff --git a/crates/mozart/src/commands/exec.rs b/crates/mozart/src/commands/exec.rs index f802721..9d79bee 100644 --- a/crates/mozart/src/commands/exec.rs +++ b/crates/mozart/src/commands/exec.rs @@ -117,12 +117,7 @@ pub async fn execute( fn resolve_bin_dir(working_dir: &Path, composer: &Composer) -> PathBuf { // bin-dir's `{$vendor-dir}` placeholder is already resolved by Composer::load. - let bin_dir = composer - .config() - .get("bin-dir") - .and_then(|v| v.as_str()) - .unwrap_or("vendor/bin"); - working_dir.join(bin_dir) + working_dir.join(&composer.config().bin_dir) } /// Returns a vec of (name, is_local) tuples for all available binaries. diff --git a/crates/mozart/src/commands/run_script.rs b/crates/mozart/src/commands/run_script.rs index 4292809..7e5cbe4 100644 --- a/crates/mozart/src/commands/run_script.rs +++ b/crates/mozart/src/commands/run_script.rs @@ -110,12 +110,14 @@ pub async fn execute( let timeout = match args.timeout { Some(0) => None, Some(secs) => Some(Duration::from_secs(secs)), - None => composer - .config() - .get("process-timeout") - .and_then(|v| v.as_u64()) - .filter(|s| *s != 0) - .map(Duration::from_secs), + None => { + let t = composer.config().process_timeout; + if t != 0 { + Some(Duration::from_secs(t)) + } else { + None + } + } }; let dev_mode = !args.no_dev; @@ -445,12 +447,7 @@ fn wait_with_timeout( fn resolve_bin_dir(working_dir: &Path, composer: &mozart_core::composer::Composer) -> PathBuf { // bin-dir's `{$vendor-dir}` placeholder is already resolved by Composer::load. - let bin_dir = composer - .config() - .get("bin-dir") - .and_then(|v| v.as_str()) - .unwrap_or("vendor/bin"); - working_dir.join(bin_dir) + working_dir.join(&composer.config().bin_dir) } fn is_php_callback(entry: &str) -> bool { |
