Skip to content

feat(cli): add search and list commands - #2154

Merged
netomi merged 3 commits into
mainfrom
feat/cli-search
Sep 4, 2026
Merged

feat(cli): add search and list commands#2154
netomi merged 3 commits into
mainfrom
feat/cli-search

Conversation

@netomi

@netomi netomi commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Completes the discovery half of the CLI. ovsx show (#2153) inspects an extension you can already name; there was no way to find out what a registry actually holds — get --metadata needs an identifier you already have, which is the gap that made writing the deletion guide for #1620 awkward.

Stacked on #2153. The base is feat/cli-show, so the diff here is only this change; GitHub retargets it to main once #2153 merges. Reviewing that one first will be easier.

$ ovsx search java --size 5 --sort-by downloadCount --sort-order desc
  Extension                       Version          Downloads   Rating  Description
  redhat.java                     1.56.2026090208  40,086,502  5.0     Java Linting, Intellisense, formatting, refactoring, Maven/…
  vscjava.vscode-java-dependency  0.27.6           39,159,807  -       Manage Java projects in Visual Studio Code
  vscjava.vscode-maven            0.45.3           27,826,245  -       Manage Maven projects, execute goals, generate project from…
  vscjava.vscode-gradle           3.18.0           26,603,436  3.0     Manage Gradle Projects, run Gradle tasks and provide better…
  vscjava.vscode-java-debug       0.59.0           25,681,912  5.0     A lightweight Java debugger for Visual Studio Code

Showing 1-5 of 408.
Pass --offset 5 for the next page.
$ ovsx list redhat
redhat (verified) - 45 extensions

  redhat.abbenay-provider
  redhat.ansible
  redhat.apache-camel-extension-pack
  ...

search

Queries /api/-/search, the endpoint built for this, which returns a summary shape rather than whole extension records. Takes --category, --target, --sort-by, --sort-order, --size and --offset, and reports where the page sits in the result set so paging is discoverable instead of guesswork.

Two deliberate details:

  • The text is optional. Browsing a category on its own is a reasonable thing to want, so ovsx search --category Snippets works.
  • Sort keys and orders are validated locally. The registry answers a bad key with a bare {"error": "sortBy parameter must be ..."} and a 400; catching it in the CLI gives a better message and saves the round trip. Worth knowing while reviewing: the accepted key is downloadCount, not downloads — I got that wrong myself when first poking at the API by hand.

list

Prints the extensions in a namespace from /api/{namespace} — one small response, no paging needed (45 extensions for redhat, ~2KB). Sorted by name, so the output is stable enough to diff and pipe rather than depending on the response object's key order.

This is the question an operator of a private registry actually asks — what is published here — and it needs no token, so it works on a deployment with no login provider configured, which is the #1620 scenario.

Why neither uses /api/v2/-/query

Same reasoning as the endpoint change in #2153, in both directions. The search endpoint already returns exactly the fields a result table needs. And a namespace listing via the query endpoint is 477KB for redhat against ~2KB from /api/{namespace}, because it repeats every extension-level field — files, tags, description, publisher — on each version/target-platform row.

The trade-off is that list prints names only. Anything more per extension would mean either that 477KB response or a request per extension; show covers the detail for one.

Shared formatting

The table helpers show introduced move into a table module, so the three commands align columns, format counts and truncate the same way instead of each carrying a copy. That unification is the reason this is stacked rather than branched from main — done separately, both PRs would have landed their own copy.

Testing

18 new cases across search.spec.ts and list.spec.ts against local HTTP stubs, following the existing unpublish.spec.ts pattern: filter and paging pass-through, the page-position line, empty results, deprecated marking, local sort validation, registry-reported errors, --json, name sorting and count pluralisation. Full CLI suite green at 89 tests, tsc and eslint clean.

Both commands were also run against open-vsx.org — the output above is real, not mocked up.

Refs #2149, #1620

🤖 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

It introduces a small but real edge-case bug in the new shared truncate helper and some newly added exported APIs/types should be tightened/renamed for clarity and contract accuracy.

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

Pull request overview

Adds discovery-oriented CLI capabilities to complement ovsx show by introducing ovsx search (paged full-text search via /api/-/search) and ovsx list (namespace inventory via /api/{namespace}), while consolidating shared tabular formatting helpers into a reusable module.

Changes:

  • Add ovsx search command with local validation for sort keys/orders, paging output, and --json.
  • Add ovsx list command to print a stable, sorted list of extensions in a namespace, with --json.
  • Extract shared formatting utilities (aligned tables, count/number formatting, truncation) into cli/src/table.ts and reuse them from show.
File summaries
File Description
cli/src/main.ts Wires new search and list commands into the CLI, including option parsing.
cli/src/search.ts Implements search request/validation and result table + paging footer output.
cli/src/search-options.ts Defines typed options contract for the search command.
cli/src/list.ts Implements namespace listing output and stable alphabetical ordering.
cli/src/list-options.ts Defines typed options contract for the list command.
cli/src/registry.ts Adds registry client methods/types for /api/-/search and /api/{namespace}.
cli/src/table.ts New shared table/number/truncation formatting helpers used across commands.
cli/src/show.ts Switches show to use shared table/count/number helpers instead of local copies.
cli/src/util.ts Adds a shared integer option parser for commander-based numeric flags.
cli/test/unit/search.spec.ts Unit tests for search output, paging, validation, error surfacing, and --json.
cli/test/unit/list.spec.ts Unit tests for list output, sorting, pluralization, error surfacing, and --json.
Review details

Suppressed comments (3)

cli/src/registry.ts:471

  • In the server API schema, SearchEntry.deprecated is always present (primitive boolean in SearchEntryJson), so it shouldn’t be optional in the CLI type definition.
    deprecated?: boolean;

cli/src/registry.ts:477

  • SearchResult.extensions is required/non-null in the server schema (@NotNull in SearchResultJson), so making it optional in the CLI type definition reduces type safety for consumers.
    extensions?: SearchEntry[];

cli/src/registry.ts:482

  • In the server API schema, Namespace.verified is required (@NotNull in NamespaceJson), so this should be non-optional in the CLI type definition to match the contract.
    verified?: boolean;
  • Files reviewed: 11/11 changed files
  • Comments generated: 3
  • 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/registry.ts
Comment thread cli/src/table.ts Outdated
Comment thread cli/src/util.ts Outdated
Base automatically changed from feat/cli-show to main September 4, 2026 11:38
Completes the discovery half of the CLI. 'ovsx show' inspects an
extension you can already name; until now there was no way to find out
what a registry holds - 'get --metadata' needs an identifier you
already have, which is the gap that made writing the deletion guide for
#1620 awkward.

'ovsx search [text]' queries /api/-/search, the endpoint built for
exactly this, and prints one row per result with the identifier,
version, downloads, rating and a truncated description. It takes
--category, --target, --sort-by, --sort-order, --size and --offset, and
reports where the page sits in the result set so paging is discoverable
rather than guesswork. The text is optional, since browsing a category
on its own is a reasonable thing to want. Sort keys and orders are
checked locally, because the registry answers a bad one with a bare
400 that says little.

'ovsx list <namespace>' prints the extensions in a namespace from
/api/{namespace}, sorted by name so the output is stable enough to diff
and pipe rather than depending on the response's key order. That is the
question an operator of a private registry actually asks - "what is
published here" - and it needs no token, so it works on a deployment
with no login provider configured.

Both take --json.

Neither uses /api/v2/-/query. The search endpoint returns a summary
shape built for result lists, and a namespace listing from
/api/{namespace} is a single small response, where the same namespace
via the query endpoint is 477KB because it repeats every extension-level
field on each version/target-platform row.

The table formatting show introduced moves to a shared table module, so
the three commands align columns and format counts the same way rather
than each carrying a copy.

Refs #2149, #1620

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
netomi and others added 2 commits September 4, 2026 13:47
…it takes

Two review points from Copilot on #2154.

truncate returned an ellipsis for a max of 0, one character wider than
the budget it was handed - the shape of bug that makes a table wrap. No
caller can reach it today (the only call site passes a width of 60), but
table.ts was extracted precisely to be shared, so it should hold at its
boundaries. Covered by a spec of its own: the helpers had no direct
tests, only whatever width the calling command happened to use.

parsePositiveInt accepted 0, as its own docstring and error message
said. Zero is the right answer for --offset, whose default it is, so the
name was the wrong half; renamed to parseNonNegativeInt. Free to do now
because the export is new in this PR and has no consumers outside it.

Left alone: the suggestion to make SearchEntry.verified/deprecated,
SearchResult.extensions and Namespace.verified required to match the
server's @NotNull. Those classes are @JsonInclude(NON_NULL), so a null
boxed Boolean is omitted rather than serialised, and the search endpoint
returns its errors *as* a SearchResultJson with no extensions at all -
which is why the type extends Response and why the server's own merge
loop null-checks getExtensions() twice. Requiring them would be a claim
nothing validates at runtime, and would make `result.extensions ?? []`
look like dead code.

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 8db032c into main Sep 4, 2026
5 checks passed
@netomi
netomi deleted the feat/cli-search branch September 4, 2026 11:57
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.

2 participants