Skip to content

feat: first-run DX, CI pipeline wiring & input fidelity#432

Open
jonaslagoni wants to merge 32 commits into
mainfrom
improvements_1
Open

feat: first-run DX, CI pipeline wiring & input fidelity#432
jonaslagoni wants to merge 32 commits into
mainfrom
improvements_1

Conversation

@jonaslagoni

Copy link
Copy Markdown
Contributor

Related Issue: none (sourced from .claude/thoughts/shared/improvements/2026-07-23-2146-improvements.md, ideas 1–3; plan .claude/thoughts/shared/plans/2026-07-23-first-run-ci-input-fidelity.md)

What

Fixes three verified problem areas end to end:

  1. First-run experience — config discovery matched the docs, codegen init can no longer silently produce an empty config, codegen generate warns on no-op channel generation, the internal base command is hidden, and the broken jsonschema-models example works again.
  2. Test pipeline — the blackbox matrix actually runs (all config × input pairs, sharded in CI), orphaned runtime suites are wired into scripts and CI, the website workflow triggers and builds (incl. the browser bundle), and every example is smoke-tested.
  3. Input-document fidelity — multi-message channels no longer drop payloads/headers, HTTP clients default their baseURL from the document's servers, and OpenAPI extraction accepts +json content types and warns (never silently drops) on skipped operations, cookie params, and webhooks.

Why

The first-run path had several silent failure modes: config file names the docs promised weren't discovered, init --no-tty without generator flags wrote generators: [] and still printed success, and generated channel barrels with no protocols were counted as output with no warning. The three-tier test pipeline had large blind spots — the blackbox suite was pinned to 1 of 23 schema files, several runtime suites ran nowhere in CI, the website workflow never triggered (wrong paths), and the browser bundle and examples had zero CI. And the input processors silently lost data: a payload-less message truncated the rest of a channel's payloads, only the first message's headers were kept, HTTP clients ignored declared servers, and OpenAPI dropped non-application/json JSON bodies and cookie/webhook constructs without a word.

Changes

First-run path

  • Single CONFIG_SEARCH_PLACES constant drives both cosmiconfig discovery and the not-found error (historical 7 names keep priority; cosmiconfig-standard names appended). Docs reordered to match.
  • codegen init: languages defaults to typescript; errors instead of writing empty generators; flag/answer composition fixed via shouldAskInclude; explicit --config-type wins over the prompt; new repeatable --channels-protocols flag seeds channel protocols; .gitignore entries derive from the generators actually built; warns when AsyncAPI channels end up with no protocols.
  • codegen generate warns (once) when a channels generator produces no protocol functions (barrel still emitted).
  • Abstract codegen base command hidden from the manifest and docs.
  • examples/jsonschema-models fixed: published dependency, codegen generate script, de-duplicated config, package-lock.json, and index.ts realigned with current generated models.
  • Support matrix corrected for OpenAPI channels/client in docs/generators/README.md, docs/getting-started/generators.md, and docs/inputs/openapi.md.

Input fidelity

  • AsyncAPI payloads: skip-and-warn on payload-less messages instead of break (keeps every payload-bearing member; collapses to a plain schema for a single one).
  • AsyncAPI headers: oneOf union across all header-bearing messages (channel-scoped id), mirroring the payloads builder; single-message output unchanged.
  • HTTP client baseURL defaults to the document's first HTTP(S) server (AsyncAPI + OpenAPI); non-HTTP/relative/variable-less servers skipped with a log. Runtime context.baseUrl still overrides.
  • OpenAPI: isJsonContentType accepts any +json subtype (prefers application/json); warns per operation on non-JSON-only content, per cookie parameter, and once per document with webhooks.

Test pipeline

  • Blackbox unfiltered and made input-type-aware (configs only pair with schema files of matching inputType), env-overridable, with a tracked-debt KNOWN_FAILING scaffold (empty — all pairs pass); CI sharded into 4 config buckets.
  • Runtime scripts/CI: test:regular + test:payload-types added to the aggregate, jsdoc.spec.ts wired in, CI matrix gains WebSocket/Organization/Payload-types (10 entries).
  • Website workflow rewritten to pull_request with real paths, PR checkout, browser-bundle build, and honest install+build (dropped the nonexistent lint, dead retry, and vercel-wait steps).
  • New examples-testing.yml smoke-tests every example against the packed CLI.

