Skip to content

feat: openapi typescript client#2885

Open
Marshevskyy wants to merge 70 commits into
mainfrom
feat/ts-client-gen
Open

feat: openapi typescript client#2885
Marshevskyy wants to merge 70 commits into
mainfrom
feat/ts-client-gen

Conversation

@Marshevskyy

@Marshevskyy Marshevskyy commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Adds @redocly/client-generator and the redocly generate-client command: generate a typed TypeScript client from an OpenAPI description. The emitted client has zero runtime dependencies (web-standard fetch/AbortController/URLSearchParams) and is built via the TypeScript compiler AST, so output is correct by construction. typescript is the only peer dep (the CLI provides it).

Existing tools force a trade-off: types-only (you hand-write every fetch/auth/retry) or a full client that drags a runtime dependency into your bundle forever. This generates the full client into code you own — no runtime dep.

This PR includes approximately 54,000 lines of code. However, around 20,000 lines are examples, and another ~20,000 lines are tests and documentation, so the actual implementation is not that large.

  • Input: OpenAPI 3.0/3.1/3.2 + Swagger 2.0.
  • Output: single/split/tags/tags-split layouts; functions or service-class facade (per-instance config + credentials); flat/grouped args.
  • Types: inline types, discriminated-union is<Member>() guards, <Op>* aliases, Date typing, typed multipart (binary → Blob).
  • Runtime: auth from securitySchemes (+ async providers, per-instance), composable middleware, opt-in abort-aware retries, parseAs, query-serialization styles, result error mode, typed Server-Sent Events (auto-reconnect, OAS 3.2 itemSchema).
  • Generators (--generators): sdk (default), zod, tanstack-query (React/Vue/Svelte/Solid), swr, transformers, mock (MSW + baked/faker), plus a custom-generator plugin API (@redocly/client-generator/plugin).
  • Config: CLI flags, redocly.yaml x-client-generator, or a defineConfig file.
  • Hardened: safe-identifier coercion, comment escaping, bounded SSE reader.

Reference

Testing

  • npm test green (compile + typecheck + unit + e2e); 100% per-file coverage on the package.
  • e2e generates clients, type-checks them under strict tsc, and runs them against a mock server.
  • Runnable, drift-checked examples under packages/client-generator/examples/.

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
Large new experimental surface (codegen + dynamic generator imports and --setup modules at generation time), but it is additive and does not alter existing CLI commands’ behavior.

Overview
Introduces @redocly/client-generator and wires redocly generate-client into the CLI. OpenAPI 3.x and Swagger 2.0 specs become a zero runtime-dependency TypeScript client (AST-based codegen, optional add-ons via --generators).

