Skip to content

feat(api): add api list command and deprecate openapi *#1070

Draft
gu-stav wants to merge 14 commits into
mainfrom
feat/api-command-list
Draft

feat(api): add api list command and deprecate openapi *#1070
gu-stav wants to merge 14 commits into
mainfrom
feat/api-command-list

Conversation

@gu-stav
Copy link
Copy Markdown
Member

@gu-stav gu-stav commented May 12, 2026

Description

Adds sanity api list for discovering Sanity's public HTTP API endpoints. Renders a flat operation table by default; --json emits one row per operation; --spec=<slug> narrows to a single spec; --web opens the HTTP Reference site.

Deprecates sanity openapi list — output shape preserved during the back-compat window, with a one-line stderr warning pointing at the canonical command.

This PR is the first in a stack:

Architecture refactors (post-review)

  • Drop revision from OpenApiSpecIndexEntry (unused).
  • Single docsUrlFor(slug) helper in docsClient.ts instead of inline template literals at every site.
  • loadOperationsIndexOrThrow() + shared DOCS_SERVICE_UNAVAILABLE constant so commands stop redefining the same try/catch + literal copy.

PR review items addressed

  • Drop unused revision field
  • Widen the per-spec try/catch in fetchAndParseEntry so one 5xx doesn't poison the whole listing
  • Test coverage: 5xx isolation, base-URL override, bypass-token header

What to review

  • packages/@sanity/cli/src/commands/api/list.ts — the new command.
  • packages/@sanity/cli/src/api/parser.tsloadOperationsIndex orchestration and parallel fetch behavior.
  • packages/@sanity/cli/src/commands/openapi/list.ts — verify the deprecation forwarder's output is byte-identical apart from the stderr warning.

Testing

10 unit tests in commands/api/__tests__/list.test.ts covering: table render, JSON output, --spec narrowing, unknown slug empty state, --web flow, network unavailable, per-spec 404 skip, per-spec 5xx skip, base-URL env override, bypass-token header.

@claude
Copy link
Copy Markdown

claude Bot commented May 12, 2026

Claude finished @gu-stav's task in 2m 24s —— View job


Review

  • Read core files
  • Read deprecation forwarder
  • Read tests + fixtures
  • Verify changeset + dependency necessity
  • Post findings

Issues

1. --method description claims "case-insensitive" but oclif's options: validates case-sensitivelypackages/@sanity/cli/src/commands/api/list.ts:51-54

method: Flags.string({
  description: 'Filter by HTTP method (case-insensitive)',
  options: [...METHODS],    // METHODS is uppercase only
}),

With options: [...], oclif rejects values that don't match exactly. --method=post will fail at parse time with "Expected --method=post to be one of: GET, …" — yet the description promises case-insensitive matching, and applyFilters calls filters.method?.toUpperCase() (list.ts:116) as if lowercase could reach it. Either remove options: and validate manually (case-insensitive), or drop the misleading description and the dead toUpperCase(). No test covers lowercase input, so this slipped through.

Fix this →

2. Deprecated openapi list error message changed wordingpackages/@sanity/cli/src/api/parser.ts:309-310

Pre-PR: "The OpenAPI service is currently unavailable. Please try again later."
Post-PR: "The OpenAPI service is currently unavailable. Try again later."

The PR description states the deprecated command's output is preserved. The user-visible error message dropped "Please". The existing test (openapi/__tests__/list.test.ts:90) only does .toContain('OpenAPI service is currently unavailable') so it missed this. If preservation is the contract, restore "Please" — otherwise note in the PR that the error copy is intentionally normalized.

3. isStreamingResponse only inspects the 200 responsepackages/@sanity/cli/src/api/parser.ts:157-163

Streaming responses can be advertised under other 2xx codes (e.g. 202). Not a Sanity issue today, but the implementation will silently mis-flag any future spec that puts SSE on a different status. Either add a comment locking the assumption, or iterate 2xx responses.

4. Empty-slug entries from the docs index aren't filteredpackages/@sanity/cli/src/api/docsClient.ts:63-70

When the docs API returns a malformed entry, slug becomes '' and fetchSpec('') ends up requesting …/docs/api/openapi/?format=yaml, which returns the index payload as JSON, then parseOpenApi fails. The error is caught and logged via fetchAndParseEntry's try/catch, so it's defensively handled — but it's also a malformed request the client could just skip up front. Consider rawSpecs.filter(s => typeof s.slug === 'string' && s.slug) instead of substituting ''.