Docs: HTTP baseURL precedence, AsyncAPI multi-message union semantics, OpenAPI content-type support and limitations, and non-interactive init usage.

Testing

All verified locally (green):

  • Unit tests — npm test (706 passing, 1 skipped)
  • Build — npm run build
  • Lint + typecheck — npm run lint
  • Assets regenerate idempotently — npm run generate:assets (clean on second run)
  • Blackbox — full matrix (214 compatible config × schema pairs) passes; ran per-config bucket via BLACKBOX_CONFIG
  • Runtime tier (live Docker brokers) — http_client (114), regular (96), payload-types (270), organization (14), websocket (4), eventsource (6), nats (33), kafka (5), mqtt (8), amqp (7)

Notes

  • Generated-output changes (users regenerating will see diffs): payload unions gain previously-dropped members; multi-message channels with 2+ header-bearing messages get a oneOf header union (new type name/shape); HTTP baseURL defaults to the document server instead of http://localhost:3000; OpenAPI +json-only operations now produce payload models. Single-message channels and documents without servers are unchanged. Non-output behavior changes: config discovery finds more file names (existing names keep priority), init errors instead of silently writing empty configs, and new warnings are emitted.
  • Fixed one pre-existing runtime spec that Phase 19's CI wiring would have surfaced: format: time unmarshals to a string (no JS Date for a time-of-day).
  • Coordination: the in-flight 2026-07-17-headers-companion-interface.md work rewrites the same header generator sites and must rebase on this branch — do not run both concurrently.

🤖 Generated with Claude Code

jonaslagoni and others added 24 commits July 23, 2026 22:50
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single source of truth for cosmiconfig searchPlaces and the not-found
error message; primary codegen.* names keep search priority, extras
(package.json, .codegenrc*, .config/codegenrc*, codegen.config.*) appended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- languages flag defaults to typescript
- error (exit 1) instead of silently writing empty generators
- shouldAskInclude reads flags-or-answers so flag-driven includes compose
- explicit --config-type wins over the interactive prompt (setFromDefault)
- new --channels-protocols flag + interactive question seeds channels protocols
- warn on asyncapi channels with no protocols
- gitignore entries derived from the generators actually built

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- published dependency (^0) instead of workspace:*
- generate script runs 'codegen generate'
- drop duplicate codegen.config.js (keep discoverable codegen.js)
- add package-lock.json for CI npm ci
- realign index.ts with current generated models (reservedName, Date fields,
  enum types, nested model classes)
- README no longer claims marshalling the models generator does not emit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenAPI channels and client are registered, implemented and shipped
(examples/openapi-http-client); mark them supported in the matrices.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the break-on-first-payload-less loop with a filter that keeps every
payload-bearing message, warns about skipped ones, and collapses to a plain
schema when only one message carries a payload.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collect all header-bearing messages: 0 -> undefined, 1 -> unchanged single
schema, 2+ -> channel-scoped oneOf union mirroring the payloads builder;
warn about skipped header-less messages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AsyncAPI and OpenAPI callers now read the document's HTTP(S) servers and
pass them as the generated client's default baseURL (runtime context.baseUrl
still overrides). Non-HTTP servers, relative URLs, and unresolved server
variables are skipped with a warning/verbose log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- accept any +json content type (isJsonContentType), preferring application/json
- warn (never silently drop) when an operation's content is all non-JSON
- warn per operation with cookie parameters (dropped, unsupported)
- warn once when a document declares webhooks (ignored, unsupported)
- headers guard checks headerParams instead of allParameters

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- runtime aggregate test now runs test:regular and test:payload-types
- test:regular includes jsdoc.spec.ts
- CI matrix adds WebSocket, Organization, Payload Types entries (10 total)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- TEST_SPECIFIC_FILE/CONFIG default to full matrix (env-overridable for CI)
- pair configs only with schema files of matching inputType (removes the
  inherent asyncapi-config x jsonschema-file cross-product failures)
- KNOWN_FAILING tracked-debt scaffold with explicit skip (currently empty:
  all 214 compatible pairs pass locally)
