Skip to content

feat(cli): add a show command - #2153

Merged
netomi merged 5 commits into
mainfrom
feat/cli-show
Sep 4, 2026
Merged

feat(cli): add a show command#2153
netomi merged 5 commits into
mainfrom
feat/cli-show

Conversation

@netomi

@netomi netomi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #2149. Follows vsce show closely enough that the habit transfers, then leans into what an Open VSX compatible registry knows and the Marketplace has no equivalent for.

$ ovsx show redhat.java
Language Support for Java(TM) by Red Hat
  Red Hat (verified publisher)
  8,421,337 downloads  4.3/5 from 142 reviews

  Java Linting, Intellisense, formatting, refactoring, Maven/Gradle support and more...

Version History:
  Version          Target Platforms
  1.56.2026090208  universal, darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64
  1.56.2026082811  universal, darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64
  1.56.2026082608  universal, darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64
  ... and 9 more (pass --all-versions to list them)

Categories:
  Programming Languages, Linters, Formatters

Tags:
  java, maven, gradle

More Info:
  Unique Identifier  redhat.java
  Version            1.42.0
  Version Aliases    latest
  Target Platform    universal
  Last Updated       2026-08-01T10:00:00Z
  Published By       redhat-bot
  License            EPL-2.0
  Homepage           https://projects.eclipse.org/projects/eclipse.jdt.ls
  Repository         https://github.com/redhat-developer/vscode-java
  Sponsor            https://github.com/sponsors/redhat
  Engines            vscode ^1.90.0

Registry:
  Verified Publisher  yes
  Trusted Publishing  yes
  Pre-Release         no
  Extension Kind      workspace
  Localized           en, de, ja
  Dependencies        vscjava.vscode-java-debug

Statistics:
  Downloads       8,421,337
  Average Rating  4.3/5
  Reviews         142

Parity

The sections and their order follow vsce show, including filtering the internal __-prefixed tags (the web UI hides those too). --json prints the raw metadata.

Identifiers accept namespace.extension@version the way vsce does — an exact version or the registry's own latest / pre-release aliases — plus -t/--target, since Open VSX is target-platform aware in a way vsce's show isn't. No token required: both endpoints it uses are public, so this also works on a private registry with no login provider configured.

Beyond parity

The Registry: block is kept separate rather than mixed into More Info, so it's obvious which fields are registry specific, and it's omitted entirely when none apply: verified publisher, trusted publishing, pre-release status, extension kind, localized languages, dependencies and bundled extensions.

Deprecation is promoted above the metadata instead of being a table row, naming the replacement where the registry supplies one, since it changes whether you should install the extension at all:

  ! Deprecated - superseded by Java Next

A disputed namespace and an extension the registry won't serve for download are surfaced the same way.

Why there is a second request

The version history needs one extra request. allVersions on the metadata response carries version numbers and links only, and allTargetPlatformVersions is populated on the user and admin endpoints rather than the public one, so the table comes from /api/{namespace}/{extension}/version-references - one page for the default table, paging to the end for --all-versions. Its one-row-per-target-platform results are collapsed into a row per version, which is also what fills the Targets column. --target scopes that listing too, via /api/{namespace}/{extension}/{target}/version-references.

That listing is best-effort. A registry that doesn't serve it, or errors on it, still gets the whole summary minus the history table; there's a test for that.

The table is Version and Target Platforms only. It initially also carried "Last Updated" and a pre-release marker, sourced from /api/v2/-/query?includeAllVersions=true, but that was the wrong source: 267KB against 11KB for the same extension, and capped at 100 of 3817 rows, so --all-versions was quietly incomplete - measurements are in the comment below. Neither version-references nor /versions reports a timestamp or the pre-release flag, so those two columns are gone for now. Adding timestamp and preRelease to VersionReferenceJson would bring them back at no bandwidth cost, which seems better than making every client pull the query endpoint - happy to put that up separately.

Versions are sorted newest first here rather than trusting the listing's order, which also means the cap drops the oldest rather than arbitrary ones. Sorting uses semver.valid and not coerce: coerce drops prerelease identifiers, so 1.2.0-alpha.1 would compare equal to 1.2.0. Versions that aren't valid semver (reachable via mirroring) sort after those that are. --all-versions lists everything, and the number left out is reported rather than truncated silently.

Testing

13 new cases in cli/test/unit/show.spec.ts against a local HTTP stub, following unpublish.spec.ts. Full CLI suite green (70 tests), tsc and eslint clean.

Worth noting that two bugs in this PR were found by running the command rather than by the tests — the unsorted version history above, and Downloads printing unformatted while the header line used thousands separators. Both are now covered, the sort by a case using 1.9.0/1.10.0/1.2.0 that a string sort would get wrong.

