aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--crates/mozart-core/src/composer.rs134
-rw-r--r--crates/mozart-core/src/config.rs329
-rw-r--r--crates/mozart-core/src/lib.rs1
-rw-r--r--crates/mozart/src/commands/archive.rs13
-rw-r--r--crates/mozart/src/commands/config.rs139
-rw-r--r--crates/mozart/src/commands/dump_autoload.rs20
-rw-r--r--crates/mozart/src/commands/exec.rs7
-rw-r--r--crates/mozart/src/commands/run_script.rs21
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 {