diff --git a/.github/workflows/blackbox-testing.yml b/.github/workflows/blackbox-testing.yml index ca7042f1..22c0cbdd 100644 --- a/.github/workflows/blackbox-testing.yml +++ b/.github/workflows/blackbox-testing.yml @@ -4,9 +4,24 @@ on: types: [opened, reopened, synchronize, ready_for_review] jobs: test: - name: BlackBox testing + name: BlackBox testing - ${{ matrix.bucket }} if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # The full 12-config x 23-file matrix is sharded into config buckets so + # no single job runs the whole (npm-install-heavy) cross-product. Each + # bucket lists config-name substrings (see test/blackbox/test_files.ts). + include: + - bucket: channels + configs: 'channels' + - bucket: client + configs: 'client' + - bucket: models + configs: 'payload,modelina' + - bucket: headers-params-jsonschema + configs: 'headers,parameters,jsonschema' steps: - name: Checkout repository uses: actions/checkout@v3 @@ -22,4 +37,6 @@ jobs: - name: Build library run: npm install && npm run build - name: Test output + env: + BLACKBOX_CONFIG: ${{ matrix.configs }} run: npm run test:blackbox \ No newline at end of file diff --git a/.github/workflows/examples-testing.yml b/.github/workflows/examples-testing.yml new file mode 100644 index 00000000..7c66cc6a --- /dev/null +++ b/.github/workflows/examples-testing.yml @@ -0,0 +1,35 @@ +name: Examples smoke testing + +on: + pull_request: + paths: + - 'src/**' + - 'examples/**' + - 'package.json' + branches: + - main + types: [opened, reopened, synchronize, ready_for_review] + +jobs: + examples: + name: Generate every example against the PR build + runs-on: ubuntu-latest + if: "github.event.pull_request.draft == false" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Check package-lock version + uses: asyncapi/.github/.github/actions/get-node-version-from-package-lock@master + id: lockversion + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "${{ steps.lockversion.outputs.version }}" + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install root dependencies + run: npm ci + - name: Smoke-test every example + # Builds + packs the CLI and generates every example against it. The + # exact same command runs locally: `npm run test:examples`. + run: npm run test:examples diff --git a/.github/workflows/runtime-testing.yml b/.github/workflows/runtime-testing.yml index 3c675991..ced73574 100644 --- a/.github/workflows/runtime-testing.yml +++ b/.github/workflows/runtime-testing.yml @@ -71,6 +71,18 @@ jobs: service: '' test-command: 'test:regular' needs-service: false + - test-type: 'WebSocket' + service: '' + test-command: 'test:websocket' + needs-service: false + - test-type: 'Organization' + service: '' + test-command: 'test:organization' + needs-service: false + - test-type: 'Payload Types' + service: '' + test-command: 'test:payload-types' + needs-service: false steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/website-pr-testing.yml b/.github/workflows/website-pr-testing.yml index b921eca6..552a02c4 100644 --- a/.github/workflows/website-pr-testing.yml +++ b/.github/workflows/website-pr-testing.yml @@ -1,53 +1,41 @@ name: Test website on: - pull_request_target: + pull_request: paths: - - 'website' - - 'website/vercel.json' + - 'website/**' + - 'src/browser/**' + - 'esbuild.browser.mjs' branches: - main types: [opened, reopened, synchronize, ready_for_review] jobs: - lighthouse-ci: - name: Lighthouse CI + website-build: + name: Build website (incl. browser bundle) runs-on: ubuntu-latest - + if: "github.event.pull_request.draft == false" steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check package-lock version uses: asyncapi/.github/.github/actions/get-node-version-from-package-lock@master id: lockversion - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "${{ steps.lockversion.outputs.version }}" cache: 'npm' cache-dependency-path: '**/package-lock.json' - - - name: Website Install dependencies - id: first-installation-website - run: cd website && npm install --loglevel verbose - continue-on-error: true - - - if: steps.first-installation-website.outputs.status == 'failure' - name: Website Clear NPM cache and install deps again - run: | - cd website && npm cache clean --force - cd website && npm install --loglevel verbose - - - name: Lint website - run: cd website && npm run lint - + - name: Install root dependencies + run: npm ci + - name: Build browser bundle + run: npm run build:browser + - name: Install website dependencies + run: cd website && npm ci + - name: Copy browser bundle into the website + run: cd website && npm run copy:bundle + - name: Verify the browser bundle was produced + run: test -f website/static/codegen.browser.mjs - name: Build website run: cd website && npm run build - - - name: Await Vercel Preview - uses: patrickedqvist/wait-for-vercel-preview@v1.3.2 - id: vercel - with: - token: ${{ secrets.GITHUB_TOKEN }} - max_timeout: 600 - check_interval: 10 diff --git a/.gitignore b/.gitignore index 159478f2..0c83dd10 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ coverage test/config/src/__gen__/ /playground test/codegen/generators/*/output -.claude \ No newline at end of file +.claude +# Packed CLI tarball from npm pack (e.g. test:examples) +the-codegen-project-cli-*.tgz diff --git a/docs/README.md b/docs/README.md index 69f702a1..4fb85dbe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -98,6 +98,8 @@ Connect AI assistants like Claude Code, Cursor, and Windsurf to The Codegen Proj + + diff --git a/docs/configurations.md b/docs/configurations.md index 2e2d4ba1..6a2a921b 100644 --- a/docs/configurations.md +++ b/docs/configurations.md @@ -29,14 +29,22 @@ For detailed usage instructions and all available options, see the [CLI usage do ## Configuration File Lookup -If no explicit configuration file is sat, it will be looked for in the following order: -- package.json +If no explicit configuration file is sat, it will be looked for in the following order (earlier names take priority): +- codegen.json +- codegen.yaml +- codegen.yml +- codegen.js +- codegen.ts +- codegen.mjs +- codegen.cjs +- package.json (the `codegen` property) - .codegenrc - .codegenrc.json - .codegenrc.yaml - .codegenrc.yml - .codegenrc.js - .codegenrc.ts +- .codegenrc.mjs - .codegenrc.cjs - .config/codegenrc - .config/codegenrc.json @@ -50,13 +58,6 @@ If no explicit configuration file is sat, it will be looked for in the following - codegen.config.ts - codegen.config.mjs - codegen.config.cjs -- codegen.json -- codegen.yaml -- codegen.yml -- codegen.js -- codegen.ts -- codegen.mjs -- codegen.cjs ## TypeScript Configuration diff --git a/docs/contributing.md b/docs/contributing.md index 05711334..0cfcd986 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -216,6 +216,8 @@ Prefix that follows specification is not enough though. Remember that the title + + diff --git a/docs/generators/README.md b/docs/generators/README.md index 077b4aa9..1ec4ffde 100644 --- a/docs/generators/README.md +++ b/docs/generators/README.md @@ -21,9 +21,11 @@ All available generators, across languages and inputs: | **Inputs** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`models`](./models.md) | [`custom`](./custom.md) | |---|---|---|---|---|---|---|---|---| | AsyncAPI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| OpenAPI | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | +| OpenAPI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | JSON Schema | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | +> OpenAPI `channels` and `client` generate an HTTP client — see the [`openapi-http-client` example](../../examples/openapi-http-client/). + | **Languages** | [`payloads`](./payloads.md) | [`parameters`](./parameters.md) | [`headers`](./headers.md) | [`types`](./types.md) | [`channels`](./channels.md) | [`client`](./client.md) | [`models`](./models.md) | [`custom`](./custom.md) | |---|---|---|---|---|---|---|---|---| | TypeScript | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index ed898735..600ea40e 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -136,6 +136,13 @@ Add a configuration file, either manually or through the CLI; codegen init ``` +For non-interactive/CI use, pass the generators explicitly (the `languages` flag defaults to `typescript`), for example: +```sh +codegen init --no-tty --input-type asyncapi --input-file ./asyncapi.yml \ + --include-payloads --include-channels --channels-protocols nats +``` +At least one generator must be selected — `codegen init` exits with an error rather than writing a configuration that would generate nothing. For AsyncAPI `channels`/`client`, choose the messaging protocols with `--channels-protocols` (repeatable). See [the usage documentation](../usage.md#codegen-init) for the full flag list. +
diff --git a/docs/getting-started/generators.md b/docs/getting-started/generators.md index 824a4241..cff7dec1 100644 --- a/docs/getting-started/generators.md +++ b/docs/getting-started/generators.md @@ -98,8 +98,8 @@ Different generators work with different input types: | `parameters` | ✅ | ✅ | ❌ | | `headers` | ✅ | ✅ | ❌ | | `types` | ✅ | ✅ | ❌ | -| `channels` | ✅ | ❌ | ❌ | -| `client` | ✅ | ❌ | ❌ | +| `channels` | ✅ | ✅ | ❌ | +| `client` | ✅ | ✅ | ❌ | | `models` | ✅ | ✅ | ✅ | | `custom` | ✅ | ✅ | ✅ | diff --git a/docs/inputs/asyncapi.md b/docs/inputs/asyncapi.md index caf7a720..f8daf402 100644 --- a/docs/inputs/asyncapi.md +++ b/docs/inputs/asyncapi.md @@ -137,6 +137,19 @@ Here's a complete basic AsyncAPI document example to get you started: } ``` +## Multi-message channels + +A channel may carry more than one message. Both the payload and header models reflect **all** of them: + +- **Payloads** — when two or more messages on a channel carry a payload, the generated payload is a `oneOf` union of every payload. A message with no payload is skipped from the union (with a warning) instead of truncating the rest; when exactly one message carries a payload, a plain (non-union) model is produced. +- **Headers** — likewise, when two or more messages declare headers, the channel's header model is a `oneOf` union of each message's headers. A single header-bearing message produces the same standalone header model as before; header-less messages alongside header-bearing ones are skipped with a warning. Reply-only messages are excluded from this union — the channel headers model the request side, so a request never carries the reply's headers. + +This means adding a payload-less or header-less message to a multi-message channel no longer silently drops the other messages' models. + +## Servers and base URL + +When a channel is generated for the `http_client` protocol, the document's first `http`/`https` server URL becomes the generated client's default `baseUrl`. Non-HTTP servers (e.g. `nats`, `kafka`) are ignored for the HTTP client. See [HTTP client base URL precedence](../protocols/http_client.md#base-url). + ## Extensions To customize the code generation through the AsyncAPI document, use the `x-the-codegen-project` [extension object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#specificationExtensions) with the following properties: diff --git a/docs/inputs/openapi.md b/docs/inputs/openapi.md index 8d60a5f1..46db6f60 100644 --- a/docs/inputs/openapi.md +++ b/docs/inputs/openapi.md @@ -16,8 +16,8 @@ Input support; `openapi` | [`parameters`](../generators/parameters.md) | ✅ | | [`headers`](../generators/headers.md) | ✅ | | [`types`](../generators/types.md) | ✅ | -| [`channels`](../generators/channels.md) | ❌ | -| [`client`](../generators/client.md) | ❌ | +| [`channels`](../generators/channels.md) | ✅ | +| [`client`](../generators/client.md) | ✅ | | [`custom`](../generators/custom.md) | ✅ | | [`models`](../generators/custom.md) | ✅ | @@ -36,6 +36,24 @@ Create a configuration file that specifies OpenAPI as the input type: } ``` +## Content types + +Request and response bodies are extracted from JSON content types: `application/json`, `text/json`, and any type whose subtype ends in `+json` (for example `application/hal+json` or `application/vnd.api+json`). When several JSON content types are present, `application/json` is preferred. + +When an operation declares a body but **none** of its content types are JSON (for example an XML-only response), no payload model is generated and a warning naming the operation and its content types is logged — the omission is never silent. + +## Servers and base URL + +For the `channels`/`client` HTTP client, the document's first `http`/`https` server URL becomes the generated default `baseUrl`. Server URL variables are substituted with their declared defaults; a server whose variable has no default, or whose URL is relative, is skipped with a log. See [HTTP client base URL precedence](../protocols/http_client.md#base-url). + +## Current limitations + +These constructs are not generated and are reported with a warning rather than dropped silently: + +- **Cookie parameters** — `in: cookie` parameters have no generated handling and are dropped (path, query, and header parameters are unaffected). +- **Webhooks** — the OpenAPI 3.1 `webhooks` section is not traversed. +- **Non-JSON bodies** — only JSON-family content types produce payload models (see [Content types](#content-types)). + ## Remote URL inputs `inputPath` accepts an `http://` or `https://` URL. Optional authentication (bearer token, API key, or custom headers) is configured via the `auth` field. Cross-spec `$ref` URLs are also resolved through the same auth-aware HTTP client. See the [configurations guide](../configurations.md#remote-url-inputs) for examples and the [auth scope and security considerations](../configurations.md#auth-scope-and-security-considerations) section — the configured headers are sent to every `$ref` target as well as the root URL. diff --git a/docs/migrations/v0.md b/docs/migrations/v0.md index 7567262c..4db6171a 100644 --- a/docs/migrations/v0.md +++ b/docs/migrations/v0.md @@ -244,6 +244,8 @@ import * as NodeFetch from 'node-fetch'; + + diff --git a/docs/protocols/http_client.md b/docs/protocols/http_client.md index 2a5d76f5..28e718cb 100644 --- a/docs/protocols/http_client.md +++ b/docs/protocols/http_client.md @@ -158,6 +158,16 @@ console.log(connect.data.safepayAccountId); See the runnable [`openapi-http-client` example](https://github.com/the-codegen-project/cli/tree/main/examples/openapi-http-client) for a complete, self-contained setup. +## Base URL + +Every generated call accepts an optional `baseUrl`. The value used at runtime follows this precedence, highest first: + +1. **`context.baseUrl`** passed to the call (e.g. `getUser({ baseUrl: 'https://api.example.com' })`) — always wins. +2. **The document's first HTTP(S) server** — when the AsyncAPI `servers` (or OpenAPI `servers`) section declares an `http`/`https` server, its URL becomes the generated default. Non-HTTP servers (nats, kafka, …), relative URLs, and OpenAPI server URLs whose variables have no default are skipped. +3. **`http://localhost:3000`** — the fallback when the document declares no usable HTTP(S) server. + +So a document with `servers: [{ url: 'https://api.example.com' }]` generates clients that target `https://api.example.com` by default, and you only pass `baseUrl` to override it (for example, to point at a staging environment). + ## Authentication The HTTP client uses a discriminated union for authentication, providing excellent TypeScript autocomplete support. diff --git a/docs/usage.md b/docs/usage.md index f516bca5..e29a6919 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -9,7 +9,7 @@ $ npm install -g @the-codegen-project/cli $ codegen COMMAND running command... $ codegen (--version) -@the-codegen-project/cli/0.79.0 linux-x64 node-v22.23.1 +@the-codegen-project/cli/0.79.0 darwin-arm64 node-v24.15.0 $ codegen --help [COMMAND] USAGE $ codegen COMMAND @@ -27,7 +27,6 @@ USAGE * [`codegen autocomplete [SHELL]`](#codegen-autocomplete-shell) -* [`codegen base`](#codegen-base) * [`codegen generate [FILE]`](#codegen-generate-file) * [`codegen help [COMMAND]`](#codegen-help-command) * [`codegen init`](#codegen-init) @@ -65,23 +64,6 @@ EXAMPLES _See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v3.2.45/src/commands/autocomplete/index.ts)_ -## `codegen base` - -``` -USAGE - $ codegen base [--json] [--no-color] [--debug | [-q | -v | --silent] | ] - -FLAGS - -q, --quiet Only show errors and warnings - -v, --verbose Show detailed output - --debug Show debug information - --json Output results as JSON for scripting - --no-color Disable colored output - --silent Suppress all output except fatal errors -``` - -_See code: [src/commands/base.ts](https://github.com/the-codegen-project/cli/blob/v0.79.0/src/commands/base.ts)_ - ## `codegen generate [FILE]` Generate code based on your configuration, use `init` to get started, `generate` to generate code from the configuration. @@ -141,40 +123,45 @@ Initialize The Codegen Project in your project USAGE $ codegen init [--json] [--no-color] [--debug | | [--silent | -v | -q]] [--help] [--input-file ] [--config-name ] [--input-type asyncapi|openapi|jsonschema] [--output-directory ] [--config-type - esm|json|yaml|ts] [--languages typescript] [--no-tty] [--include-payloads] [--include-headers] [--include-client] - [--include-parameters] [--include-channels] [--include-types] [--include-models] [--gitignore-generated] + esm|json|yaml|ts] [--languages typescript] [--channels-protocols + nats|kafka|mqtt|amqp|event_source|http_client|websocket] [--no-tty] [--include-payloads] [--include-headers] + [--include-client] [--include-parameters] [--include-channels] [--include-types] [--include-models] + [--gitignore-generated] FLAGS - -q, --quiet Only show errors and warnings - -v, --verbose Show detailed output - --config-name= [default: codegen] The name to use for the configuration file (dont include file - extension) - --config-type=