diff --git a/.changeset/client-generator.md b/.changeset/client-generator.md new file mode 100644 index 0000000000..d19eeceddf --- /dev/null +++ b/.changeset/client-generator.md @@ -0,0 +1,9 @@ +--- +'@redocly/client-generator': minor +'@redocly/openapi-core': minor +'@redocly/cli': minor +--- + +Added an experimental `generate-client` command that generates a typed, zero-dependency TypeScript client from an OpenAPI description — with auth, retries, middleware, typed SSE streaming, auto-pagination, and multipart support. +The `--generator` option emits companion modules from the same description: `zod`, `tanstack-query`, `swr`, `transformers`, and `mock`. +See the [`generate-client` command reference](https://redocly.com/docs/cli/commands/generate-client) and the [Use the generated client](https://redocly.com/docs/cli/guides/use-generated-client) guide. diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a9bd6ec2b0..fd322abf71 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -206,6 +206,11 @@ jobs: cache: npm registry-url: https://registry.npmjs.org + # Install before the version bump: npm links workspace packages only while + # their versions still match the dependency specs in package.json files. + - name: Install dependencies + run: npm install + - name: Update package versions run: | TIMESTAMP=$(date +%s) @@ -218,6 +223,10 @@ jobs: jq ".version = \"$VERSION\"" packages/respect-core/package.json > tmp.json && mv tmp.json packages/respect-core/package.json jq ".dependencies[\"@redocly/openapi-core\"] = \"$VERSION\"" packages/respect-core/package.json > tmp.json && mv tmp.json packages/respect-core/package.json + # Update Client Generator package version and dependencies + jq ".version = \"$VERSION\"" packages/client-generator/package.json > tmp.json && mv tmp.json packages/client-generator/package.json + jq ".dependencies[\"@redocly/openapi-core\"] = \"$VERSION\"" packages/client-generator/package.json > tmp.json && mv tmp.json packages/client-generator/package.json + # Update CLI package version jq ".version = \"$VERSION\"" packages/cli/package.json > tmp.json && mv tmp.json packages/cli/package.json @@ -236,9 +245,6 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} - - name: Install dependencies - run: npm install - - name: Build packages run: npm run compile @@ -258,3 +264,7 @@ jobs: npm run prepare:publish-dir npm publish ./.publish --tag snapshot npm run clean:publish-dir + sleep 10 + + cd ../client-generator + npm publish --tag snapshot diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c7432bee66..fa05d472a5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -7,6 +7,9 @@ on: branches: - main +permissions: + contents: read + env: CI: true REDOCLY_TELEMETRY: off @@ -32,8 +35,44 @@ jobs: continue-on-error: true # Do not fail if there is an error during reporting uses: davelosert/vitest-coverage-report-action@d63aa97db4c0319f304f1787689de1ca548365cf # v2.11.1 - - name: E2E Tests - run: npm run e2e + e2e: + # The e2e suite is split across shards so no single runner carries the whole set. + # Running all suites in one step was cancelled mid-run by the Actions service once the + # generate-client suites grew past ~28 (a healthy runner, no resource exhaustion); + # each shard stays well under that. + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: [1, 2] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 + cache: npm + - name: Install dependencies + run: npm ci + - name: E2E Tests (shard ${{ matrix.shard }}/2) + run: npm run e2e -- --shard=${{ matrix.shard }}/2 + + examples: + # The examples gitignore their generated clients (only zero-install-quickstart commits + # its canonical copy), so this job regenerates every client and type-checks the + # hand-written consumer code against it. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 + cache: npm + - name: Install dependencies + run: npm ci + - name: Regenerate example clients + run: npm run examples:regen -w @redocly/client-generator + - name: Type-check examples + run: npm run typecheck:examples -w @redocly/client-generator code-style-check: runs-on: ubuntu-latest diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 86a784dacb..fc6793d2c6 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -10,8 +10,11 @@ "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/examples/*/src/api/", + "tests/e2e/generate-client/*-consumer/api*.ts", "tests/smoke/**/*.yaml", "snapshot*.txt", + "tests/e2e/generate-client/*.snapshot.ts", "*.html", "*.hbs", "*.toml", diff --git a/.oxlintrc.json b/.oxlintrc.json index e98cdc4a7e..c6904b20a1 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -9,7 +9,15 @@ "builtin": true, "es2026": true }, - "ignorePatterns": ["packages/*/lib/**", "*.js", "*.cjs"], + "ignorePatterns": [ + "packages/*/lib/**", + "*.js", + "*.cjs", + "tests/e2e/generate-client/examples/*/src/api/**", + "tests/e2e/generate-client/examples/*/generate.ts", + "tests/e2e/generate-client/*.snapshot.ts", + "tests/e2e/generate-client/*-consumer/api.ts" + ], "rules": { "eslint/no-array-constructor": "error", "eslint/no-case-declarations": "error", @@ -68,6 +76,15 @@ "rules": { "eslint/no-console": "allow" } + }, + { + "files": [ + "tests/e2e/generate-client/examples/*/src/**", + "tests/e2e/generate-client/examples/*/worker.ts" + ], + "rules": { + "eslint/no-console": "allow" + } } ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5b55c42b7..f6e570e780 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -212,6 +212,7 @@ Run `npm test` to start both unit and e2e tests (and additionally typecheck the ### Unit tests Run unit tests with this command: `npm run unit`. +This command runs the suite for every package whose tests match the discovery glob — there is no per-package `npm test` script. Unit tests in the **cli** package are sensitive to top-level configuration file (**redocly.yaml**). diff --git a/README.md b/README.md index 7ffb8715b2..c08012b42e 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,21 @@ Respect is Redocly's community-edition product. Looking for something more? We a Learn more about [Respect](https://redocly.com/respect) and [get started with API contract testing](https://redocly.com/docs/respect/get-started). +### Generate a TypeScript client + +> ⚠️ **Experimental** — flags, output, and configuration may change in any minor release until declared stable. + +Turn an OpenAPI description (3.0/3.1/3.2 or Swagger 2.0) into a typed TypeScript client with the `generate-client` command. +The emitted client has zero runtime dependencies and runs in browsers, Node, Bun, Deno, and edge runtimes. + +```sh +redocly generate-client openapi.yaml --output src/client.ts +``` + +Inline types plus a typed client instance and one async function per operation, with auth, opt-in abort-aware retries, middleware, and typed Server-Sent Events. +The same command can also emit Zod schemas, TanStack Query / SWR hooks, MSW mocks, and more via `--generators`. +For detailed information, read the [ `generate-client` docs](./docs/@v2/commands/generate-client.md). + ### Transform an OpenAPI description If your OpenAPI description isn't everything you hoped it would be, enhance it with the Redocly [decorators](https://redocly.com/docs/cli/decorators) feature. diff --git a/docs/@v2/commands/generate-client.md b/docs/@v2/commands/generate-client.md new file mode 100644 index 0000000000..48c909bf0a --- /dev/null +++ b/docs/@v2/commands/generate-client.md @@ -0,0 +1,113 @@ +# `generate-client` + +{% admonition type="warning" name="Experimental" %} +`generate-client` is an experimental feature: its flags, generated output, configuration schema, and custom-generator API may change in any minor release until it's stable. +We'd love your feedback while we stabilize it. +{% /admonition %} + +## Introduction + +The `generate-client` command generates a typed TypeScript client from an OpenAPI 3.x description. +Swagger 2.0 descriptions are also accepted and normalized to the 3.x shape before generation. + +The generated client has zero runtime dependencies by default — it uses only web-standard APIs (`fetch`, `AbortController`, `URLSearchParams`), so it runs in browsers, Node, Bun, Deno, and edge runtimes. +By default it emits a single self-contained file with inline types and one async function per operation. + +The `` argument is a file path, a URL, or an [`apis:` alias](../configuration/index.md), resolved the same way as in other commands such as `bundle` and `lint`. +An alias, or a path matching an api's `root`, uses that api's `client` block and `clientOutput`; an unmatched path or URL uses the top-level `client` defaults. +With no argument, a client is generated for every api that declares a `client` block or a `clientOutput` (see [`client` configuration](../configuration/reference/client.md)). + +This page covers running the command; for the generated client's runtime API (auth, error handling, middleware, retries, and the add-on generators), see [Use the generated client](../guides/use-generated-client.md). + +## Usage + +```bash +redocly generate-client +redocly generate-client +redocly generate-client [--output=] [--output-mode=] [--runtime=] +redocly generate-client [--generator=] [--args-style=