One thing to decide

--json overlaps with the existing get --metadata, which already prints the same JSON. I kept it for vsce parity and made the content identical rather than subtly different, but if you would rather there were only one way to get it, it is easy to drop.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Version-history sorting can be incorrect for prerelease SemVer due to semver.coerce() (and there’s also a user-facing identifier error message mismatch), which should be fixed before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a new ovsx show CLI command to display extension metadata in a vsce show-like layout, including Open VSX–specific registry fields and an optional version-history table sourced from the v2 query endpoint.

Changes:

  • Introduces cli/src/show.ts with formatted output, notices (deprecation/disputed namespace/not downloadable), and version history aggregation.
  • Extends the CLI registry client to support version-specific metadata fetches and a best-effort /api/v2/-/query?includeAllVersions=true call.
  • Adds a comprehensive unit test suite for show using a local HTTP stub.
File summaries
File Description
cli/src/show.ts Implements the show command output, including version history sorting/capping and registry-specific sections.
cli/src/show-options.ts Defines ShowOptions for show command flags (--json, --all-versions, --target).
cli/src/registry.ts Adds queryAllVersions and extends getMetadata to optionally request a specific version.
cli/src/main.ts Wires the new show subcommand into the CLI entrypoint.
cli/test/unit/show.spec.ts Adds unit tests for formatting, filtering internal tags, version history behavior, and best-effort query failure handling.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cli/src/show.ts
Comment thread cli/src/show.ts Outdated
@netomi

netomi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Switched the version history off the query endpoint in 9994223 — you were right that it was the wrong source, and measuring it made that clear.

For redhat.java on open-vsx.org:

endpoint payload rows returned
/api/{ns}/{ext}/versions 1.4 KB 18 of 654 versions
/api/{ns}/{ext}/version-references 11 KB 18 of 3817 pairs
/api/v2/-/query?includeAllVersions=true 267 KB 100 of 3817 pairs

The query endpoint repeats every extension-level field — files, tags, description, publisher — on each of the 3817 version/target-platform rows, which is where the 267KB goes. And it pages at 100, so --all-versions was silently listing at most 100 of them; the option was not doing what it said.

version-references returns version and target platform, newest first, with size/offset paging, so the listing is now complete: one page for the default table, paging to the end for --all-versions. One subtlety worth flagging for review — totalSize counts version/target-platform pairs, not versions, so the paging loop runs off what has actually been returned rather than comparing against a version count.

The cost is the two columns that endpoint cannot supply. Neither version-references nor /versions carries a timestamp or the pre-release flag, so the table is now:

Version History:
  Version          Target Platforms
  1.56.2026090208  universal, darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64
  1.56.2026082811  universal, darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64
  ...
  ... and 9 more (pass --all-versions to list them)

Adding timestamp and preRelease to VersionReferenceJson would restore "Last Updated" and the pre-release marker at essentially no bandwidth cost, and seems the better fix than making every client pull the query endpoint for them. Happy to put that up as a small server-side PR if you agree — #1692 is already extending that endpoint with a preReleases parameter, so it looks like the intended home for pre-release awareness.

Unrelated, but it showed up while testing against the live registry: redhat.java reports Version Aliases latest, pre-release on a single version, which is #1675 visible in production.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are confirmed functional correctness issues (semver sorting behavior, potential NaN output, and --target not applying to version history) plus a notable mismatch with the described version-history parity.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

cli/src/show.ts:328

  • formatRating calls Number(extension.reviewCount); if reviewCount is missing in the registry response this becomes NaN and prints "NaN reviews". formatCount already handles undefined safely, so pass the value through directly.
    cli/test/unit/show.spec.ts:113
  • If the CLI accepts both namespace.extension and namespace/extension, this assertion should be updated alongside the CLI error message; otherwise the test will either fail unnecessarily or allow a misleading message to persist.

cli/src/show.ts:53

  • The error message says identifiers must be namespace.extension, but matchExtensionId accepts both namespace.extension and namespace/extension (and ShowOptions docs mention both). This makes the thrown message misleading for users.
    const match = matchExtensionId(id);
    if (!match) {
        throw new Error('The extension identifier must have the form `namespace.extension`.');
    }

cli/src/show.ts:140

  • byNewestFirst uses semver.coerce, which contradicts the comment that non‑semver sorts after valid semver (many invalid strings will be coerced) and also drops prerelease/build metadata (e.g. 1.2.0-beta coerces to 1.2.0), which can mis-order prereleases relative to releases.
