diff --git a/cli/args/flags.rs b/cli/args/flags.rs index cdeaa1b335682c..99db9428138c01 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -477,10 +477,21 @@ pub enum DenoSubcommand { Help(HelpFlags), } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum OutdatedOutputFmt { + Table, + Json, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum OutdatedKind { - Update { latest: bool }, - PrintOutdated { compatible: bool }, + Update { + latest: bool, + }, + PrintOutdated { + compatible: bool, + output_fmt: OutdatedOutputFmt, + }, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -2725,7 +2736,13 @@ Specific version requirements to update to can be specified: .long("recursive") .short('r') .action(ArgAction::SetTrue) - .help("include all workspace members"), + .help("Include all workspace members"), + ) + .arg( + Arg::new("json") + .long("json") + .action(ArgAction::SetTrue) + .help("Output outdated packages in JSON format") ) }) } @@ -4495,7 +4512,16 @@ fn outdated_parse( OutdatedKind::Update { latest } } else { let compatible = matches.get_flag("compatible"); - OutdatedKind::PrintOutdated { compatible } + let json = matches.get_flag("json"); + let output_fmt = if json { + OutdatedOutputFmt::Json + } else { + OutdatedOutputFmt::Table + }; + OutdatedKind::PrintOutdated { + compatible, + output_fmt, + } }; flags.subcommand = DenoSubcommand::Outdated(OutdatedFlags { filters, @@ -11641,7 +11667,21 @@ Usage: deno repl [OPTIONS] [-- [ARGS]...]\n" svec![], OutdatedFlags { filters: vec![], - kind: OutdatedKind::PrintOutdated { compatible: false }, + kind: OutdatedKind::PrintOutdated { + compatible: false, + output_fmt: OutdatedOutputFmt::Table, + }, + recursive: false, + }, + ), + ( + svec!["--json"], + OutdatedFlags { + filters: vec![], + kind: OutdatedKind::PrintOutdated { + compatible: false, + output_fmt: OutdatedOutputFmt::Json, + }, recursive: false, }, ), @@ -11649,7 +11689,10 @@ Usage: deno repl [OPTIONS] [-- [ARGS]...]\n" svec!["--recursive"], OutdatedFlags { filters: vec![], - kind: OutdatedKind::PrintOutdated { compatible: false }, + kind: OutdatedKind::PrintOutdated { + compatible: false, + output_fmt: OutdatedOutputFmt::Table, + }, recursive: true, }, ), @@ -11657,7 +11700,10 @@ Usage: deno repl [OPTIONS] [-- [ARGS]...]\n" svec!["--recursive", "--compatible"], OutdatedFlags { filters: vec![], - kind: OutdatedKind::PrintOutdated { compatible: true }, + kind: OutdatedKind::PrintOutdated { + compatible: true, + output_fmt: OutdatedOutputFmt::Table, + }, recursive: true, }, ), @@ -11697,7 +11743,10 @@ Usage: deno repl [OPTIONS] [-- [ARGS]...]\n" svec!["--latest"], OutdatedFlags { filters: svec![], - kind: OutdatedKind::PrintOutdated { compatible: false }, + kind: OutdatedKind::PrintOutdated { + compatible: false, + output_fmt: OutdatedOutputFmt::Table, + }, recursive: false, }, ), diff --git a/cli/tools/registry/pm/deps.rs b/cli/tools/registry/pm/deps.rs index d82e9954cd458a..20aff4151cc451 100644 --- a/cli/tools/registry/pm/deps.rs +++ b/cli/tools/registry/pm/deps.rs @@ -30,6 +30,7 @@ use deno_semver::VersionReq; use import_map::ImportMap; use import_map::ImportMapWithDiagnostics; use import_map::SpecifierMapEntry; +use serde::Serialize; use tokio::sync::Semaphore; use crate::args::CliLockfile; @@ -112,7 +113,7 @@ impl std::fmt::Debug for DepLocation { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] pub enum DepKind { Jsr, Npm, diff --git a/cli/tools/registry/pm/outdated.rs b/cli/tools/registry/pm/outdated.rs index 2a29014267d67e..2b054f53126219 100644 --- a/cli/tools/registry/pm/outdated.rs +++ b/cli/tools/registry/pm/outdated.rs @@ -4,15 +4,19 @@ use std::collections::HashSet; use std::sync::Arc; use deno_core::error::AnyError; +use deno_core::serde_json; use deno_semver::package::PackageNv; use deno_semver::package::PackageReq; use deno_semver::VersionReq; use deno_terminal::colors; +use serde::Serialize; +use serde::Serializer; use crate::args::CacheSetting; use crate::args::CliOptions; use crate::args::Flags; use crate::args::OutdatedFlags; +use crate::args::OutdatedOutputFmt; use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::jsr::JsrFetchResolver; @@ -24,15 +28,29 @@ use super::deps::DepManager; use super::deps::DepManagerArgs; use super::deps::PackageLatestVersion; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)] struct OutdatedPackage { + #[serde(rename = "specifier", serialize_with = "lowercase_serializer")] kind: DepKind, + #[serde(rename = "latest")] latest: String, + #[serde(rename = "update")] semver_compatible: String, current: String, + #[serde(rename = "package")] name: String, } +fn lowercase_serializer(kind: &DepKind, s: S) -> Result +where + S: Serializer, +{ + match kind { + DepKind::Npm => s.serialize_str("npm"), + DepKind::Jsr => s.serialize_str("jsr"), + } +} + #[allow(clippy::print_stdout)] fn print_outdated_table(packages: &[OutdatedPackage]) { const HEADINGS: &[&str] = &["Package", "Current", "Update", "Latest"]; @@ -103,6 +121,7 @@ fn print_outdated_table(packages: &[OutdatedPackage]) { fn print_outdated( deps: &mut DepManager, compatible: bool, + output_fmt: OutdatedOutputFmt, ) -> Result<(), AnyError> { let mut outdated = Vec::new(); let mut seen = std::collections::BTreeSet::new(); @@ -147,7 +166,13 @@ fn print_outdated( if !outdated.is_empty() { outdated.sort(); - print_outdated_table(&outdated); + match output_fmt { + OutdatedOutputFmt::Table => print_outdated_table(&outdated), + OutdatedOutputFmt::Json => { + let json = serde_json::to_string_pretty(&outdated)?; + println!("{json}"); + } + } } Ok(()) @@ -214,8 +239,11 @@ pub async fn outdated( crate::args::OutdatedKind::Update { latest } => { update(deps, latest, &filter_set, flags).await?; } - crate::args::OutdatedKind::PrintOutdated { compatible } => { - print_outdated(&mut deps, compatible)?; + crate::args::OutdatedKind::PrintOutdated { + compatible, + output_fmt, + } => { + print_outdated(&mut deps, compatible, output_fmt)?; } }