Notes (not blocking)

  • applyFilters' toUpperCase() (list.ts:116) is dead code given oclif's options: constraint — falls out of fix chore: Configure Renovate - autoclosed #1.
  • --web --spec=<unknown-slug> opens a 404 docs page without validating against the index. Documented behavior, but worth a thought.
  • Per-spec fetches happen concurrently via Promise.all (parser.ts:285). Fine at ~22 specs; if the index grows, consider p-map with a concurrency cap (already a dep).
  • wrapForCell break-character regex includes . but the docstring only mentions /, _, - (views.ts:106-108).
  • Changeset content + bump type look correct (minor; adds new command, deprecates old one). One sentence, no marketing voice.
  • Bundle stats are flat / slightly down. No unnecessary new deps (@scalar/openapi-parser and @scalar/openapi-types are load-bearing for this feature).
    · branch feat/api-command-list

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

📦 Bundle Stats — @sanity/cli

Compared against main (3e479c08)

@sanity/cli

Metric Value vs main (3e479c0)
Internal (raw) 2.1 KB -8 B, -0.4%
Internal (gzip) 799 B -2 B, -0.2%
Bundled (raw) 10.97 MB -106 B, -0.0%
Bundled (gzip) 2.06 MB -33 B, -0.0%
Import time 762ms +45ms, +6.3%

bin:sanity

Metric Value vs main (3e479c0)
Internal (raw) 975 B -
Internal (gzip) 460 B -
Bundled (raw) 9.84 MB -
Bundled (gzip) 1.77 MB -
Import time 1.93s +169ms, +9.6%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against main (3e479c08)

Metric Value vs main (3e479c0)
Internal (raw) 95.5 KB -721 B, -0.7%
Internal (gzip) 22.5 KB -40 B, -0.2%
Bundled (raw) 21.60 MB -721 B, -0.0%
Bundled (gzip) 3.42 MB -93 B, -0.0%
Import time 738ms +56ms, +8.2%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against main (3e479c08)

Metric Value vs main (3e479c0)
Internal (raw) 976 B -
Internal (gzip) 507 B -
Bundled (raw) 50.7 KB -
Bundled (gzip) 12.6 KB -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@gu-stav gu-stav changed the base branch from main to feat/api-command-spec May 12, 2026 10:04
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

Coverage Delta

File Statements
packages/@sanity/cli/scripts/check-topic-aliases.ts 0.0% (±0%)
packages/@sanity/cli/src/api/docsClient.ts 95.8% (new)
packages/@sanity/cli/src/api/parser.ts 92.7% (new)
packages/@sanity/cli/src/api/views.ts 44.4% (new)
packages/@sanity/cli/src/commands/api/list.ts 97.3% (new)
packages/@sanity/cli/src/commands/openapi/list.ts 100.0% (±0%)

Comparing 6 changed files against main @ 3e479c080419f692b9e2ff4719e30843435f04e6

Overall Coverage

Metric Coverage
Statements 84.2% (+ 0.0%)
Branches 74.1% (±0%)
Functions 84.3% (+ 0.3%)
Lines 84.7% (+ 0.1%)

@gu-stav gu-stav changed the base branch from feat/api-command-spec to main May 13, 2026 07:27
@gu-stav gu-stav force-pushed the feat/api-command-list branch from 58db56d to 0660c0d Compare May 13, 2026 07:29
@gu-stav gu-stav changed the title Feat/api command list feat(api): add api list command and deprecate openapi * May 13, 2026
gu-stav added a commit that referenced this pull request May 13, 2026
The deprecation forwarder pattern changed `openapi list`'s output
from one row per spec to one row per operation — a silent breaking
change for anyone piping `openapi list --json` into a script.

Restores the pre-deprecation shape: `{title, slug, description}` per
spec, same human format, byte-identical stdout. The deprecation
warning still surfaces on stderr, and the new operation-level shape
remains on the canonical `sanity api list`.

Addresses review comment on PR #1070.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gu-stav and others added 6 commits May 13, 2026 10:15
Adds `sanity api list` rendering a flat operation table across all
public Sanity HTTP specs. Replaces the existing `sanity openapi list`
with a thin deprecation forwarder that delegates to the canonical
implementation.

