aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-08 20:44:26 +0900
committernsfisis <nsfisis@gmail.com>2026-05-08 20:44:26 +0900
commitee17433f9beb95071f37aa6cfe659f14b81ce503 (patch)
tree512187f12ce668cb6ac09c14bd8fd2cfad212449
parent92fa497cc345118198508fcf948ff650e8902434 (diff)
downloadphp-mozart-ee17433f9beb95071f37aa6cfe659f14b81ce503.tar.gz
php-mozart-ee17433f9beb95071f37aa6cfe659f14b81ce503.tar.zst
php-mozart-ee17433f9beb95071f37aa6cfe659f14b81ce503.zip
fix(outdated): align with Composer's OutdatedCommand proxy
Composer's OutdatedCommand (isProxyCommand = true) just remaps options and re-invokes `show --latest [--outdated]`; Mozart's outdated.rs was a ~700-line parallel reimplementation of show's outdated logic with its own classifier, renderer, JSON shape, and platform predicate. Collapse it into a thin proxy that builds a ShowArgs from OutdatedArgs and calls show::execute, mirroring OutdatedCommand::execute field-for-field. This restores show as the single source of truth for rendering, JSON fields, --strict, and the mutual-exclusion checks. Surfaced gaps in show.rs (--major-only/--minor-only/--patch-only filtering, --sort-by-age, JSON enrichment with homepage/source/time/abandoned) are deferred per the plan.
-rw-r--r--crates/mozart/src/commands/outdated.rs734
1 files changed, 35 insertions, 699 deletions
diff --git a/crates/mozart/src/commands/outdated.rs b/crates/mozart/src/commands/outdated.rs
index 46f09f9..c17360d 100644
--- a/crates/mozart/src/commands/outdated.rs
+++ b/crates/mozart/src/commands/outdated.rs
@@ -1,21 +1,11 @@
use clap::Args;
-use indexmap::IndexSet;
-use mozart_core::console_format;
-use mozart_core::console_writeln;
-use mozart_core::matches_wildcard;
-use std::cmp::Ordering;
-use std::path::Path;
#[derive(Args)]
pub struct OutdatedArgs {
/// Package to inspect
pub package: Option<String>,
- /// Show only packages that are outdated
- #[arg(short, long)]
- pub outdated: bool,
-
- /// Show all installed packages
+ /// Show all installed packages including up-to-date ones
#[arg(short, long)]
pub all: bool,
@@ -48,8 +38,8 @@ pub struct OutdatedArgs {
pub sort_by_age: bool,
/// Output format (text, json)
- #[arg(short, long)]
- pub format: Option<String>,
+ #[arg(short, long, default_value = "text")]
+ pub format: String,
/// Ignore specified package(s)
#[arg(long)]
@@ -68,697 +58,43 @@ pub struct OutdatedArgs {
pub ignore_platform_reqs: bool,
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum UpdateCategory {
- UpToDate,
- /// Constraint allows the update — show in RED (you SHOULD update)
- SemverCompatible,
- /// New major / constraint change needed — show in YELLOW
- SemverIncompatible,
-}
-
-#[derive(Debug, Clone)]
-struct PackageInfo {
- name: String,
- version: String,
- version_normalized: String,
- description: String,
-}
-
-#[derive(Debug, Clone)]
-struct OutdatedEntry {
- name: String,
- current_version: String,
- latest_version: String,
- description: String,
- category: UpdateCategory,
- is_direct: bool,
-}
-
+/// `outdated` is a proxy command — it mirrors Composer's `OutdatedCommand::execute`
+/// (see `composer/src/Composer/Command/OutdatedCommand.php` 68–126), which remaps
+/// its options into a `show --latest [--outdated]` invocation. Keeping the logic
+/// in one place means every behavioral aspect (rendering, JSON shape, --strict,
+/// mutual-exclusion checks, ignore warnings, etc.) has a single source of truth.
pub async fn execute(
args: &OutdatedArgs,
cli: &super::Cli,
console: &mozart_core::console::Console,
) -> anyhow::Result<()> {
- let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache);
- let repo_cache = mozart_registry::cache::Cache::repo(&cache_config);
-
- // Validate mutually exclusive level filters
- let level_count = args.major_only as u8 + args.minor_only as u8 + args.patch_only as u8;
- if level_count > 1 {
- anyhow::bail!("Only one of --major-only, --minor-only or --patch-only can be used at once");
- }
-
- let working_dir = cli.working_dir()?;
-
- // Load packages (installed or locked)
- let packages = if args.locked {
- load_locked_packages(&working_dir, args.no_dev)?
- } else {
- load_installed_packages(&working_dir, args.no_dev)?
+ let show_args = super::show::ShowArgs {
+ package: args.package.clone(),
+ version: None,
+ all: false,
+ locked: args.locked,
+ installed: false,
+ platform: false,
+ available: false,
+ self_info: false,
+ name_only: false,
+ path: false,
+ tree: false,
+ latest: true,
+ // Composer: `if (!--all) $args['--outdated'] = true;`
+ outdated: !args.all,
+ ignore: args.ignore.clone(),
+ major_only: args.major_only,
+ minor_only: args.minor_only,
+ patch_only: args.patch_only,
+ sort_by_age: args.sort_by_age,
+ direct: args.direct,
+ strict: args.strict,
+ format: Some(args.format.clone()),
+ no_dev: args.no_dev,
+ ignore_platform_req: args.ignore_platform_req.clone(),
+ ignore_platform_reqs: args.ignore_platform_reqs,
};
- if packages.is_empty() {
- return Ok(());
- }
-
- // Load root composer.json for --direct filtering and constraint lookup
- let composer_json_path = working_dir.join("composer.json");
- let root_package = if composer_json_path.exists() {
- mozart_core::package::read_from_file(&composer_json_path).ok()
- } else {
- None
- };
-
- // Build set of direct dependency names
- let direct_names: IndexSet<String> = if let Some(ref root) = root_package {
- let mut names: IndexSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect();
- if !args.no_dev {
- names.extend(root.require_dev.keys().map(|k| k.to_lowercase()));
- }
- names
- } else {
- IndexSet::new()
- };
-
- // Process each package
- let mut entries: Vec<OutdatedEntry> = Vec::new();
- for pkg in &packages {
- // Skip ignored packages
- if args
- .ignore
- .iter()
- .any(|pattern| matches_wildcard(&pkg.name, pattern))
- {
- continue;
- }
-
- // --direct filter
- if args.direct && !direct_names.contains(&pkg.name.to_lowercase()) {
- continue;
- }
-
- // --package filter
- if let Some(ref filter) = args.package {
- if filter.contains('*') {
- if !matches_wildcard(&pkg.name, filter) {
- continue;
- }
- } else if !pkg.name.eq_ignore_ascii_case(filter) {
- continue;
- }
- }
-
- // Fetch latest version from Packagist
- let latest = match fetch_latest_version(&pkg.name, &repo_cache).await {
- Ok(v) => v,
- Err(_) => {
- // Skip packages we can't fetch (platform packages, private, etc.)
- continue;
- }
- };
-
- // Classify the update
- let category = classify_update(&pkg.version_normalized, &latest.version_normalized);
-
- // If showing all, include up-to-date; otherwise only show outdated
- if !args.all && category == UpdateCategory::UpToDate {
- continue;
- }
-
- // Apply level filter (--major-only, --minor-only, --patch-only)
- if (args.major_only || args.minor_only || args.patch_only)
- && category != UpdateCategory::UpToDate
- && !passes_level_filter(args, &pkg.version_normalized, &latest.version_normalized)
- {
- continue;
- }
-
- let is_direct = direct_names.contains(&pkg.name.to_lowercase());
-
- entries.push(OutdatedEntry {
- name: pkg.name.clone(),
- current_version: pkg.version.clone(),
- latest_version: latest.version.clone(),
- description: latest.description.clone(),
- category,
- is_direct,
- });
- }
-
- // Sort alphabetically by name
- entries.sort_by(|a, b| a.name.cmp(&b.name));
-
- // Render output
- let format = args.format.as_deref().unwrap_or("text");
- match format {
- "json" => render_json(&entries, console)?,
- _ => render_text(&entries, console),
- }
-
- // --strict: exit with code 1 if any outdated packages exist
- if args.strict {
- let has_outdated = entries
- .iter()
- .any(|e| e.category != UpdateCategory::UpToDate);
- if has_outdated {
- return Err(mozart_core::exit_code::bail_silent(
- mozart_core::exit_code::GENERAL_ERROR,
- ));
- }
- }
-
- Ok(())
-}
-
-fn load_installed_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec<PackageInfo>> {
- let vendor_dir = working_dir.join("vendor");
- let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?;
-
- let dev_names: IndexSet<String> = installed
- .dev_package_names
- .iter()
- .map(|n| n.to_lowercase())
- .collect();
-
- let mut packages: Vec<PackageInfo> = installed
- .packages
- .iter()
- .filter(|p| {
- // Skip dev packages when --no-dev
- if no_dev && dev_names.contains(&p.name.to_lowercase()) {
- return false;
- }
- // Skip platform packages
- if is_platform_package(&p.name) {
- return false;
- }
- true
- })
- .map(|p| {
- let version_normalized = p
- .version_normalized
- .clone()
- .unwrap_or_else(|| normalize_version_simple(&p.version));
- let description = p
- .extra_fields
- .get("description")
- .and_then(|v| v.as_str())
- .unwrap_or("")
- .to_string();
- PackageInfo {
- name: p.name.clone(),
- version: p.version.clone(),
- version_normalized,
- description,
- }
- })
- .collect();
-
- packages.sort_by(|a, b| a.name.cmp(&b.name));
- Ok(packages)
-}
-
-fn load_locked_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec<PackageInfo>> {
- let lock_path = working_dir.join("composer.lock");
- if !lock_path.exists() {
- anyhow::bail!(
- "A valid composer.json and composer.lock file is required to run this command with --locked"
- );
- }
-
- let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?;
-
- let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> =
- lock.packages.iter().collect();
-
- if !no_dev && let Some(ref pkgs_dev) = lock.packages_dev {
- all_packages.extend(pkgs_dev.iter());
- }
-
- let packages: Vec<PackageInfo> = all_packages
- .iter()
- .filter(|p| !is_platform_package(&p.name))
- .map(|p| {
- let version_normalized = p
- .version_normalized
- .clone()
- .unwrap_or_else(|| normalize_version_simple(&p.version));
- let description = p.description.as_deref().unwrap_or("").to_string();
- PackageInfo {
- name: p.name.clone(),
- version: p.version.clone(),
- version_normalized,
- description,
- }
- })
- .collect();
-
- Ok(packages)
-}
-
-async fn fetch_latest_version(
- name: &str,
- repo_cache: &mozart_registry::cache::Cache,
-) -> anyhow::Result<PackageInfo> {
- use mozart_core::package::Stability;
- use mozart_registry::version::find_best_candidate;
-
- let versions = mozart_registry::packagist::fetch_package_versions(name, repo_cache).await?;
- let best = find_best_candidate(&versions, Stability::Stable)
- .ok_or_else(|| anyhow::anyhow!("No stable version found for {name}"))?;
-
- Ok(PackageInfo {
- name: name.to_string(),
- version: best.version.clone(),
- version_normalized: best.version_normalized.clone(),
- description: best.description.as_deref().unwrap_or("").to_string(),
- })
-}
-
-/// Determine the update category for a package.
-///
-/// Mirrors Composer's logic: constructs `^<installed_version>` and checks if the
-/// latest version satisfies it.
-///
-/// - If latest <= current → UpToDate
-/// - If latest satisfies `^<current>` → SemverCompatible (semver-safe update)
-/// - Otherwise → SemverIncompatible (update-possible, may require constraint change)
-fn classify_update(current_normalized: &str, latest_normalized: &str) -> UpdateCategory {
- use mozart_registry::version::compare_normalized_versions;
-
- // If latest is not newer than current, it's up-to-date
- if compare_normalized_versions(latest_normalized, current_normalized) != Ordering::Greater {
- return UpdateCategory::UpToDate;
- }
-
- // Build ^<current_normalized> constraint and check if latest satisfies it.
- // This mirrors Composer's approach of checking semver safety.
- let caret_constraint = format!("^{current_normalized}");
- if let Ok(constraint) = mozart_semver::VersionConstraint::parse(&caret_constraint)
- && let Ok(latest_ver) = mozart_semver::Version::parse(latest_normalized)
- {
- if constraint.matches(&latest_ver) {
- return UpdateCategory::SemverCompatible;
- } else {
- return UpdateCategory::SemverIncompatible;
- }
- }
-
- // Fallback: parse failed — compare major versions
- let current_major = extract_major(current_normalized);
- let latest_major = extract_major(latest_normalized);
- if current_major == latest_major {
- UpdateCategory::SemverCompatible
- } else {
- UpdateCategory::SemverIncompatible
- }
-}
-
-/// Extract the major version number from a normalized version string like "1.2.3.0".
-fn extract_major(version_normalized: &str) -> u64 {
- let base = if let Some(pos) = version_normalized.find('-') {
- &version_normalized[..pos]
- } else {
- version_normalized
- };
- base.split('.')
- .next()
- .and_then(|p| p.parse().ok())
- .unwrap_or(0)
-}
-
-/// Extract the minor version number from a normalized version string like "1.2.3.0".
-fn extract_minor(version_normalized: &str) -> u64 {
- let base = if let Some(pos) = version_normalized.find('-') {
- &version_normalized[..pos]
- } else {
- version_normalized
- };
- base.split('.')
- .nth(1)
- .and_then(|p| p.parse().ok())
- .unwrap_or(0)
-}
-
-/// Extract the patch version number from a normalized version string like "1.2.3.0".
-fn extract_patch(version_normalized: &str) -> u64 {
- let base = if let Some(pos) = version_normalized.find('-') {
- &version_normalized[..pos]
- } else {
- version_normalized
- };
- base.split('.')
- .nth(2)
- .and_then(|p| p.parse().ok())
- .unwrap_or(0)
-}
-
-/// Check whether a version change passes the --major-only/--minor-only/--patch-only filter.
-///
-/// Returns true if the version change matches the requested level.
-fn passes_level_filter(args: &OutdatedArgs, current: &str, latest: &str) -> bool {
- let cur_major = extract_major(current);
- let lat_major = extract_major(latest);
- let cur_minor = extract_minor(current);
- let lat_minor = extract_minor(latest);
- let cur_patch = extract_patch(current);
- let lat_patch = extract_patch(latest);
-
- if args.major_only {
- return lat_major > cur_major;
- }
- if args.minor_only {
- return lat_major == cur_major && lat_minor > cur_minor;
- }
- if args.patch_only {
- return lat_major == cur_major && lat_minor == cur_minor && lat_patch > cur_patch;
- }
-
- // No level filter active
- true
-}
-
-fn render_text(entries: &[OutdatedEntry], console: &mozart_core::console::Console) {
- if entries.is_empty() {
- console_writeln!(
- console,
- &console_format!("<info>All packages are up to date.</info>"),
- );
- return;
- }
-
- // Compute column widths
- let name_width = entries.iter().map(|e| e.name.len()).max().unwrap_or(0);
- let cur_width = entries
- .iter()
- .map(|e| e.current_version.len())
- .max()
- .unwrap_or(0);
- let lat_width = entries
- .iter()
- .map(|e| e.latest_version.len())
- .max()
- .unwrap_or(0);
-
- for entry in entries {
- let name_col = format!("{:<width$}", entry.name, width = name_width);
- let cur_col = format!("{:<width$}", entry.current_version, width = cur_width);
- let lat_col = format!("{:<width$}", entry.latest_version, width = lat_width);
-
- let (name_str, lat_str) = match entry.category {
- UpdateCategory::UpToDate => (
- console_format!("<info>{name_col}</info>"),
- console_format!("<info>{lat_col}</info>"),
- ),
- UpdateCategory::SemverCompatible => (
- console_format!("<highlight>{name_col}</highlight>"),
- console_format!("<highlight>{lat_col}</highlight>"),
- ),
- UpdateCategory::SemverIncompatible => (
- console_format!("<comment>{name_col}</comment>"),
- console_format!("<comment>{lat_col}</comment>"),
- ),
- };
-
- console_writeln!(
- console,
- &format!(
- "{} {} {} {}",
- name_str,
- console_format!("<comment>{cur_col}</comment>"),
- lat_str,
- entry.description
- ),
- );
- }
-}
-
-fn render_json(
- entries: &[OutdatedEntry],
- console: &mozart_core::console::Console,
-) -> anyhow::Result<()> {
- let json_entries: Vec<serde_json::Value> = entries
- .iter()
- .map(|entry| {
- let status = match entry.category {
- UpdateCategory::UpToDate => "up-to-date",
- UpdateCategory::SemverCompatible => "semver-safe-update",
- UpdateCategory::SemverIncompatible => "update-possible",
- };
- serde_json::json!({
- "name": entry.name,
- "version": entry.current_version,
- "latest": entry.latest_version,
- "latest-status": status,
- "description": entry.description,
- "direct-dependency": entry.is_direct,
- })
- })
- .collect();
-
- let output = serde_json::json!({ "installed": json_entries });
- console_writeln!(console, &serde_json::to_string_pretty(&output)?);
- Ok(())
-}
-
-/// Returns true if the given package name is a platform package (php, ext-*, etc.).
-fn is_platform_package(name: &str) -> bool {
- let lower = name.to_lowercase();
- lower == "php"
- || lower.starts_with("ext-")
- || lower.starts_with("lib-")
- || lower == "php-64bit"
- || lower == "php-ipv6"
- || lower == "php-zts"
- || lower == "php-debug"
- || lower == "composer-plugin-api"
- || lower == "composer-runtime-api"
-}
-
-/// Simple version normalizer fallback when `version_normalized` is absent.
-/// Strips leading 'v' and appends '.0' segments to reach 4 parts.
-fn normalize_version_simple(version: &str) -> String {
- let v = version.strip_prefix('v').unwrap_or(version);
- // Split off pre-release suffix
- let (base, suffix) = if let Some(pos) = v.find('-') {
- (&v[..pos], Some(&v[pos..]))
- } else {
- (v, None)
- };
- let parts: Vec<&str> = base.split('.').collect();
- let mut segments: Vec<String> = parts.iter().take(4).map(|p| p.to_string()).collect();
- while segments.len() < 4 {
- segments.push("0".to_string());
- }
- let mut result = segments.join(".");
- if let Some(suf) = suffix {
- result.push_str(suf);
- }
- result
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_classify_up_to_date_equal() {
- let cat = classify_update("1.2.3.0", "1.2.3.0");
- assert_eq!(cat, UpdateCategory::UpToDate);
- }
-
- #[test]
- fn test_classify_up_to_date_latest_older() {
- let cat = classify_update("2.0.0.0", "1.5.0.0");
- assert_eq!(cat, UpdateCategory::UpToDate);
- }
-
- #[test]
- fn test_classify_semver_compatible_minor_update() {
- // Current 1.2.0, latest 1.3.0 — ^1.2.0 allows 1.3.0
- let cat = classify_update("1.2.0.0", "1.3.0.0");
- assert_eq!(cat, UpdateCategory::SemverCompatible);
- }
-
- #[test]
- fn test_classify_semver_incompatible_major_bump() {
- // Current 1.2.0, latest 2.0.0 — ^1.2.0 does not allow 2.0.0
- let cat = classify_update("1.2.0.0", "2.0.0.0");
- assert_eq!(cat, UpdateCategory::SemverIncompatible);
- }
-
- #[test]
- fn test_classify_semver_compatible_same_major() {
- // Same major, minor bump → semver-safe under ^
- let cat = classify_update("1.2.0.0", "1.5.0.0");
- assert_eq!(cat, UpdateCategory::SemverCompatible);
- }
-
- #[test]
- fn test_classify_semver_incompatible_different_major() {
- // Different major → not semver-safe under ^
- let cat = classify_update("1.9.0.0", "2.0.0.0");
- assert_eq!(cat, UpdateCategory::SemverIncompatible);
- }
-
- #[test]
- fn test_classify_semver_compatible_patch_update() {
- // Patch bump → SemverCompatible
- let cat = classify_update("1.2.3.0", "1.2.4.0");
- assert_eq!(cat, UpdateCategory::SemverCompatible);
- }
-
- fn make_args_with_filter(major: bool, minor: bool, patch: bool) -> OutdatedArgs {
- OutdatedArgs {
- package: None,
- outdated: false,
- all: false,
- locked: false,
- direct: false,
- strict: false,
- major_only: major,
- minor_only: minor,
- patch_only: patch,
- sort_by_age: false,
- format: None,
- ignore: vec![],
- no_dev: false,
- ignore_platform_req: vec![],
- ignore_platform_reqs: false,
- }
- }
-
- #[test]
- fn test_passes_level_filter_no_filter() {
- let args = make_args_with_filter(false, false, false);
- assert!(passes_level_filter(&args, "1.0.0.0", "2.0.0.0"));
- assert!(passes_level_filter(&args, "1.0.0.0", "1.1.0.0"));
- assert!(passes_level_filter(&args, "1.0.0.0", "1.0.1.0"));
- }
-
- #[test]
- fn test_passes_level_filter_major_only() {
- let args = make_args_with_filter(true, false, false);
- // Major bump: 1 → 2
- assert!(passes_level_filter(&args, "1.0.0.0", "2.0.0.0"));
- // Minor bump: same major
- assert!(!passes_level_filter(&args, "1.0.0.0", "1.1.0.0"));
- // Patch bump: same major
- assert!(!passes_level_filter(&args, "1.0.0.0", "1.0.1.0"));
- }
-
- #[test]
- fn test_passes_level_filter_minor_only() {
- let args = make_args_with_filter(false, true, false);
- // Major bump: different major, not a minor-only bump
- assert!(!passes_level_filter(&args, "1.0.0.0", "2.0.0.0"));
- // Minor bump: same major, different minor
- assert!(passes_level_filter(&args, "1.0.0.0", "1.1.0.0"));
- // Patch bump: same major+minor
- assert!(!passes_level_filter(&args, "1.0.0.0", "1.0.1.0"));
- }
-
- #[test]
- fn test_passes_level_filter_patch_only() {
- let args = make_args_with_filter(false, false, true);
- // Major bump
- assert!(!passes_level_filter(&args, "1.0.0.0", "2.0.0.0"));
- // Minor bump
- assert!(!passes_level_filter(&args, "1.0.0.0", "1.1.0.0"));
- // Patch bump: same major+minor, different patch
- assert!(passes_level_filter(&args, "1.0.0.0", "1.0.1.0"));
- // Patch same: not a bump
- assert!(!passes_level_filter(&args, "1.0.1.0", "1.0.1.0"));
- }
-
- #[test]
- fn test_normalize_version_simple_short() {
- assert_eq!(normalize_version_simple("1.2"), "1.2.0.0");
- }
-
- #[test]
- fn test_normalize_version_simple_three_parts() {
- assert_eq!(normalize_version_simple("1.2.3"), "1.2.3.0");
- }
-
- #[test]
- fn test_normalize_version_simple_four_parts() {
- assert_eq!(normalize_version_simple("1.2.3.4"), "1.2.3.4");
- }
-
- #[test]
- fn test_normalize_version_simple_v_prefix() {
- assert_eq!(normalize_version_simple("v1.2.3"), "1.2.3.0");
- }
-
- #[test]
- fn test_normalize_version_simple_with_prerelease() {
- assert_eq!(normalize_version_simple("1.2.3-beta1"), "1.2.3.0-beta1");
- }
-
- #[test]
- fn test_extract_major() {
- assert_eq!(extract_major("2.3.4.0"), 2);
- assert_eq!(extract_major("0.1.2.0"), 0);
- assert_eq!(extract_major("2.3.4.0-beta1"), 2);
- }
-
- #[test]
- fn test_extract_minor() {
- assert_eq!(extract_minor("2.3.4.0"), 3);
- assert_eq!(extract_minor("1.0.0.0"), 0);
- }
-
- #[test]
- fn test_extract_patch() {
- assert_eq!(extract_patch("2.3.4.0"), 4);
- assert_eq!(extract_patch("1.2.0.0"), 0);
- }
-
- #[test]
- fn test_is_platform_package() {
- assert!(is_platform_package("php"));
- assert!(is_platform_package("ext-json"));
- assert!(is_platform_package("lib-pcre"));
- assert!(is_platform_package("composer-plugin-api"));
- assert!(!is_platform_package("monolog/monolog"));
- assert!(!is_platform_package("psr/log"));
- }
-
- fn test_console() -> mozart_core::console::Console {
- mozart_core::console::Console {
- interactive: false,
- verbosity: mozart_core::console::Verbosity::Normal,
- decorated: false,
- }
- }
-
- #[test]
- fn test_render_json_empty() {
- render_json(&[], &test_console()).unwrap();
- }
-
- #[test]
- fn test_render_json_with_entries() {
- let entries = vec![
- OutdatedEntry {
- name: "monolog/monolog".to_string(),
- current_version: "3.0.0".to_string(),
- latest_version: "3.8.0".to_string(),
- description: "A logging library".to_string(),
- category: UpdateCategory::SemverCompatible,
- is_direct: true,
- },
- OutdatedEntry {
- name: "psr/log".to_string(),
- current_version: "2.0.0".to_string(),
- latest_version: "3.0.0".to_string(),
- description: "PSR-3 logging interface".to_string(),
- category: UpdateCategory::SemverIncompatible,
- is_direct: false,
- },
- ];
- render_json(&entries, &test_console()).unwrap();
- }
+ super::show::execute(&show_args, cli, console).await
}