- shard blackbox CI into 4 config buckets via BLACKBOX_CONFIG

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- pull_request (not pull_request_target) with real paths (website/**,
  src/browser/**, esbuild.browser.mjs) so it triggers and checks out the PR
- build the browser bundle (first CI coverage) and verify it lands in the site
- drop the nonexistent lint step, dead retry, and wait-for-vercel step

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New CI job packs the CLI, then for every example runs npm ci, installs the
packed tarball where the example depends on the published CLI, runs
npm run generate, and (if present) npm run build against the PR's build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- HTTP client base URL precedence (context.baseUrl > servers[0] > localhost)
- OpenAPI content types (+json), servers, and limitations (cookie/webhooks/non-JSON)
- OpenAPI inputs matrix: channels/client now supported
- AsyncAPI multi-message payload/header union semantics
- getting-started: non-interactive init flags + empty-generators guard
- regenerate openapi-http-client example (server-derived baseURL)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Command docs and JSON schemas already reflect the changes; this run only
updates markdown tocs and applies prettier to touched source files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Runtime regeneration reflects the new generators:
- HTTP clients default baseURL from document servers
- request-reply channel uses the multi-message oneOf header union
  (UserItemsHeaders / ItemResponseHeaders)
Fix pre-existing formats spec: format:time unmarshals to a string, not a Date.

Verified green: unit (706), blackbox full matrix (214 pairs), runtime tier
across http/nats/kafka/mqtt/amqp/regular/payload-types/organization/
websocket/eventsource, plus build, lint and typecheck.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonaslagoni
jonaslagoni requested a review from ALagoni97 as a code owner July 24, 2026 09:13
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
the-codegen-project Ready Ready Preview, Comment Jul 24, 2026 12:54pm
the-codegen-project-mcp Ready Ready Preview, Comment Jul 24, 2026 12:54pm

jonaslagoni and others added 2 commits July 24, 2026 11:41
A request/reply channel lists both its request and reply messages, and the
channel-level headers are consumed as the request headers. The header union
was folding the reply message's headers into the request type (e.g.
requestHeaders?: ItemRequestHeaders | ItemResponseHeaders), which is wrong —
a request must not carry the response's headers. Reply-only messages are now
excluded from the channel header union (a message used as both request and
reply is kept). Request/reply generated output returns to request-only headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- reformat typescript-library/typescript-nextjs asyncapi.json to spaces
  (tabs trigger a parser warning that the loader treats as fatal, failing
  codegen generate)
- scope the examples smoke test to generation only; running each example's
  own build couples CI to app toolchains/deps (Next.js, ajv) and is redundant
  with the blackbox tier, which already type-checks generated output

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jonaslagoni and others added 2 commits July 24, 2026 12:01
Extract buildChannelHeaderEntry and addMessageIds helpers so the reply-only
exclusion logic stays under the complexity limit (no behavior change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the per-example loop out of the CI bash script into scripts/testExamples.js
(exposed as 'npm run test:examples') so anyone can run the exact same check
locally. It builds+packs the CLI, then for each example runs npm ci, installs
the packed tarball with --no-save where the example depends on the CLI (so it
never rewrites committed manifests), and runs npm run generate. The tarball is
cleaned up and gitignored. CI now just calls the script.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- rename scripts/testExamples.js -> generateExamples.js; it (re)generates
  every example against the current build
- 'npm run generate:examples' refreshes the examples' committed output;
  'npm run test:examples' aliases it as the CI smoke test (fails on any error)
- release workflow regenerates examples against the released CLI and commits
  the refresh back ([skip ci], only when generation changed something) so the
  committed example output never drifts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…release step

Examples are refreshed through the same generate:assets hook that already
regenerates docs/schemas (run in prepare:pr and prepublishOnly), so no bespoke
release-workflow commit-back is needed. Reverts the release.yml change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
npm pack --json's filename field is scoped (@scope/name-x.y.z.tgz) on some npm
versions (e.g. npm 8 used by pr-testing) while the on-disk file is flattened
(scope-name-x.y.z.tgz), so joining root + that field pointed at a nonexistent
path and the tarball install failed on CI. Pack into an isolated temp dir and
read the actual produced file from disk instead; clean up the dir afterward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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