Brings the shared infrastructure: revision-keyed cache (cache.ts),
docs endpoint client (docsClient.ts), OpenAPI parser (parser.ts),
revalidation orchestration (revalidate.ts), table + JSON renderers
(views.ts). All under packages/@sanity/cli/src/api/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the hand-rolled YAML walk + manual asObject/asArray casts
with @scalar/openapi-parser's validate(), which returns a typed
OpenAPI 3.x AST. Keeps $refs in place — link-not-resolve behavior
is preserved.

Strict validation warnings (missing descriptions, unbound path
params, etc.) are not fatal; the parser surfaces whatever it can
read. Only hard parse failures abort.

parseOpenApi is now async.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drops the revision-keyed YAML cache (cache.ts + revalidate.ts) and
fetches the index + per-spec YAML directly from the docs endpoint
on every `sanity api list` invocation. The 22 spec fetches run in
parallel, keeping cold runs around 1s end-to-end.

`loadParsedSpecs` now pulls specs from `docsClient.fetchSpec` and
parses them in memory; nothing is written to disk. Tests no longer
seed a temp cache dir — they mock the per-spec endpoint via nock.

Cache invalidation, the `SANITY_CLI_CACHE_PATH` test override, and
the pre-merge `revision: ''` fallback all go away with the cache.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the three-step fetch + parse + flatten recipe in list.ts
with a single `loadOperationsIndex()` seam in parser.ts. The
intermediate helpers (`loadParsedSpecs`, `buildOperationsIndex`) are
now private — they were never useful in isolation.

Also drops the unused `contentType` field from `fetchSpec`'s return
shape; callers only ever read the YAML body, so `fetchSpec(slug):
Promise<string | null>` is the honest signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The deprecation forwarder pattern changed `openapi list`'s output
from one row per spec to one row per operation — a silent breaking
change for anyone piping `openapi list --json` into a script.

Restores the pre-deprecation shape: `{title, slug, description}` per
spec, same human format, byte-identical stdout. The deprecation
warning still surfaces on stderr, and the new operation-level shape
remains on the canonical `sanity api list`.

Addresses review comment on PR #1070.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Walks through the review comments on #1070:

- Push `--spec` filter into `loadOperationsIndex` so single-spec
  invocations make 1 + 1 requests instead of 1 + 22. Adds the
  `onlySlug` option and drops the post-hoc filter from list.ts.
- Drop unused `ParsedSpec` (description/title/version/serverTemplate)
  in favor of a flat `ParsedOperation[]` return from `parseOpenApi`.
  None of those fields were consumed downstream.
- URL-encode the slug when building `docsUrl` in the JSON projection
  (parity with `fetchSpec`).
- Skip operations without an `operationId` instead of emitting them
  with `operationId: ''` — agent consumers index by operationId, and
  empty strings would collide silently.