Configuration merges top-level and per-API client blocks in redocly.yaml** with CLI flags (fan-out when no is passed). New **--setup** bakes publisher defaults via defineClientSetup({ config, middleware }). Generated middleware gets **literal-typed** ctx.operation and an **OPERATIONS** map (with tags`).

Docs cover the command, client config reference, and a consumer guide; README and sidebars are updated. E2E coverage and formatter/linter ignores for generated example output are added. The CLI build banner gains __filename / __dirname shims for the bundled TypeScript compiler used during generation.

Reviewed by Cursor Bugbot for commit 5e404d0. Bugbot is set up for automated code reviews on this repo. Configure here.

…enAPI client

Add `@redocly/openapi-typescript` and the `redocly generate-client` command:
generate a typed TypeScript client from an OpenAPI description. The emitted
client has zero runtime dependencies (web-standard fetch/AbortController/
URLSearchParams) and is produced via the TypeScript compiler AST, so output is
correct by construction; `typescript` is the only peer dependency.

Input: OpenAPI 3.0/3.1/3.2.0 + Swagger 2.0 (normalized to 3.x); file, URL, or a
redocly.yaml `apis:` alias; operationId synthesized when absent.

Output: single / split / tags / tags-split layouts; `functions` or
`service-class` facade (per-instance config + credentials); flat or grouped
argument styles.

Types: inline types; enums as unions or runtime const objects; discriminated-
union `is<Member>()` guards; `<Op>Result/Error/Params/Body/Headers/Variables`
aliases with collision suppression; JSDoc from validation keywords; optional
`Date` typing; typed multipart bodies (binary → Blob) auto-serialized to FormData.

Runtime: setBaseUrl + typed ClientConfig; composable middleware (onRequest/
onResponse/onError); opt-in abort-aware retries (backoff, jitter, Retry-After,
custom retryOn); per-call parseAs; OpenAPI query-serialization styles;
`--error-mode result` discriminated returns; minification-safe OPERATIONS map;
typed Server-Sent Events (async iterators, auto-reconnect, OAS 3.2 itemSchema).

Auth: Basic / Bearer / apiKey (header, query, cookie) from securitySchemes, async
token providers, and per-instance credentials via ClientConfig.auth.

Generators (--generators): sdk (default), zod, tanstack-query (react/vue/svelte/
solid), swr, transformers, mock (MSW handlers + baked or faker data, seedable),
plus an experimental custom-generator plugin API (@redocly/openapi-typescript/
plugin) with dual loading (inline + import specifier) and a validated
compatibility contract. Each generator declares requires/facades/errorModes/
dateTypes, validated up front.

Configuration via CLI flags, a redocly.yaml `x-openapi-typescript` block, or a
defineConfig file; plus `--watch`. Hardened: document-derived names coerced to
safe unique identifiers, comment text escaped, bounded SSE reader. Architecture,
ADRs (0001-0012), and runnable examples included.
@Marshevskyy Marshevskyy requested review from a team as code owners June 16, 2026 07:24
@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5e404d0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@redocly/client-generator Minor
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/cli/src/commands/generate-client.ts Outdated
Comment thread packages/cli/src/commands/generate-client.ts Outdated
Comment thread .changeset/openapi-typescript.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread README.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread packages/client-generator/src/index.ts
Comment thread packages/client-generator/src/emitters/runtime.ts
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
Comment thread packages/client-generator/src/emitters/runtime.ts
Comment thread packages/client-generator/src/generators/index.ts
Comment thread packages/client-generator/src/index.ts
JLekawa and others added 3 commits June 18, 2026 12:17
…ing docs

Switch the runnable examples and the cafe e2e fixture/snapshot to the spec's
`servers[0].url` (api.cafe.redocly.com), regenerated with the current generator.
Demonstrate middleware in the fetch-functions example via `onResponse` so it
isn't blocked by the demo API's CORS preflight (it doesn't allow a custom
`X-Request-Id` request header). Add a "Testing the generated client" section to
the README (Node / browser-CORS / MSW mocks).
Comment thread packages/client-generator/src/generators/resolve.ts
Resolve conflicts in package.json, package-lock.json, packages/cli/package.json,
tsconfig.json, tsconfig.build.json, and vitest.config.ts.

- Adopt main's esbuild-bundled CLI build; add @redocly/openapi-typescript to
  packages/cli devDependencies so it bundles alongside openapi-core/respect-core.
- Bump openapi-typescript's @redocly/openapi-core dependency 2.31.4 -> 2.34.0 to
  match the workspace, so npm symlinks the workspace package instead of a nested
  copy (the mismatch produced divergent Config type identities).
- Extend the CLI bundle banner to shim __filename/__dirname (via var, to coexist
  with deps that self-declare them) so the bundled typescript compiler used by
  generate-client runs in ESM scope.
- Keep the per-glob 100% coverage threshold for openapi-typescript alongside
  main's repo-wide branches:73.
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x ± 0.01 ▓ 1.00x (Fastest) ▓ 1.00x ± 0.01
cli-next ▓ 1.00x (Fastest) ▓ 1.00x ± 0 ▓ 1.00x (Fastest)

Comment thread packages/client-generator/src/emitters/runtime.ts
Comment thread packages/client-generator/src/generators/resolve.ts
…MD009

Three prose lines had a stray single trailing space (lines 644, 651, 939),
which markdownlint MD009 rejects (expects 0 or 2). Verified clean with
markdownlint-cli2 v0.22.0 against the repo's .markdownlint.yaml.
Comment thread packages/cli/src/commands/generate-client.ts Outdated
The vale job used reviewdog with filter_mode: file, which fetches the PR
diff to scope findings to changed files. GitHub's diff API caps at 20000
lines, so large PRs (this one adds ~54k lines) return 406 and reviewdog
fails on the post step — not on any actual vale finding.

filter_mode: nofilter skips the diff fetch and lints the full files
directly. Verified the committed docs are clean (0 errors/warnings/
suggestions across 320 files with vale 3.15.1), so nofilter adds no noise
and the error-level gate still holds.
The consumer harnesses (base/cafe/sse) have tracked index*.ts that import a
generated `./api.js`. The repo-wide `tsc --noEmit` includes tests/**/*.ts, so
it typechecks those imports — but `api.ts` was gitignored and only created when
the e2e suite ran, so a fresh checkout (CI) failed with TS2307.

The harnesses already expected api.ts present for typecheck (see the note in
sse.runtime.test.ts); gitignoring it was the gap. generate-client output is
byte-deterministic for these fixtures (fixed ports, fixed specs — verified by
regenerating and diffing), so commit the files instead of touching tsconfig.
The e2e suite still regenerates them each run, producing identical content
(verified: no drift after running base.test.ts).
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 84.48% (🎯 81%) 9461 / 11199
🔵 Statements 84.28% (🎯 80%) 10102 / 11985
🔵 Functions 87.8% (🎯 84%) 1988 / 2264
🔵 Branches 77.84% (🎯 73%) 6639 / 8528
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/generate-client.ts 0% 0% 0% 0% 17-145
packages/client-generator/src/config-file.ts 100% 100% 100% 100%
packages/client-generator/src/errors.ts 100% 100% 100% 100%
packages/client-generator/src/index.ts 93.1% 83.33% 100% 93.1% 104-105
packages/client-generator/src/loader.ts 100% 100% 100% 100%
packages/client-generator/src/plugin.ts 100% 100% 100% 100%
packages/client-generator/src/runtime-contract.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/auth.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/client.ts 99.23% 98.07% 100% 100% 428
packages/client-generator/src/emitters/faker.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/identifier.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/jsdoc.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/mock.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/operation-aliases.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/operation-signature.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/operation-types.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/operations.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/runtime.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/sample.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/setup-bake.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/sse.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/support.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/swr.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/tanstack-query.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/transformers.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/ts.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/type-guards.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/types.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/wrapper-support.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/zod.ts 100% 100% 100% 100%
packages/client-generator/src/generators/index.ts 100% 100% 100% 100%
packages/client-generator/src/generators/mock.ts 100% 100% 100% 100%
packages/client-generator/src/generators/resolve.ts 100% 100% 100% 100%
packages/client-generator/src/generators/sdk.ts 100% 100% 100% 100%
packages/client-generator/src/generators/swr.ts 100% 100% 100% 100%
packages/client-generator/src/generators/tanstack-query.ts 100% 100% 100% 100%
packages/client-generator/src/generators/transformers.ts 100% 100% 100% 100%
packages/client-generator/src/generators/zod.ts 100% 100% 100% 100%
packages/client-generator/src/ir/build.ts 100% 100% 100% 100%
packages/client-generator/src/ir/normalize-swagger2.ts 100% 100% 100% 100%
packages/client-generator/src/ir/refs.ts 100% 100% 100% 100%
packages/client-generator/src/ir/sanitize-identifiers.ts 100% 100% 100% 100%
packages/client-generator/src/writers/group-by-tag.ts 100% 100% 100% 100%
packages/client-generator/src/writers/index.ts 100% 100% 100% 100%
packages/client-generator/src/writers/single-file-writer.ts 100% 100% 100% 100%
packages/client-generator/src/writers/split-writer.ts 100% 100% 100% 100%
packages/client-generator/src/writers/tagged.ts 100% 100% 100% 100%
packages/client-generator/src/writers/tags-split-writer.ts 100% 100% 100% 100%
packages/client-generator/src/writers/tags-writer.ts 100% 100% 100% 100%
packages/client-generator/src/writers/util.ts 100% 100% 100% 100%
Generated in workflow #10521 for commit 18705f9 by the Vitest Coverage Report Action

Comment thread packages/cli/src/commands/generate-client.ts Outdated
…apshots

The branch added `react`/`react-dom` `^18.2.0` to the root devDependencies
(main has neither — it resolves react 19.2.7 via packages/cli's
`^17 || ^18.2.0 || ^19.2.7` range). That root `^18.2.0` cap forced npm to
hoist react 18.3.1 for the whole workspace, so build-docs rendered React 18's
`useId` format (`tab:R9pq:0`) instead of the React 19 format (`tab_R_9pq_0`)
the committed redoc-static snapshots were generated with — failing
build-docs.test.ts on CI.

Bump root react/react-dom to `^19.2.0` so npm resolves 19.2.7, matching main.
Verified: build-docs.test.ts (7/7) and the react-19 consumers
(tanstack-query.runtime, swr) all pass.
filter_mode: nofilter alone wasn't enough — the github-pr-annotations reporter
still fetches the PR diff to position comments, and GitHub caps that diff at
20000 lines, so this 54k-line PR gets a 406 and reviewdog exits 1 (on the diff
fetch, not on any vale finding).

Switch reporter to `local`: reviewdog prints findings to the job log and exits
non-zero only on vale errors, with no GitHub API call and therefore no diff to
fetch. The gate still holds (committed docs verified: 0 vale errors across 320
files). Trade-off: findings show in the Actions log rather than as inline PR
annotations — which a 20k-line-capped diff can't render on a PR this size anyway.

@tatomyr tatomyr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checked a couple of root files. Haven't checked any actual implementation yet.

Comment thread .github/workflows/docs-tests.yaml Outdated
with:
files: '["README.md", "docs", ".changeset"]'
filter_mode: file
# `nofilter` instead of `file`: file-level filtering makes reviewdog

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What's this change for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

vale failed due to too GH api limitation, I'll try to change it back and see if GA action can work...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

reverted, but I expect GH action to fail now

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yes, it's expected for large docs changes. You can run vale locally to ensure the docs are correct.

Comment thread docs/@v2/commands/generate-client.md Outdated
Comment thread CONTRIBUTING.md Outdated
Having `redocly.yaml` in the root of the project affects the unit tests, and console logs affect the e2e tests, so make sure to get rid of both before running tests.
Run `npm test` to start both unit and e2e tests (and additionally typecheck the code).

### Monorepo test conventions

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These additions are mostly duplications or explanations of a common testing approaches. Noone ever asked us to clarify that. I would refrain from adding this info to the contribution guide as it makes it harder to read and find actually useful info.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed

Comment thread vitest.config.ts Outdated
@@ -24,6 +25,14 @@ const configExtension: { [key: string]: ViteUserConfig } = {
functions: 84,
statements: 80,
branches: 73,
// Strict per-file 100% coverage for the new client generator. Per-glob thresholds run

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wouldn't add separate thresholds per packages. We used to do it that way but eventually moved away because of the maintenance complications.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed

Comment thread tsconfig.json Outdated
Comment on lines +26 to +27
"exclude": ["node_modules"],
"include": [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated a bit, but we have to exclude examples since they include react code, which is not covered in this tsconfig...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's discuss offline whether we need React examples.

js: "import { createRequire as __createRequire } from 'node:module';\nconst require = __createRequire(import.meta.url);",
js: [
"import { createRequire as __createRequire } from 'node:module';",
"import { fileURLToPath as __fileURLToPath } from 'node:url';",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What are the packages that require these? Cannot we use ESM ones instead to have as few fallbacks as possible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the new one...
I'll try to find a better approach

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question, this change tells the CLI bundler how to handle the typescript compiler that generate-client newly pulls in — without it, the bundled CLI crashes with __filename is not defined.
As alternative we can revert this changes, but add typescript as dep instead of devDep in packages/cli, what would you prefer?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's discuss that offline.

Comment thread packages/openapi-typescript/README.md Outdated
@@ -0,0 +1,387 @@
# @redocly/openapi-typescript

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not just @redocly/sdk or something like that? What if we want to support flavours other than OpenAPI, say AsyncAPI? Or other languages?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about naming, but in the future, we can add support for other languages: openapi-go, openapi-python...
we can name it like sdk-ts, sdk-python, what do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sounds good. I just wouldn't restrict it to openapi since we could support other spes in the same package.

Complete the serverUrl rename: the generated client's runtime helper
`setBaseUrl()` becomes `setServerUrl()` (it reassigns the inlined BASE binding).
Updates the emitter, the sorted public re-export list, unit + e2e tests, and
regenerates the committed clients, consumer fixtures, and cafe snapshot.
Rewrite the command reference Configuration + Usage for the new model: a top-level
`client` block, per-API `apis.<name>.client` / `clientOutput`, and the three
invocation modes (fan-out / alias / path). Rename baseUrl -> serverUrl and
--base-url -> --server-url throughout the docs, drop the removed `defineConfig`
from the README (use `satisfies Config`), refresh ARCHITECTURE.md and the
regenerate/mergeConfig comments, and mark ADR-0008 superseded.
// and other strict HTTP clients. Ignore errors (e.g. a middleware already read it).
await response.body?.cancel().catch(() => undefined);
await __sleep(__retryDelay(retry, attempt, retryAfter), signal);
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Retries skip middleware refresh

Medium Severity

In generated __send, onRequest middleware, optional per-request config.headers() resolution, and body serialization run once before the retry loop. Later attempts reuse the same context, headers, and payload, so middleware that must refresh signatures, tokens, or timestamps on each attempt will not run again on retries.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 794649e. Configure here.

Add `client` (config root + per-API) and `clientOutput` (per-API) to the config
types as a loosely-typed ClientGeneratorConfig (core does not depend on
@redocly/client-generator, which owns the precise shape). The generate-client CLI
now reads `config.resolvedConfig.client` / `.apis` through those types instead of
casting the whole resolved config to Record<string, unknown>.
The body-after-onRequest behavior is part of the new experimental generate-client
command's first release, not a fix to a shipped version, so it folds into the
single client-generator changeset for this PR.
…mary

Collapse the manual files.length === 1 branch into one message using the shared
`pluralize` helper (the house pattern), so it reads "1 file" / "N files" at the
output path.
The parse-only intent reads from the code (split/trim/filter, empty -> undefined);
name the accumulator `generators` instead.
Comment thread packages/cli/src/commands/generate-client.ts Outdated
Spell out the only abbreviated directory (the IR — intermediate representation)
as intermediate-representation/, updating all imports, the moved __tests__, and the
path references in ARCHITECTURE.md and the ADRs. The `refs`/`sse`/`jsdoc` names
stay — they're standard, meaningful domain terms, not cryptic abbreviations.
…toFormData import

Comment review across the branch after the renames/refactors:
- fix stale references: ir/ path in support.ts, ADR-0007 -> ADR-0009 for per-instance
  auth, drop the internal '(Task 6)' note, drop 'in PoC' from the $ref error, and the
  'mirroring how config files load' analogy (that mechanism was removed)
- move two JSDoc blocks that had drifted onto the wrong function (the __request-args
  doc onto renderRequestArgs; the body-type one-liner onto bodyTypeNode), and correct
  the __request-args doc (it omitted the op-identity arg and the multipart flag)
- reword the 'three things vary' runtime header (now several gated blocks) and the
  EmitOptions parenthetical (it omitted knobs generators actually read), and clarify
  the client-block schema comment

The review surfaced a real bug: since multipart bodies now serialize in __send (after
onRequest), the endpoints module no longer references __toFormData, yet still imported
it (an unused import, invisible to tsc --strict). Make __toFormData module-private in
the runtime and stop the endpoints module importing it; update the unit test.
Comment thread packages/cli/src/commands/generate-client.ts
export function use(...middleware: Middleware[]): void {
// Reassign (don't push) so a caller-provided \`middleware\` array isn't mutated.
__config.middleware = [...(__config.middleware ?? []), ...middleware];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Configure replaces baked middleware

Medium Severity

Publisher --setup applies baked middleware via use(...) on module load, but a later configure({ middleware: [...] }) uses Object.assign and replaces the entire middleware array instead of appending. Consumer middleware configured through configure drops baked interceptors, contrary to documented compose semantics.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5728f00. Configure here.

Remove the `tests/e2e/**/*.yaml` ignore added on this branch — it suppressed
formatting for every e2e spec fixture. The only file it was shielding is
fixtures/cafe.yaml, which is a normal spec with no reason to skip formatting, so
format it rather than ignore the whole tree. Also normalizes the oxlint ignore
ordering.
Apply oxfmt to files committed with --no-verify earlier on the branch (emitters,
CLI command, docs, e2e tests) so the tree is clean under `npm run format`. Also
drops a redundant coverage sentence in CONTRIBUTING.md. No behavior change.
… node16

The minimum supported Node is v20, so the node16 module/moduleResolution used by
the e2e strict-compile checks and the consumer tsconfigs is outdated. Switch them to
nodenext (the repo-wide convention; still node-style resolution, so the emitted `.js`
extensions are still exercised). Update the moduleSpecifier comment to drop the stale
node16 reference.
Comment thread packages/client-generator/src/emitters/setup-bake.ts
… root

Fold the ./config-file and ./plugin subpath exports into the root entry: index.ts
now re-exports `mergeConfig` and `export *`s the plugin surface (defineGenerator,
the codegen toolkit, IR types), and package.json declares only the "." export. The
CLI reads `mergeConfig` from the root import, and the plugin/config-file references
in docs, comments, the custom-generator example, and ADR-0012/0013 point at the root
(ADR-0012 amended to note the entry move). One import path for consumers.
…ipts

Wrap the flush-left multi-line template literals across the test suite (inline
OpenAPI/Swagger specs, TS consumer/driver scripts, and setup-module sources) with
the `outdent` tag so they read naturally at their nesting level while producing the
same string at runtime. Applied systematically across 15 files (~45 literals);
files whose specs/consumers live in fixtures/ or *-consumer/ were left unchanged.
Comment thread tests/e2e/generate-client/base.test.ts
The single generate-client reference grew to cover both the CLI and the generated
client's runtime API. Split it three ways:
- commands/generate-client.md — CLI usage: flags, output modes, a config pointer
- commands/generate-client-usage.md (new) — the runtime surface: auth, error modes,
  middleware, retries, query/multipart/decoding, metadata, SSE, and the add-on generators
- configuration/client.md (new) — the redocly.yaml `client` block + `clientOutput`

Cross-page anchor links repointed, both new pages added to the v2 sidebar.
Revert the three-way split: no other command has a secondary '-usage' page or a
dedicated configuration/ page — each command is one commands/<name>.md that
documents its flags and config inline (lint.md is 487 lines on one page). Fold the
client-usage and client-config content back into commands/generate-client.md and
drop the two extra pages + their sidebar entries, matching the existing convention.
Follow-up to the single-page revert — remove the generate-client (client usage) and
configuration/client.md sidebar entries left over from the split.
…rence

Re-split generate-client the way the docs are organized:
- commands/generate-client.md — the command reference (flags, output modes)
- guides/use-generated-client.md — a usage guide for the generated client (auth,
  error modes, middleware, retries, add-on generators), in the Guides group
- configuration/reference/client.md — the `client` config-key reference, alongside
  the other reference pages (apis, rules, …)

Cross-page anchor links wired; all three added to the v2 sidebar (Commands, Guides,
Configuration → Reference).
…tput, setup alias, ready probe)

- Reject a bare-hostname --server-url (e.g. api.example.com): validate as an absolute
  URL or a root-relative path, not new URL(value, base) which accepts any string as a path.
- Error when two fan-out apis resolve to the same output path instead of silently
  overwriting; track resolved output paths across jobs.
- bakeSetup now unwraps a renamed defineClientSetup import (import { defineClientSetup
  as setup }); it matched the literal name before, leaving a call to a dropped import.
- The e2e readiness probe records a non-OK HTTP status as the failure reason so the
  timeout message is accurate.

Adds unit coverage for the aliased-import unwrap and e2e coverage for the duplicate
clientOutput and serverUrl validation.
…e semantics

- Retries resend the same request (onRequest/headers()/body run once); refresh
  per-attempt via onResponse/onError/retryOn.
- use() appends (composes with baked middleware); configure({middleware}) replaces.
Comment thread .oxfmtrc.json
Comment on lines +8 to +14
"tests/e2e/generate-client/examples/*/src/api/",
"lib/",
"output/",
"packages/respect-core/src/modules/runtime-expressions/abnf-parser.js",
"packages/core/src/rules/common/__tests__/fixtures/invalid-yaml.yaml",
"tests/performance/api-definitions/",
"tests/e2e/generate-client/*-consumer/api.ts",

@tatomyr tatomyr Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you group these two together?

    "tests/e2e/generate-client/examples/*/src/api/",
    "tests/e2e/generate-client/*-consumer/api.ts",

Comment thread README.md Outdated
Co-authored-by: Andrew Tatomyr <andrew.tatomyr@redocly.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3f0323c. Configure here.

return true;
} catch {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Server URL scheme not restricted

Medium Severity

isValidServerUrl treats any string that new URL(value) accepts as valid, while the CLI error text only mentions https://… and root-relative /v1 paths. Values such as javascript: or file: URLs pass validation and are inlined as the generated client’s base URL, so misconfiguration or a hostile config can produce a client whose default base URL is not a normal HTTP API origin.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3f0323c. Configure here.

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.

6 participants