Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support json output for outdated command #27271

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the other --json flags, we've wanted to require people to specify the version of json output that they want similar to what Rust requires. This allows us to break the output over time while maintaining backwards compatibility.

If adding this flag, we should probably proactively do that here, but I'm not sure what that would look like. There's an open issue for this somewhere I think, but it's hard to search for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that it would be specified on the cmd line, something like --json v1?

I just checked what npm outdated --json does and here is an example of the output it uses:

{
  "@types/node": {
    "current": "20.11.30",
    "wanted": "20.17.9",
    "latest": "22.10.1",
    "dependent": "deno-astro-adapter",
    "location": "/Users/irbull/git/deno/deno-astro-adapter/node_modules/@types/node"
  },
...

Do you think we should align with that format? It's a bit different, particularly that the package is the key.

)
})
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -11641,23 +11667,43 @@ 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,
},
),
(
svec!["--recursive"],
OutdatedFlags {
filters: vec![],
kind: OutdatedKind::PrintOutdated { compatible: false },
kind: OutdatedKind::PrintOutdated {
compatible: false,
output_fmt: OutdatedOutputFmt::Table,
},
recursive: true,
},
),
(
svec!["--recursive", "--compatible"],
OutdatedFlags {
filters: vec![],
kind: OutdatedKind::PrintOutdated { compatible: true },
kind: OutdatedKind::PrintOutdated {
compatible: true,
output_fmt: OutdatedOutputFmt::Table,
},
recursive: true,
},
),
Expand Down Expand Up @@ -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,
},
),
Expand Down
3 changes: 2 additions & 1 deletion cli/tools/registry/pm/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
36 changes: 32 additions & 4 deletions cli/tools/registry/pm/outdated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<S>(kind: &DepKind, s: S) -> Result<S::Ok, S::Error>
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"];
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this feature is added, we should use object notation and then include a version number (ex. "1"), similar to the other --json output. That allows adding new properties in the future in a backwards compatible way and also attaches a version to the object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. Let's hammer out the format first (see above) and then we can add a version field. Typically semantic version I guess (like 1.0.0)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a single integer is fine. Additions fall under the same version, but breaking changes cause a version bump. Otherwise people need to parse the version with something like std/semver to see if they can understand it rather than just doing an equality check.

println!("{json}");
}
}
}

Ok(())
Expand Down Expand Up @@ -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)?;
}
}

Expand Down
Loading