- Drop stale doc-comment references to `revalidate.ts` (deleted) and
  the "Phase 2 destructive guard" (doesn't exist in this PR).

Plus the test gaps the review flagged:
- `--spec=<unknown-slug>` empty path
- empty-operationId skipping
- `SANITY_DOCS_API_URL` / `SANITY_DOCS_API_BYPASS_TOKEN` env overrides

Adds a changeset for the runtime change (minor bump on @sanity/cli).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gu-stav and others added 2 commits May 13, 2026 11:45
- Widen try/catch in fetchAndParseEntry to wrap fetchSpec too. The
  docstring promises one spec's failure doesn't poison the run; the
  prior code let any 5xx / network error reject the whole Promise.all.
  Adds a per-spec 5xx test that locks the contract.
- Dedup collectParamNames by (name, in). Per OpenAPI 3.x, operation-
  level params override path-item-level with the same key. Prior code
  pushed both, surfacing duplicate names in pathParams / requiredQueryParams.
- Drop the `revision` field from OpenApiSpecIndexEntry — no consumer
  reads it now that the cache is gone.
- Lift HTTP_REFERENCE_URL into docsClient.ts, dropping three duplicate
  definitions (list, openapi/list, views).
- Clarify the capability classification docstring: `POST` and any
  other method (including `TRACE`/`CONNECT`) fall back to `write`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two small de-dup wins from the architecture review:

- Add `docsUrlFor(slug)` in api/docsClient.ts; replace the inline
  `${HTTP_REFERENCE_URL}/${encodeURIComponent(slug)}` template at
  the one site already using it (views.ts).
- Add `loadOperationsIndexOrThrow()` + `DOCS_SERVICE_UNAVAILABLE`
  in api/parser.ts so commands can drop their bespoke try/catch
  + literal "service unavailable" string. `commands/api/list.ts`
  loses its `loadOperations` wrapper entirely; `commands/openapi/list.ts`
  references the shared constant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gu-stav and others added 6 commits May 13, 2026 14:22
…column

Three additions in response to agent-discoverability review:

- `--method`, `--capability`, `--grep` filter flags on `api list` so an
  agent searching for "delete a document" can narrow the result set
  without piping the full JSON through `jq`.
- New `OPERATION` column in the human table — the `operationId` is the
  cross-reference key for `sanity api spec --operation=<id>`, and
  omitting it from the table forced an extra `--json` round-trip just
  to recover the id.
- `optionalQueryParams: string[]` on the JSON row alongside
  `requiredQueryParams`, so callers get a full query-param picture from
  `list` without hopping to `spec`.

Also clarifies in `--web` help text that it's a human-only mode (no
machine-readable output).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pulls `mockIndexAndSpecs`, the standard `afterEach` cleanup, and the
canonical Jobs/Mutate spec YAMLs into `__tests__/fixtures.ts`. The
three `api/*` test files (list, spec, call — added in the upstack PRs)
all reach for the same shapes; this is the first step toward each test
file containing only what it actually asserts.

Extends the ESLint dep-exemption to cover any `**/__tests__/**/*.ts`
file (was: only the legacy `test/__fixtures__/` dir) so colocated test
helpers can import nock/vitest like `*.test.ts` files do.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The previous commit widened the eslint extraneous-deps exemption to
`**/__tests__/**/*.ts`, which loosened lint coverage for an entire
class of files. Reverts that and uses two narrowly-scoped
`eslint-disable-next-line` comments on the nock/vitest imports in
the colocated fixtures module — same effect, no global config drift.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…on-ids

The OPERATION column was bloating the table beyond any reasonable
terminal width: synthesized operationIds for spec operations missing
one (added on the call branch) can reach ~90 chars
(`delete_organizations_organizationId_providers_…`), and that single
row sets the column width for every other line.

Drops the column from the default human render; readers who want it
on screen pass `--operation-ids`. The id is always present in `--json`,
so this is a display preference, not a data loss. Long endpoints are
still soft-wrapped in `wrapForCell` so deeply-nested paths don't blow
out the ENDPOINT column either.

Also drops "human-only" / "agent-friendly" qualifiers from `--web`
and `--json` copy — these flags are useful for everyone, the
audience tag was noise.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Description: drop "OpenAPI"/"public" qualifiers — the topic
  already implies it, and the doc URL the table points at uses
  the same shorthand.
- Example "Render a table" → "Show a table" — render is unusual
  phrasing in CLI help.
- `--capability` flag: drop "bucket" jargon; spell out the values
  inline so the hint is self-describing.
- `--operation-ids` flag: shorter rationale — say *why* it's off
  (synthesized ids are long), drop the meta about JSON output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- docsClient: drop stale "until the docs-team PR adding `revision`
  merges" comment; `revision` was already removed from the index entry.
- docsClient: drop unnecessary `body?.specs` optional chain
  (the `await` cannot produce a nullish value).
- list.ts: empty-state copy framed "service may be unreachable" but
  that branch is unreachable when the loader threw — replaced with
  the actual condition: "No operations to list."
- list.ts: `--web --spec=<slug>` opens the per-spec docs page; the
  flag was silently ignored before.
- Tests: drop the now-noise `revision: ''` from the api/* nock
  mocks (single regression test in `openapi/list.test.ts` still
  asserts the field isn't leaked to consumers); assert exit code on
  the `openapi/list` "service unavailable" error.
- Changeset: trim to one sentence per project convention.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@socket-security
Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​scalar/​openapi-types@​0.8.01001009096100
Addednpm/​@​scalar/​openapi-parser@​0.28.299100100100100

View full report

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.

1 participant