diff options
Diffstat (limited to 'crates/mozart/src/package.rs')
| -rw-r--r-- | crates/mozart/src/package.rs | 703 |
1 files changed, 0 insertions, 703 deletions
diff --git a/crates/mozart/src/package.rs b/crates/mozart/src/package.rs deleted file mode 100644 index 9904dc4..0000000 --- a/crates/mozart/src/package.rs +++ /dev/null @@ -1,703 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::fs; -use std::path::Path; - -/// Package stability level. -/// Higher value = less stable. -/// Corresponds to `Composer\Package\BasePackage::STABILITY_*`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -#[repr(u8)] -pub enum Stability { - #[default] - Stable = 0, - RC = 5, - Beta = 10, - Alpha = 15, - Dev = 20, -} - -impl Stability { - /// Parse a stability string (case-insensitive) into a `Stability` value. - /// - /// Recognizes: "stable", "RC", "beta", "alpha", "dev". - /// Defaults to `Stability::Stable` for unrecognized values. - pub fn parse(s: &str) -> Self { - match s.to_lowercase().as_str() { - "dev" => Stability::Dev, - "alpha" => Stability::Alpha, - "beta" => Stability::Beta, - "rc" => Stability::RC, - _ => Stability::Stable, - } - } -} - -/// A versioned relationship between two packages. -/// Corresponds to `Composer\Package\Link`. -#[derive(Debug, Clone)] -pub struct Link { - pub source: String, - pub target: String, - pub constraint: String, - pub pretty_constraint: Option<String>, - pub description: String, -} - -/// Package author metadata. -#[derive(Debug, Clone)] -pub struct Author { - pub name: Option<String>, - pub email: Option<String>, - pub homepage: Option<String>, - pub role: Option<String>, -} - -/// Autoload rule sets (PSR-4, PSR-0, classmap, files). -#[derive(Debug, Clone, Default)] -pub struct AutoloadRules { - pub psr4: BTreeMap<String, Vec<String>>, - pub psr0: BTreeMap<String, Vec<String>>, - pub classmap: Vec<String>, - pub files: Vec<String>, -} - -/// Support channel information. -#[derive(Debug, Clone, Default)] -pub struct Support { - pub email: Option<String>, - pub issues: Option<String>, - pub forum: Option<String>, - pub wiki: Option<String>, - pub source: Option<String>, - pub docs: Option<String>, - pub irc: Option<String>, - pub chat: Option<String>, - pub rss: Option<String>, - pub security: Option<String>, -} - -/// Funding link. -#[derive(Debug, Clone)] -pub struct Funding { - pub url: Option<String>, - pub funding_type: Option<String>, -} - -/// Version alias entry for root packages. -#[derive(Debug, Clone)] -pub struct VersionAlias { - pub package: String, - pub version: String, - pub alias: String, - pub alias_normalized: String, -} - -/// Core package data covering `BasePackage` + `Package` fields. -/// Corresponds to `Composer\Package\Package` (implements `PackageInterface`). -#[derive(Debug, Clone)] -pub struct PackageData { - // BasePackage fields - pub name: String, - pub pretty_name: String, - - // Package fields - pub version: String, - pub pretty_version: String, - pub package_type: String, - pub target_dir: Option<String>, - - // source - pub source_type: Option<String>, - pub source_url: Option<String>, - pub source_reference: Option<String>, - - // dist - pub dist_type: Option<String>, - pub dist_url: Option<String>, - pub dist_reference: Option<String>, - pub dist_sha1_checksum: Option<String>, - - pub release_date: Option<String>, - pub extra: BTreeMap<String, serde_json::Value>, - pub binaries: Vec<String>, - pub dev: bool, - pub stability: Stability, - pub notification_url: Option<String>, - - // dependency links - pub requires: BTreeMap<String, Link>, - pub conflicts: BTreeMap<String, Link>, - pub provides: BTreeMap<String, Link>, - pub replaces: BTreeMap<String, Link>, - pub dev_requires: BTreeMap<String, Link>, - pub suggests: BTreeMap<String, String>, - - // autoload - pub autoload: AutoloadRules, - pub dev_autoload: AutoloadRules, - - pub is_default_branch: bool, -} - -/// Package with full metadata (description, authors, license, etc.). -/// Corresponds to `Composer\Package\CompletePackage`. -#[derive(Debug, Clone)] -pub struct CompletePackageData { - pub package: PackageData, - - pub description: Option<String>, - pub homepage: Option<String>, - pub license: Vec<String>, - pub keywords: Vec<String>, - pub authors: Vec<Author>, - pub scripts: BTreeMap<String, Vec<String>>, - pub support: Support, - pub funding: Vec<Funding>, - pub repositories: Vec<serde_json::Value>, - /// `None` = not abandoned, `Some("")` = abandoned, `Some(pkg)` = replaced by pkg. - pub abandoned: Option<String>, - pub archive_name: Option<String>, - pub archive_excludes: Vec<String>, -} - -/// The root project package with project-level configuration. -/// Corresponds to `Composer\Package\RootPackage`. -#[derive(Debug, Clone)] -pub struct RootPackageData { - pub complete: CompletePackageData, - - pub minimum_stability: Stability, - pub prefer_stable: bool, - pub stability_flags: BTreeMap<String, Stability>, - pub config: BTreeMap<String, serde_json::Value>, - pub references: BTreeMap<String, String>, - pub aliases: Vec<VersionAlias>, -} - -/// Accessor for `PackageData` fields. -/// Corresponds to `Composer\Package\PackageInterface`. -pub trait Package { - fn name(&self) -> &str; - fn pretty_name(&self) -> &str; - fn version(&self) -> &str; - fn pretty_version(&self) -> &str; - fn package_type(&self) -> &str; - fn target_dir(&self) -> Option<&str>; - fn source_type(&self) -> Option<&str>; - fn source_url(&self) -> Option<&str>; - fn source_reference(&self) -> Option<&str>; - fn dist_type(&self) -> Option<&str>; - fn dist_url(&self) -> Option<&str>; - fn dist_reference(&self) -> Option<&str>; - fn dist_sha1_checksum(&self) -> Option<&str>; - fn release_date(&self) -> Option<&str>; - fn extra(&self) -> &BTreeMap<String, serde_json::Value>; - fn binaries(&self) -> &[String]; - fn is_dev(&self) -> bool; - fn stability(&self) -> Stability; - fn notification_url(&self) -> Option<&str>; - fn requires(&self) -> &BTreeMap<String, Link>; - fn conflicts(&self) -> &BTreeMap<String, Link>; - fn provides(&self) -> &BTreeMap<String, Link>; - fn replaces(&self) -> &BTreeMap<String, Link>; - fn dev_requires(&self) -> &BTreeMap<String, Link>; - fn suggests(&self) -> &BTreeMap<String, String>; - fn autoload(&self) -> &AutoloadRules; - fn dev_autoload(&self) -> &AutoloadRules; - fn is_default_branch(&self) -> bool; -} - -/// Accessor for `CompletePackageData` fields. -/// Corresponds to `Composer\Package\CompletePackageInterface`. -pub trait CompletePackage: Package { - fn description(&self) -> Option<&str>; - fn homepage(&self) -> Option<&str>; - fn license(&self) -> &[String]; - fn keywords(&self) -> &[String]; - fn authors(&self) -> &[Author]; - fn scripts(&self) -> &BTreeMap<String, Vec<String>>; - fn support(&self) -> &Support; - fn funding(&self) -> &[Funding]; - fn repositories(&self) -> &[serde_json::Value]; - fn abandoned(&self) -> Option<&str>; - fn archive_name(&self) -> Option<&str>; - fn archive_excludes(&self) -> &[String]; -} - -/// Accessor for `RootPackageData` fields. -/// Corresponds to `Composer\Package\RootPackageInterface`. -pub trait RootPackage: CompletePackage { - fn minimum_stability(&self) -> Stability; - fn prefer_stable(&self) -> bool; - fn stability_flags(&self) -> &BTreeMap<String, Stability>; - fn config(&self) -> &BTreeMap<String, serde_json::Value>; - fn references(&self) -> &BTreeMap<String, String>; - fn aliases(&self) -> &[VersionAlias]; -} - -// ────────────────────────────────────────────── -// Delegation macros -// ────────────────────────────────────────────── - -/// Implements `Package` trait by delegating to an inner `PackageData` field. -macro_rules! delegate_package { - ($type:ty => $($path:ident).+) => { - impl Package for $type { - fn name(&self) -> &str { &self.$($path).+.name } - fn pretty_name(&self) -> &str { &self.$($path).+.pretty_name } - fn version(&self) -> &str { &self.$($path).+.version } - fn pretty_version(&self) -> &str { &self.$($path).+.pretty_version } - fn package_type(&self) -> &str { &self.$($path).+.package_type } - fn target_dir(&self) -> Option<&str> { self.$($path).+.target_dir.as_deref() } - fn source_type(&self) -> Option<&str> { self.$($path).+.source_type.as_deref() } - fn source_url(&self) -> Option<&str> { self.$($path).+.source_url.as_deref() } - fn source_reference(&self) -> Option<&str> { self.$($path).+.source_reference.as_deref() } - fn dist_type(&self) -> Option<&str> { self.$($path).+.dist_type.as_deref() } - fn dist_url(&self) -> Option<&str> { self.$($path).+.dist_url.as_deref() } - fn dist_reference(&self) -> Option<&str> { self.$($path).+.dist_reference.as_deref() } - fn dist_sha1_checksum(&self) -> Option<&str> { self.$($path).+.dist_sha1_checksum.as_deref() } - fn release_date(&self) -> Option<&str> { self.$($path).+.release_date.as_deref() } - fn extra(&self) -> &BTreeMap<String, serde_json::Value> { &self.$($path).+.extra } - fn binaries(&self) -> &[String] { &self.$($path).+.binaries } - fn is_dev(&self) -> bool { self.$($path).+.dev } - fn stability(&self) -> Stability { self.$($path).+.stability } - fn notification_url(&self) -> Option<&str> { self.$($path).+.notification_url.as_deref() } - fn requires(&self) -> &BTreeMap<String, Link> { &self.$($path).+.requires } - fn conflicts(&self) -> &BTreeMap<String, Link> { &self.$($path).+.conflicts } - fn provides(&self) -> &BTreeMap<String, Link> { &self.$($path).+.provides } - fn replaces(&self) -> &BTreeMap<String, Link> { &self.$($path).+.replaces } - fn dev_requires(&self) -> &BTreeMap<String, Link> { &self.$($path).+.dev_requires } - fn suggests(&self) -> &BTreeMap<String, String> { &self.$($path).+.suggests } - fn autoload(&self) -> &AutoloadRules { &self.$($path).+.autoload } - fn dev_autoload(&self) -> &AutoloadRules { &self.$($path).+.dev_autoload } - fn is_default_branch(&self) -> bool { self.$($path).+.is_default_branch } - } - }; -} - -/// Implements `CompletePackage` trait by delegating to an inner `CompletePackageData` field. -macro_rules! delegate_complete_package { - ($type:ty => $($path:ident).+) => { - impl CompletePackage for $type { - fn description(&self) -> Option<&str> { self.$($path).+.description.as_deref() } - fn homepage(&self) -> Option<&str> { self.$($path).+.homepage.as_deref() } - fn license(&self) -> &[String] { &self.$($path).+.license } - fn keywords(&self) -> &[String] { &self.$($path).+.keywords } - fn authors(&self) -> &[Author] { &self.$($path).+.authors } - fn scripts(&self) -> &BTreeMap<String, Vec<String>> { &self.$($path).+.scripts } - fn support(&self) -> &Support { &self.$($path).+.support } - fn funding(&self) -> &[Funding] { &self.$($path).+.funding } - fn repositories(&self) -> &[serde_json::Value] { &self.$($path).+.repositories } - fn abandoned(&self) -> Option<&str> { self.$($path).+.abandoned.as_deref() } - fn archive_name(&self) -> Option<&str> { self.$($path).+.archive_name.as_deref() } - fn archive_excludes(&self) -> &[String] { &self.$($path).+.archive_excludes } - } - }; -} - -impl Package for PackageData { - fn name(&self) -> &str { - &self.name - } - fn pretty_name(&self) -> &str { - &self.pretty_name - } - fn version(&self) -> &str { - &self.version - } - fn pretty_version(&self) -> &str { - &self.pretty_version - } - fn package_type(&self) -> &str { - &self.package_type - } - fn target_dir(&self) -> Option<&str> { - self.target_dir.as_deref() - } - fn source_type(&self) -> Option<&str> { - self.source_type.as_deref() - } - fn source_url(&self) -> Option<&str> { - self.source_url.as_deref() - } - fn source_reference(&self) -> Option<&str> { - self.source_reference.as_deref() - } - fn dist_type(&self) -> Option<&str> { - self.dist_type.as_deref() - } - fn dist_url(&self) -> Option<&str> { - self.dist_url.as_deref() - } - fn dist_reference(&self) -> Option<&str> { - self.dist_reference.as_deref() - } - fn dist_sha1_checksum(&self) -> Option<&str> { - self.dist_sha1_checksum.as_deref() - } - fn release_date(&self) -> Option<&str> { - self.release_date.as_deref() - } - fn extra(&self) -> &BTreeMap<String, serde_json::Value> { - &self.extra - } - fn binaries(&self) -> &[String] { - &self.binaries - } - fn is_dev(&self) -> bool { - self.dev - } - fn stability(&self) -> Stability { - self.stability - } - fn notification_url(&self) -> Option<&str> { - self.notification_url.as_deref() - } - fn requires(&self) -> &BTreeMap<String, Link> { - &self.requires - } - fn conflicts(&self) -> &BTreeMap<String, Link> { - &self.conflicts - } - fn provides(&self) -> &BTreeMap<String, Link> { - &self.provides - } - fn replaces(&self) -> &BTreeMap<String, Link> { - &self.replaces - } - fn dev_requires(&self) -> &BTreeMap<String, Link> { - &self.dev_requires - } - fn suggests(&self) -> &BTreeMap<String, String> { - &self.suggests - } - fn autoload(&self) -> &AutoloadRules { - &self.autoload - } - fn dev_autoload(&self) -> &AutoloadRules { - &self.dev_autoload - } - fn is_default_branch(&self) -> bool { - self.is_default_branch - } -} - -impl CompletePackage for CompletePackageData { - fn description(&self) -> Option<&str> { - self.description.as_deref() - } - fn homepage(&self) -> Option<&str> { - self.homepage.as_deref() - } - fn license(&self) -> &[String] { - &self.license - } - fn keywords(&self) -> &[String] { - &self.keywords - } - fn authors(&self) -> &[Author] { - &self.authors - } - fn scripts(&self) -> &BTreeMap<String, Vec<String>> { - &self.scripts - } - fn support(&self) -> &Support { - &self.support - } - fn funding(&self) -> &[Funding] { - &self.funding - } - fn repositories(&self) -> &[serde_json::Value] { - &self.repositories - } - fn abandoned(&self) -> Option<&str> { - self.abandoned.as_deref() - } - fn archive_name(&self) -> Option<&str> { - self.archive_name.as_deref() - } - fn archive_excludes(&self) -> &[String] { - &self.archive_excludes - } -} - -impl RootPackage for RootPackageData { - fn minimum_stability(&self) -> Stability { - self.minimum_stability - } - fn prefer_stable(&self) -> bool { - self.prefer_stable - } - fn stability_flags(&self) -> &BTreeMap<String, Stability> { - &self.stability_flags - } - fn config(&self) -> &BTreeMap<String, serde_json::Value> { - &self.config - } - fn references(&self) -> &BTreeMap<String, String> { - &self.references - } - fn aliases(&self) -> &[VersionAlias] { - &self.aliases - } -} - -// CompletePackageData delegates Package → inner PackageData -delegate_package!(CompletePackageData => package); - -// RootPackageData delegates Package → inner CompletePackageData → PackageData -delegate_package!(RootPackageData => complete.package); - -// RootPackageData delegates CompletePackage → inner CompletePackageData -delegate_complete_package!(RootPackageData => complete); - -/// Unstructured representation of a composer.json file. -/// Used by `init` and `create-project` to write a new composer.json. -/// Unlike the typed hierarchy above, all fields live at a single level -/// and map directly to the JSON keys via serde. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RawPackageData { - pub name: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option<String>, - - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub package_type: Option<String>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub homepage: Option<String>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub license: Option<String>, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub authors: Vec<RawAuthor>, - - #[serde(rename = "minimum-stability", skip_serializing_if = "Option::is_none")] - pub minimum_stability: Option<String>, - - #[serde(default)] - pub require: BTreeMap<String, String>, - - #[serde( - rename = "require-dev", - default, - skip_serializing_if = "BTreeMap::is_empty" - )] - pub require_dev: BTreeMap<String, String>, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub repositories: Vec<RawRepository>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub autoload: Option<RawAutoload>, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub bin: Vec<String>, - - #[serde(flatten)] - pub extra_fields: BTreeMap<String, serde_json::Value>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RawAuthor { - pub name: String, - - #[serde(skip_serializing_if = "Option::is_none")] - pub email: Option<String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RawAutoload { - #[serde(rename = "psr-4")] - pub psr4: BTreeMap<String, String>, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RawRepository { - #[serde(rename = "type")] - pub repo_type: String, - pub url: String, -} - -impl RawPackageData { - pub fn new(name: String) -> Self { - Self { - name, - description: None, - package_type: None, - homepage: None, - license: None, - authors: Vec::new(), - minimum_stability: None, - require: BTreeMap::new(), - require_dev: BTreeMap::new(), - repositories: Vec::new(), - autoload: None, - bin: Vec::new(), - extra_fields: BTreeMap::new(), - } - } -} - -pub fn read_from_file(path: &Path) -> anyhow::Result<RawPackageData> { - let content = fs::read_to_string(path)?; - let data: RawPackageData = serde_json::from_str(&content)?; - Ok(data) -} - -pub fn to_json_pretty(value: &impl Serialize) -> serde_json::Result<String> { - let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); - let mut buf = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter); - value.serialize(&mut ser)?; - let mut json = String::from_utf8(buf).expect("serde_json produces valid UTF-8"); - json.push('\n'); - Ok(json) -} - -pub fn write_to_file(value: &impl Serialize, path: &Path) -> anyhow::Result<()> { - let json = to_json_pretty(value)?; - fs::write(path, json)?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn raw_minimal_json() { - let raw = RawPackageData::new("test/pkg".to_string()); - let json = to_json_pretty(&raw).unwrap(); - let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); - - assert_eq!(parsed["name"], "test/pkg"); - assert!(parsed["require"].is_object()); - assert!(parsed.get("description").is_none()); - assert!(parsed.get("type").is_none()); - assert!(parsed.get("authors").is_none()); - assert!(parsed.get("require-dev").is_none()); - assert!(parsed.get("autoload").is_none()); - } - - #[test] - fn raw_full_json() { - let mut raw = RawPackageData::new("acme/full".to_string()); - raw.description = Some("A full package".to_string()); - raw.package_type = Some("library".to_string()); - raw.homepage = Some("https://example.com".to_string()); - raw.license = Some("MIT".to_string()); - raw.authors = vec![RawAuthor { - name: "Jane Doe".to_string(), - email: Some("jane@example.com".to_string()), - }]; - raw.minimum_stability = Some("dev".to_string()); - raw.require.insert("php".to_string(), ">=8.1".to_string()); - raw.require_dev - .insert("phpunit/phpunit".to_string(), "^10.0".to_string()); - raw.repositories = vec![RawRepository { - repo_type: "vcs".to_string(), - url: "https://github.com/acme/repo".to_string(), - }]; - - let mut psr4 = BTreeMap::new(); - psr4.insert("Acme\\Full\\".to_string(), "src/".to_string()); - raw.autoload = Some(RawAutoload { psr4 }); - - let json = to_json_pretty(&raw).unwrap(); - let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); - - assert_eq!(parsed["name"], "acme/full"); - assert_eq!(parsed["description"], "A full package"); - assert_eq!(parsed["type"], "library"); - assert_eq!(parsed["homepage"], "https://example.com"); - assert_eq!(parsed["license"], "MIT"); - assert_eq!(parsed["minimum-stability"], "dev"); - assert_eq!(parsed["authors"][0]["name"], "Jane Doe"); - assert_eq!(parsed["authors"][0]["email"], "jane@example.com"); - assert_eq!(parsed["require"]["php"], ">=8.1"); - assert_eq!(parsed["require-dev"]["phpunit/phpunit"], "^10.0"); - assert_eq!(parsed["repositories"][0]["type"], "vcs"); - assert_eq!(parsed["autoload"]["psr-4"]["Acme\\Full\\"], "src/"); - } - - #[test] - fn raw_deserialize_minimal() { - let json = r#"{"name": "test/pkg"}"#; - let raw: RawPackageData = serde_json::from_str(json).unwrap(); - assert_eq!(raw.name, "test/pkg"); - assert!(raw.description.is_none()); - assert!(raw.require.is_empty()); - assert!(raw.require_dev.is_empty()); - assert!(raw.authors.is_empty()); - assert!(raw.extra_fields.is_empty()); - } - - #[test] - fn raw_roundtrip_preserves_all_fields() { - let mut raw = RawPackageData::new("acme/roundtrip".to_string()); - raw.description = Some("Test roundtrip".to_string()); - raw.require.insert("php".to_string(), ">=8.1".to_string()); - raw.require_dev - .insert("phpunit/phpunit".to_string(), "^10.0".to_string()); - - let json1 = to_json_pretty(&raw).unwrap(); - let deserialized: RawPackageData = serde_json::from_str(&json1).unwrap(); - let json2 = to_json_pretty(&deserialized).unwrap(); - assert_eq!(json1, json2); - } - - #[test] - fn raw_extra_fields_preserved() { - let json = r#"{ - "name": "test/extra", - "require": {}, - "scripts": {"post-install-cmd": ["echo hello"]}, - "config": {"sort-packages": true}, - "extra": {"custom-key": "custom-value"} - }"#; - let raw: RawPackageData = serde_json::from_str(json).unwrap(); - assert_eq!(raw.name, "test/extra"); - assert!(raw.extra_fields.contains_key("scripts")); - assert!(raw.extra_fields.contains_key("config")); - assert!(raw.extra_fields.contains_key("extra")); - - // Roundtrip: extra fields should be preserved in output - let output = to_json_pretty(&raw).unwrap(); - let parsed: serde_json::Value = serde_json::from_str(&output).unwrap(); - assert!(parsed["scripts"].is_object()); - assert!(parsed["config"].is_object()); - assert!(parsed["extra"].is_object()); - } - - #[test] - fn raw_read_from_file() { - let dir = tempfile::tempdir().unwrap(); - let path = dir.path().join("composer.json"); - let content = r#"{"name": "test/file", "require": {"php": ">=8.0"}}"#; - std::fs::write(&path, content).unwrap(); - - let raw = read_from_file(&path).unwrap(); - assert_eq!(raw.name, "test/file"); - assert_eq!(raw.require.get("php").unwrap(), ">=8.0"); - } - - #[test] - fn raw_none_fields_omitted() { - let raw = RawPackageData::new("test/empty".to_string()); - let json = to_json_pretty(&raw).unwrap(); - - assert!(!json.contains("\"description\"")); - assert!(!json.contains("\"type\"")); - assert!(!json.contains("\"homepage\"")); - assert!(!json.contains("\"license\"")); - assert!(!json.contains("\"authors\"")); - assert!(!json.contains("\"minimum-stability\"")); - assert!(!json.contains("\"require-dev\"")); - assert!(!json.contains("\"repositories\"")); - assert!(!json.contains("\"autoload\"")); - } -} |