function byNewestFirst(a: VersionSummary, b: VersionSummary): number {
    const left = semver.coerce(a.version);
    const right = semver.coerce(b.version);
    if (left && right) {
        return semver.rcompare(left, right);
  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread cli/src/show.ts
Comment thread cli/src/show.ts
netomi added a commit that referenced this pull request Sep 3, 2026
Review feedback on #2153, all four points valid.

byNewestFirst used semver.coerce, which drops prerelease identifiers:
1.2.0-alpha.1 coerced to 1.2.0, so a prerelease compared equal to its
release and the order was unstable. coerce also accepts input semver
itself rejects - 'v1.2' becomes 1.2.0 - which defeated the string
fallback and made the comment about non-semver sorting last untrue.
semver.valid keeps prerelease identifiers, orders them below their
release, and rejects what it should.

--target is described as scoping the report, but only the metadata
lookup honoured it; the version listing always asked for the unscoped
path. The registry serves /api/{ns}/{ext}/{target}/version-references,
so the target is passed through and the history table now matches the
rest of the output.

formatRating wrapped reviewCount in Number(), so a response without one
printed 'NaN reviews'. formatCount already handles undefined, so the
value goes through directly.

The identifier error message named only the dotted form, though
matchExtensionId accepts namespace/extension too and ShowOptions
documents both. Reworded - and the same wording in get and unpublish is
updated with it, since three commands validating identically should not
report it differently.

Each fix has a test, all three confirmed to fail with the bug put back.

Refs #2149

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@netomi

netomi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot why is this PR marked as being not ready in the stacked PR view?

Copilot AI commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@copilot why is this PR marked as being not ready in the stacked PR view?

It’s currently marked not ready because the PR is in a blocked state (mergeable_state: blocked) with an in-progress Copilot check run (copilot from workflow run 33809012338). In stacked view that shows as not ready until required checks/review state clear.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

There are a couple of concrete CLI behavior and determinism issues (notably @ empty-suffix handling and stable ordering of version target platforms) that should be addressed before approval.

Review details

Suppressed comments (4)

Previously missed (4) — in code that hasn't changed since the last review.

cli/src/show.ts:82

  • splitVersion() treats an identifier ending with @ as if no version was provided (it returns an empty string, and getMetadata() then omits the version segment). This silently ignores user input like ovsx show redhat.java@, which should be rejected as invalid.
    cli/src/main.ts:122
  • The show command help/usage only mentions the dot form (namespace.extension), but the CLI now explicitly supports namespace/extension too (and the error messages say so). Updating the help text avoids misleading users.
    cli/src/show.ts:131
  • The order of targetPlatforms in the version history output depends on the registry response order. Since the API doesn’t promise a stable ordering, this can make CLI output noisy and potentially flaky across registries/runs. Sorting the collected targetPlatforms per version makes the output deterministic.
    cli/test/unit/show.spec.ts:116
  • There’s no unit test for the edge case namespace.extension@ (empty version suffix). Since splitVersion() currently treats that as “no version”, adding a test will prevent regressions once the empty-suffix validation is fixed.
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

netomi added a commit that referenced this pull request Sep 4, 2026
Review feedback on #2153, all four points valid.

byNewestFirst used semver.coerce, which drops prerelease identifiers:
1.2.0-alpha.1 coerced to 1.2.0, so a prerelease compared equal to its
release and the order was unstable. coerce also accepts input semver
itself rejects - 'v1.2' becomes 1.2.0 - which defeated the string
fallback and made the comment about non-semver sorting last untrue.
semver.valid keeps prerelease identifiers, orders them below their
release, and rejects what it should.

--target is described as scoping the report, but only the metadata
lookup honoured it; the version listing always asked for the unscoped
path. The registry serves /api/{ns}/{ext}/{target}/version-references,
so the target is passed through and the history table now matches the
rest of the output.

formatRating wrapped reviewCount in Number(), so a response without one
printed 'NaN reviews'. formatCount already handles undefined, so the
value goes through directly.

The identifier error message named only the dotted form, though
matchExtensionId accepts namespace/extension too and ShowOptions
documents both. Reworded - and the same wording in get and unpublish is
updated with it, since three commands validating identically should not
report it differently.

Each fix has a test, all three confirmed to fail with the bug put back.

Refs #2149

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
netomi added a commit that referenced this pull request Sep 4, 2026
splitVersion read `namespace.extension@` as "no version given": the
trailing `@` cleared the version to an empty string, and getMetadata
appends the version segment only when it is truthy, so the request went
out without one and show reported the latest version instead.

Silent, and wrong in the case that actually produces this input - a
shell variable that did not expand, as in `ovsx show ext@$VERSION` with
VERSION unset. A script asking about one version and being told about
another is worse than an error, so this is now an error.

Reported by Copilot on #2153.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
netomi and others added 5 commits September 4, 2026 13:37
Prints an extension's metadata, following 'vsce show' closely enough
that the habit transfers: display name, publisher, downloads and
rating, description, a version history table, categories, tags (the
internal '__'-prefixed ones filtered out, as the web UI does), a
"More Info" block and statistics. '--json' prints the raw metadata.

Beyond parity, a Registry block reports what an Open VSX compatible
registry knows and the Marketplace has no equivalent for: verified
publisher, trusted publishing, pre-release status, extension kind,
localized languages, dependencies and bundled extensions. Deprecation
leads the output instead, naming the replacement extension where the
registry supplies one, since that changes whether you should install
the extension at all - as do a disputed namespace and an extension the
registry won't serve for download.

The version history needs per-version timestamps and pre-release
flags, which the metadata response doesn't carry - 'allVersions' is
version numbers and links only, and 'allTargetPlatformVersions' is
populated on the user and admin endpoints rather than the public one.
So it comes from '/api/v2/-/query?includeAllVersions=true', one request
for every version, whose one-row-per-target-platform results are
collapsed to a row per version. That query is best-effort: a registry
that doesn't serve it still gets the rest of the output.

Versions are sorted newest first here rather than trusting the query's
order, which also means the table's cap drops the oldest versions
rather than arbitrary ones. '--all-versions' lists them all, and the
count left out is reported rather than truncating silently.

Accepts 'namespace.extension@version' the way vsce does, including the
'latest' and 'pre-release' aliases, plus '--target' for a specific
target platform. No token needed - both endpoints are public.

Closes #2149

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review feedback: the history table was reading
/api/v2/-/query?includeAllVersions=true, which is by far the most
expensive way to get it. For redhat.java on open-vsx.org that response
is 267KB against 11KB for the same information from
/api/{namespace}/{extension}/version-references, because the query
repeats every extension-level field - files, tags, description,
publisher - on all 3817 version/target-platform rows.

Worse, the query pages at 100 rows, so '--all-versions' silently listed
at most 100 of those 3817 rather than all of them, which is not what
the option claims.

version-references returns version and target platform, newest first,
with size/offset paging, so the listing is now complete: one page for
the default table, paging to the end for '--all-versions'. totalSize
counts version/target-platform pairs rather than versions, so paging
runs off what has actually been returned.

The cost is the two columns that endpoint doesn't carry: neither it nor
/versions reports a timestamp or the pre-release flag, so the table is
now Version and Target Platforms. Adding those two fields to
VersionReferenceJson would restore them at no bandwidth cost, and looks
like the right fix rather than making every client pay for the query
endpoint - raised separately.

Refs #2149

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review feedback on #2153, all four points valid.

byNewestFirst used semver.coerce, which drops prerelease identifiers:
1.2.0-alpha.1 coerced to 1.2.0, so a prerelease compared equal to its
release and the order was unstable. coerce also accepts input semver
itself rejects - 'v1.2' becomes 1.2.0 - which defeated the string
fallback and made the comment about non-semver sorting last untrue.
semver.valid keeps prerelease identifiers, orders them below their
release, and rejects what it should.

--target is described as scoping the report, but only the metadata
lookup honoured it; the version listing always asked for the unscoped
path. The registry serves /api/{ns}/{ext}/{target}/version-references,
so the target is passed through and the history table now matches the
rest of the output.

formatRating wrapped reviewCount in Number(), so a response without one
printed 'NaN reviews'. formatCount already handles undefined, so the
value goes through directly.

The identifier error message named only the dotted form, though
matchExtensionId accepts namespace/extension too and ShowOptions
documents both. Reworded - and the same wording in get and unpublish is
updated with it, since three commands validating identically should not
report it differently.

Each fix has a test, all three confirmed to fail with the bug put back.

Refs #2149

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
splitVersion read `namespace.extension@` as "no version given": the
trailing `@` cleared the version to an empty string, and getMetadata
appends the version segment only when it is truthy, so the request went
out without one and show reported the latest version instead.

Silent, and wrong in the case that actually produces this input - a
shell variable that did not expand, as in `ovsx show ext@$VERSION` with
VERSION unset. A script asking about one version and being told about
another is worse than an error, so this is now an error.

Reported by Copilot on #2153.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@netomi
netomi merged commit 31d9455 into main Sep 4, 2026
5 checks passed
@netomi
netomi deleted the feat/cli-show branch September 4, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support a show command in ovsx similar to vsce

3 participants