Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
75f9e00
docs(client-generator): record the generator-rewrite decisions and an…
RomanHotsiy Aug 21, 2026
a58a6a4
docs(client-generator): state that the single-file generators are ref…
RomanHotsiy Aug 21, 2026
048de15
chore: update snapshots (#3048)
AlbinaBlazhko17 Aug 21, 2026
62fdb42
chore: 🔖 release new versions (#3043)
github-actions[bot] Aug 21, 2026
03cb486
fix: resolve $refs and validate AsyncAPI 3 Multi Format Schema Object…
vadyvas Aug 21, 2026
73935f9
fix(client-generator): the live defects the generator review found
Marshevskyy Aug 22, 2026
4cf8438
refactor(client-generator): promote deref and the operation-shape tri…
Marshevskyy Aug 22, 2026
e6f4459
refactor(client-generator): parse server-URL templates once, in the t…
Marshevskyy Aug 22, 2026
07cb923
refactor(client-generator): denormalize operation security once, in t…
Marshevskyy Aug 22, 2026
b5422db
refactor(client-generator): resolve a pagination rule's item element …
Marshevskyy Aug 22, 2026
09fd73b
refactor(client-generator): one TypeScript reserved-word list, one de…
Marshevskyy Aug 22, 2026
bdacfa2
feat(client-generator): one syntax printer per output language (ADR-0…
Marshevskyy Aug 22, 2026
0bfd32a
fix(client-generator): python and go string literals get a real escap…
Marshevskyy Aug 22, 2026
ee9afd0
fix(client-generator): one TypeScript string escaper, on the stricter…
Marshevskyy Aug 22, 2026
c85e381
refactor: resolve pagination once in the pipeline and hand every gene…
Marshevskyy Aug 22, 2026
43074a7
refactor: compute SSE facts once in the IR builder as op.sse and drop…
Marshevskyy Aug 22, 2026
5d344d9
refactor: hand generators a parsed output anchor and the banner lines…
Marshevskyy Aug 22, 2026
44ba882
refactor: move the satellite emitters (zod, mock, swr, tanstack-query…
Marshevskyy Aug 22, 2026
844bd63
refactor: move the option vocabulary (EmitOptions, ArgsStyle, ErrorMo…
Marshevskyy Aug 22, 2026
48771d8
refactor: move the TypeScript emitters into generators/typescript and…
Marshevskyy Aug 22, 2026
5cd42d2
refactor: fold the standalone TS text helpers (identifier, ts-literal…
Marshevskyy Aug 22, 2026
f07738e
refactor: move the pagination resolver to the pipeline layer (src/pag…
Marshevskyy Aug 22, 2026
7ea1a67
refactor: split the python generator into the ADR-0020 stage files
Marshevskyy Aug 22, 2026
874ef15
refactor: split the go generator into the ADR-0020 stage files
Marshevskyy Aug 22, 2026
6687a5a
refactor: split the php generator into the ADR-0020 stage files
Marshevskyy Aug 22, 2026
eac41e1
refactor: language generator folders import their own package by spec…
Marshevskyy Aug 22, 2026
11b193a
feat: eject the language generators as their TypeScript source folder…
Marshevskyy Aug 22, 2026
0fa3f56
feat!: remove the package runtime mode and the client runtime from th…
Marshevskyy Aug 22, 2026
1daa39f
refactor: move the runtime sources into their generator folders and d…
Marshevskyy Aug 22, 2026
a2414d3
feat: runtime 'module' writes the runtime as real files beside the Ty…
Marshevskyy Aug 22, 2026
1f02b23
feat: runtime 'module' writes the python, go, and php runtimes as rea…
Marshevskyy Aug 22, 2026
818f4b5
feat: every generator ejects as its TypeScript source folder — emitte…
Marshevskyy Aug 22, 2026
8ce3314
test: the composed CLI wires the ejected folder entry, not the retire…
Marshevskyy Aug 22, 2026
f3e3236
Merge remote-tracking branch 'origin/main' into docs/generator-rewrit…
Marshevskyy Aug 23, 2026
bf284d2
test: the config schema snapshot follows the runtime enum change (pac…
Marshevskyy Aug 23, 2026
8d994d2
test: cover the reference-page renderer at the unit level
Marshevskyy Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .changeset/ninety-phones-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@redocly/openapi-core': patch
'@redocly/cli': patch
---

Fixed an issue where the `bundle` command didn't resolve `$ref`s inside an AsyncAPI 3 Multi Format Schema Object.
6 changes: 0 additions & 6 deletions .changeset/olive-donkeys-shave.md

This file was deleted.

6 changes: 0 additions & 6 deletions .changeset/seven-waves-create.md

This file was deleted.

11 changes: 11 additions & 0 deletions docs/@v2/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ toc:

<!-- do-not-remove -->

## 2.47.0 (2026-08-21)

### Minor Changes

- Added a Vendor Extensions metric to the `stats` command that reports how many distinct `x-` extensions a description file uses and how often each one occurs.

### Patch Changes

- Fixed the `stats` command reporting wrong parameter count for AsyncAPI descriptions.
- Updated @redocly/openapi-core to v2.47.0.

## 2.46.2 (2026-08-19)

### Patch Changes
Expand Down
35 changes: 16 additions & 19 deletions docs/@v2/commands/eject-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Introduction

The `eject-generator` command copies a built-in client generator into your repository as an editable file.
The `eject-generator` command copies a built-in client generator into your repository as editable source.
You own the ejected generator and can customize it.
The generated client stays generated and reproducible.
Do not edit it manually.
Expand All @@ -29,23 +29,20 @@ redocly eject-generator php --force
| ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| generator | string | The built-in generator to eject. |
| `--config` | string | The path to the config file. |
| `--dir` | string | The directory that receives the ejected files. Default: `./generators`. |
| `--dir` | string | The directory that receives the ejected copy. Default: `./generators`. |
| `--update` | boolean | Do a three-way merge of the current built-in version into your customized copy. The command marks conflicts with standard markers. |
| `--force` | boolean | Overwrite an existing ejected file and discard the local edits. |
| `--force` | boolean | Overwrite an existing ejected copy and discard the local edits. |

## How it works

The eject operation writes two files:
The eject operation writes the generator and its design:

- `<dir>/<name>.mjs` is the generator itself, as a plain ESM file that you own.
The file contains everything that it needs to run standalone.
A language generator (`python`, `go`, `php`) is one self-contained file.
You get its source exactly as it was written.
A TypeScript generator is a thin entry point that uses shared emitters, so you get it bundled together with those emitters.
The bundle is not minified, and a comment marks each source module.
- Every generator ejects as `<dir>/<name>/` — its TypeScript source folder, exactly as it was written.
Each concern of the generator is one file, and `index.ts` is the entry.
Running an ejected generator uses Node's own type stripping, which requires Node 22.18, 23.6, or newer.

In both cases, the file imports the authoring toolkit from `@redocly/client-generator`.
A bundled generator also imports `logger` and `isPlainObject` from `@redocly/openapi-core`, which is a dependency of the toolkit.
The generator imports the authoring toolkit from `@redocly/client-generator`.
Some generators also import `logger` or `isPlainObject` from `@redocly/openapi-core`, which is a dependency of the toolkit; the command tells you when yours does.
If your package manager does not hoist dependencies, add `@redocly/openapi-core` explicitly.

- `.claude/skills/<name>-generator/SKILL.md` is the design of the generator, written as an agent skill.
Expand All @@ -65,17 +62,17 @@ The command keeps everything that you add outside the markers in that file.

The eject command also configures your project.
It adds `@redocly/client-generator` to your `devDependencies` if the package is not there.
It also points your config at the ejected file: in `client.generators`, the path to your copy replaces the built-in name.
It also points your config at the ejected copy: in `client.generators`, the path to your copy replaces the built-in name.
If the config has no `client.generators` list yet, the command adds one.

```yaml
client:
generators:
- ./generators/python.mjs
- ./generators/python/index.ts
```

If you leave the ejected generator unmodified, its output is byte-identical to the output of the built-in generator.
To roll back, delete the file and the config line.
To roll back, delete the ejected copy and the config line.

## Run the ejected generator

Expand All @@ -85,21 +82,21 @@ Generation is the same command as before the eject, because the config now point
redocly generate-client openapi.yaml --output src/client.ts
```

If you did not wire the config, name the file with `--generator`:
If you did not wire the config, name your copy with `--generator`:

```sh
redocly generate-client openapi.yaml --output src/client.ts --generator ./generators/python.mjs
redocly generate-client openapi.yaml --output src/client.ts --generator ./generators/python/index.ts
```

The command reports a generator that takes over a built-in name, so you can see that your copy is the one that runs.
Edit the file and run the command again to see the change.
Edit your copy and run the command again to see the change.
The eject command prints these instructions as well.

## Update an ejected generator

The `redocly eject-generator <name> --update` command merges a newer version into your copy.
That version is the one shipped by your installed `@redocly/client-generator` package.
The three-way merge uses the version recorded in the header of the ejected file as the common ancestor.
The three-way merge uses the version recorded in the header of each ejected file as the common ancestor, and a folder generator merges file by file.
Because of this, you do not have to commit extra files, and there is no snapshot to keep in sync.

The command merges the two skills in the same way, so an update keeps the design notes that you added to them.
Expand Down
29 changes: 16 additions & 13 deletions docs/@v2/commands/generate-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ redocly generate-client [--help] [--version]
| `api` | string | The file path to the OpenAPI description, a URL, or an `apis:` alias. Omit it to generate a client for each api that has a `client` block or `clientOutput`. |
| `--output`, `-o` | string | The output path (it must end in `.ts`). In multi-file modes, this is the entry file. Defaults to the `clientOutput` of the api, else `<name>.client.ts` next to the configuration file. Use this option only when you generate one API. |
| `--output-mode` | string | The file layout. See [Choose an output mode](#choose-an-output-mode). <br/> **Possible values:** `single`, `split`. Default: `single`. |
| `--runtime` | string | The location of the client engine. See [Choose a runtime](#choose-a-runtime). <br/> **Possible values:** `inline`, `package`. Default: `inline`. |
| `--runtime` | string | The location of the client engine. <br/> **Possible values:** `inline`, `module`. Default: `inline`. |
| `--import-ext` | string | The extension in the generated relative imports. See [Run with Node directly](../guides/use-generated-client.md#run-with-node-directly). <br/> **Possible values:** `js` (the tsc/bundler convention), `ts` (for Node's built-in type stripping). Default: `js`. |
| `--generator` | [string] | The generator to run: a built-in name, or the path or package of a custom generator. Repeat the flag to run more than one generator. Default value is `typescript`. See [Generators](../guides/use-generated-client.md#generators) for the full list. |
| `--args-style` | string | Sets how you pass inputs to operations. See [Argument style](../guides/use-generated-client.md#argument-style). <br/> **Possible values:** `grouped`, `flat`. Default: `grouped`. |
Expand Down Expand Up @@ -141,22 +141,25 @@ The `--output-mode` flag controls how the command splits the client into files:
redocly generate-client openapi.yaml -o src/api/client.ts --output-mode split
```

Both modes work with both runtimes.

### Choose a runtime

The `--runtime` flag controls the location of the client engine (request building, auth, retries, middleware, SSE):
The `--runtime` flag controls where the client engine lives:

- `inline` (default): the engine is embedded in the generated file, so the client is one self-contained file.
- `module`: the command writes the engine as real files in a `runtime/` folder beside the client, and the client imports them relatively.
Several generated clients in one repository can share one `runtime/` folder, and you can read the engine as ordinary source files.
The files are still machine-owned: the command regenerates them on every run.

Every generator that embeds an engine supports both modes, each in its language's shape:

- `inline` (default): the command embeds the runtime source in the generated output.
It embeds only the parts that your API needs.
The output is self-contained and has zero runtime dependencies.
- `package`: the generated file imports the runtime from `@redocly/client-generator`.
The file contains only the types, the operation descriptors, and thin call wrappers.
- `typescript` and `cli` write `runtime/*.ts` modules.
- `python` writes the `_*.py` runtime modules beside the client, which imports them.
- `go` writes a `runtime.go` file in the same package as the client.
- `php` writes a `runtime.php` file that the client loads with `require_once`.

Choose `package` if you want to get engine fixes with `npm update @redocly/client-generator` and no regeneration.
In this mode, the app that uses the client must install that package as a regular dependency.
Your application code is the same in both modes.
See [Package runtime](../guides/use-generated-client.md#package-runtime) in the usage guide and the [`package-runtime` example](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/package-runtime).
```bash
redocly generate-client openapi.yaml -o src/api/client.ts --runtime module
```

## Resources

Expand Down
2 changes: 1 addition & 1 deletion docs/@v2/configuration/reference/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ As an alternative, pass `pagination` to the programmatic `generateClient(...)`.
| ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `generators` | [string] | The generators to run, in order. Each entry is a built-in name (`typescript`, `zod`, `tanstack-query` or its `-vue`/`-svelte`/`-solid` variants, `swr`, `mock`, `transformers`, `cli`, `python`, `go`, `php`), or the path or package name of a custom generator. |
| `outputMode` | string | The file layout: `single` or `split`. This option applies to TypeScript output only. The `python`, `go`, and `php` SDKs always emit one self-contained file. |
| `runtime` | string | The runtime distribution: `inline` or `package`. This option applies to TypeScript output only. The `python`, `go`, and `php` SDKs always embed their runtime. |
| `runtime` | string | The runtime distribution: `inline` (the runtime is embedded in the generated output) or `module` (the runtime is written as real files in a `runtime/` folder beside the client). |
| `importExt` | string | The extension in generated relative imports: `js` (default, for tsc and bundlers) or `ts` (for Node's built-in type stripping). This option applies to TypeScript output only. |
| `argsStyle` | string | How the client receives operation inputs: `grouped` (default) groups them by transport layer (`path`, `query`, `headers`, `cookies`, `body`), and `flat` merges them into one object. This option applies to TypeScript output only. Each language SDK follows its own idiom (keyword arguments, named arguments, a params struct). |
| `errorMode` | string | How operations report HTTP errors: `throw` or `result`. The `python` SDK implements both. The `go` and `php` SDKs support only `throw`, because that is the language idiom, and they reject `result`. |
Expand Down
16 changes: 8 additions & 8 deletions docs/@v2/guides/customize-client-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ See the [`baked-setup` example](https://github.com/Redocly/redocly-cli/tree/main

The quickest method to get a customized generator is
[`redocly eject-generator <name>`](../commands/eject-generator.md).
The command copies any built-in generator into `./generators/` as an editable file that you own.
The command copies any built-in generator into `./generators/` as its TypeScript source folder — editable source that you own.
An ejected generator with no changes produces byte-identical output.
In `client.generators`, the path to your copy replaces the built-in name.
Because of this, `redocly generate-client` now runs your version.
Expand Down Expand Up @@ -142,7 +142,7 @@ export default defineGenerator({
properties: { groupBy: { enum: ['tag', 'path'], default: 'tag' } },
additionalProperties: false,
},
run({ model, outputPath, options }) {
run({ model, output, options }) {
// `options` is validated against the schema before `run` is called.
},
});
Expand Down Expand Up @@ -204,7 +204,7 @@ Your coding agent then has the contract, the model reference, and this helper ta

TypeScript is one more output language.
The `@redocly/client-generator/generate` entry exports the TypeScript-specific renderers.
These renderers are not on the package root, so the import graph of a `runtime: 'package'` client never includes the generation toolkit.
These renderers are not on the package root, which stays a small authoring surface.
`tsType` is the schema-to-type renderer that the built-in `typescript` generator itself uses.
Because of this, the mapping (refs, arrays, unions, formats, parenthesization) is exactly the same as in the generated client:

Expand All @@ -214,7 +214,7 @@ import { tsType } from '@redocly/client-generator/generate';
export default {
name: 'response-map',
requires: ['typescript'],
run({ model, outputPath }) {
run({ model, output }) {
const members = model.services
.flatMap((service) => service.operations)
.flatMap((op) => {
Expand All @@ -223,7 +223,7 @@ export default {
});
return [
{
path: outputPath.replace(/\.ts$/, '.responses.ts'),
path: output.path.replace(/\.ts$/, '.responses.ts'),
content: `export type ResponseShapes = {\n${members.join('\n')}\n};\n`,
},
];
Expand Down Expand Up @@ -295,14 +295,14 @@ const rubyCall = (operation) => ({ lang: 'ruby', source: `client.${operation.nam

export default defineGenerator({
name: 'ruby',
run({ model, outputPath }) {
run({ model, output }) {
/* the SDK */
},
sample: rubyCall,
docs({ model, outputPath, emit }) {
docs({ model, output, emit }) {
return [
{
path: outputPath.replace(/\.[^.\\/]+$/, '.ruby.md'),
path: output.path.replace(/\.[^.\\/]+$/, '.ruby.md'),
content: renderReferencePage(model, {
title: `${model.title} Ruby SDK reference`,
frontmatter: emit.docsFrontmatter === true,
Expand Down
Loading
Loading