From 52fb68e2a7547fd911edd15b7587b3b20e124f2b Mon Sep 17 00:00:00 2001 From: benjamineckstein <13351939+benjamineckstein@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:31:14 +0200 Subject: [PATCH 1/3] chore(petstore-hono): regenerate client/server/hooks on demand instead of committing them Move user-owned src/schemas.ts out of generated/ into src/ so the entire generated/ directory can be gitignored. Update both generator configs (input_schema: "src/schemas.ts"). Wire pnpm generate into build, dev, and test:e2e scripts so generated output is always fresh. Add packages/petstore-hono/.gitignore covering generated/, test-results/, and playwright-report/. Add CI comment noting generated code is produced fresh from spec/api.json by the workspace generators. --- .github/workflows/ci.yml | 2 + packages/petstore-hono/.gitignore | 7 + packages/petstore-hono/CLAUDE.md | 16 +-- packages/petstore-hono/README.md | 22 ++-- .../petstore-hono/generated/client-config.ts | 74 ----------- packages/petstore-hono/generated/client.ts | 116 ----------------- packages/petstore-hono/generated/hooks.ts | 121 ------------------ packages/petstore-hono/generated/index.ts | 5 - packages/petstore-hono/generated/models.ts | 7 - packages/petstore-hono/generated/router.ts | 40 ------ packages/petstore-hono/generated/service.ts | 14 -- .../petstore-hono/generated/test-utils.ts | 44 ------- .../petstore-hono/openapi-server.config.json | 2 +- .../petstore-hono/openapi-zod-ts.config.json | 2 +- packages/petstore-hono/package.json | 6 +- .../{generated => src}/schemas.ts | 0 16 files changed, 33 insertions(+), 445 deletions(-) create mode 100644 packages/petstore-hono/.gitignore delete mode 100644 packages/petstore-hono/generated/client-config.ts delete mode 100644 packages/petstore-hono/generated/client.ts delete mode 100644 packages/petstore-hono/generated/hooks.ts delete mode 100644 packages/petstore-hono/generated/index.ts delete mode 100644 packages/petstore-hono/generated/models.ts delete mode 100644 packages/petstore-hono/generated/router.ts delete mode 100644 packages/petstore-hono/generated/service.ts delete mode 100644 packages/petstore-hono/generated/test-utils.ts rename packages/petstore-hono/{generated => src}/schemas.ts (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0eea639..edbe723 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,5 +96,7 @@ jobs: - name: Install Playwright browsers run: npx playwright install --with-deps chromium working-directory: packages/petstore-hono + # Generated code is not committed. test:e2e runs `pnpm generate` first, which + # rebuilds generated/ from spec/api.json using the workspace generators built above. - name: Run E2E tests run: pnpm --filter @codewithagents/petstore-hono run test:e2e diff --git a/packages/petstore-hono/.gitignore b/packages/petstore-hono/.gitignore new file mode 100644 index 0000000..74bc44e --- /dev/null +++ b/packages/petstore-hono/.gitignore @@ -0,0 +1,7 @@ +# Generated output — regenerated on demand, never committed. +# Run `pnpm generate` (or `pnpm build` / `pnpm test:e2e`) to recreate. +generated/ + +# Playwright artifacts +test-results/ +playwright-report/ diff --git a/packages/petstore-hono/CLAUDE.md b/packages/petstore-hono/CLAUDE.md index a881b3c..abe5565 100644 --- a/packages/petstore-hono/CLAUDE.md +++ b/packages/petstore-hono/CLAUDE.md @@ -5,9 +5,9 @@ Full-stack demo and e2e test harness for the `@codewithagents` OpenAPI toolchain ## Purpose - **Demo**: shows a real production-shaped project built on top of the generated code -- **E2E validation**: Playwright tests cover the full round-trip — browser form → Hono server → Zod validation → 422/201 response → React UI update +- **E2E validation**: Playwright tests cover the full round-trip: browser form to Hono server to Zod validation to 422/201 response to React UI update. -Not published to npm (`private: true`). No unit tests — integration-level testing lives in `packages/integration/`. +Not published to npm (`private: true`). No unit tests; integration-level testing lives in `packages/integration/`. ## Generators used @@ -17,18 +17,18 @@ Not published to npm (`private: true`). No unit tests — integration-level test | `openapi-server.config.json` | `service.ts`, `router.ts` (Hono + Zod validation) | | `openapi-react-query.config.json` | `hooks.ts`, `test-utils.ts` | -All three share `spec/api.json` and `generated/schemas.ts`. +All three share `spec/api.json`. The `input_schema` for `openapi-zod-ts` and `openapi-server` points at `src/schemas.ts`. -## `generated/schemas.ts` — user-owned +## `src/schemas.ts` — user-owned -Written by hand with real business rules (`.min(1, 'Name is required')`). Generators never overwrite it. Regenerating `generated/` is safe. +Written by hand with real business rules (`.min(1, 'Name is required')`). Generators never overwrite it. The `generated/` directory is gitignored and regenerated on demand. ## Dev / generate / test ```bash -pnpm run generate # re-run all three generators (does NOT touch schemas.ts) -pnpm run dev # Vite + Hono server in watch mode (concurrently) -pnpm run test:e2e # vite build → playwright test (Chromium) +pnpm run generate # re-run all three generators (does NOT touch src/schemas.ts) +pnpm run dev # generate + Vite + Hono server in watch mode (concurrently) +pnpm run test:e2e # generate + vite build + playwright test (Chromium) ``` ## CI diff --git a/packages/petstore-hono/README.md b/packages/petstore-hono/README.md index 323361e..0cb989c 100644 --- a/packages/petstore-hono/README.md +++ b/packages/petstore-hono/README.md @@ -17,11 +17,11 @@ A complete, runnable full-stack application that shows the entire `@codewithagen | React Query hooks | `hooks.ts` | ✅ `@codewithagents/openapi-react-query` | | Server interface (framework-agnostic) | `service.ts` | ✅ `@codewithagents/openapi-server` | | Router + Zod validation (Hono — demo choice) | `router.ts` | ✅ `@codewithagents/openapi-server` | -| Zod schemas | `schemas.ts` | ⚠️ Bootstrapped once, then **yours to own** | +| Zod schemas | `src/schemas.ts` | ⚠️ Bootstrapped once, then **yours to own** | | Business logic | `src/server/petService.ts` | ❌ You write this | | React UI | `src/client/App.tsx` | ❌ You write this | -**The key insight:** everything in `generated/` is disposable. Change `spec/api.json`, run `pnpm generate`, and the types, client, hooks, and router update automatically. Your business logic in `src/` is untouched because it implements a stable TypeScript interface. +**The key insight:** everything in `generated/` is disposable and not committed to git. Change `spec/api.json`, run `pnpm generate`, and the types, client, hooks, and router update automatically. Your business logic in `src/` is untouched because it implements a stable TypeScript interface. --- @@ -60,7 +60,7 @@ Open `http://localhost:5173` and you'll see a pet management UI. Add a pet, dele ## The Zod validation story -This is the part that ties everything together. Open `generated/schemas.ts`: +This is the part that ties everything together. Open `src/schemas.ts`: ```ts // Bootstrapped by openapi-zod-ts — this file is yours. Never overwritten. @@ -78,7 +78,7 @@ export const CreatePetRequestSchema = z.object({ }) ``` -The `.min(1, ...)` rules are custom — they weren't in the spec. This is business logic you own. +The `.min(1, ...)` rules are custom, they weren't in the spec. This is business logic you own. Now look at the generated `router.ts`: @@ -93,7 +93,7 @@ app.post('/pets', async (c) => { }) ``` -The router was regenerated (second pass) because `openapi-server.config.json` points at `input_schema: "generated/schemas.ts"`. The generator found `CreatePetRequestSchema`, wired it into the route, and now invalid requests return a structured `422` before they ever reach your service implementation. +The router was regenerated (second pass) because `openapi-server.config.json` points at `input_schema: "src/schemas.ts"`. The generator found `CreatePetRequestSchema`, wired it into the route, and now invalid requests return a structured `422` before they ever reach your service implementation. **The full round-trip:** ``` @@ -119,7 +119,7 @@ This runs all three generators in order: 2. `openapi-server` → `service.ts`, `router.ts` (with Zod validation wired in) 3. `openapi-react-query` → `hooks.ts`, `test-utils.ts` -**`generated/schemas.ts` is never overwritten.** It's bootstrapped on the very first run (if it doesn't exist), then left entirely to you. Add validation rules, refinements, and business logic freely. +**`src/schemas.ts` is never overwritten.** It's bootstrapped on the very first run (if it doesn't exist), then left entirely to you. Add validation rules, refinements, and business logic freely. --- @@ -210,7 +210,7 @@ function ItemList() { spec/ api.json OpenAPI 3.1 — single source of truth -generated/ Auto-generated — safe to delete and re-run +generated/ Auto-generated, gitignored — safe to delete and re-run models.ts TypeScript types (Pet, CreatePetRequest) client.ts Typed fetch functions (zero runtime deps) client-config.ts configureClient() — base URL + auth setup @@ -219,9 +219,9 @@ generated/ Auto-generated — safe to delete and re-run router.ts createRouter(service) — Hono routes + Zod validation hooks.ts useListPets, useCreatePet, useDeletePet (React Query) test-utils.ts MSW handlers for testing hooks - schemas.ts ⚠️ User-owned — bootstrapped once, never overwritten src/ + schemas.ts ⚠️ User-owned — bootstrapped once, never overwritten server/ petService.ts Implements PetstoreService (in-memory Map) index.ts Hono app — mounts router, serves React build @@ -245,7 +245,7 @@ openapi-react-query.config.json Generator config (React Query hooks) { "input_openapi": "spec/api.json", "output": "generated/", - "input_schema": "generated/schemas.ts" + "input_schema": "src/schemas.ts" } ``` @@ -255,7 +255,7 @@ openapi-react-query.config.json Generator config (React Query hooks) "input_openapi": "spec/api.json", "output": "generated/", "framework": "hono", - "input_schema": "generated/schemas.ts" + "input_schema": "src/schemas.ts" } ``` @@ -267,4 +267,4 @@ openapi-react-query.config.json Generator config (React Query hooks) } ``` -All three share the same spec and output directory. The `input_schema` points both `openapi-zod-ts` and `openapi-server` at the same `schemas.ts`, so client-side and server-side validation use identical rules. +All three share the same spec and output directory. The `input_schema` points both `openapi-zod-ts` and `openapi-server` at `src/schemas.ts`, so client-side and server-side validation use identical rules. The `generated/` directory is gitignored and recreated on every build. diff --git a/packages/petstore-hono/generated/client-config.ts b/packages/petstore-hono/generated/client-config.ts deleted file mode 100644 index 34704fa..0000000 --- a/packages/petstore-hono/generated/client-config.ts +++ /dev/null @@ -1,74 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -export interface ClientConfig { - /** Base URL for all API requests (e.g. 'https://api.example.com') */ - baseUrl: string - /** - * Bearer token or a function that returns one (supports async refresh). - * When provided, adds `Authorization: Bearer ` to every request. - */ - token?: string | (() => string | Promise) - /** - * Fetch credentials mode. Use 'include' for cookie-based auth. - * Defaults to 'same-origin'. - */ - credentials?: RequestCredentials - /** Additional headers sent with every request */ - headers?: Record - /** - * Global error hook called with every non-2xx response error before it is - * thrown. Use for logging, monitoring, or triggering auth refresh flows. - * - * @example - * configureClient({ - * onError: (err) => Sentry.captureException(err), - * }) - */ - onError?: (err: Error) => void - /** - * Default AbortSignal for every request. Useful for cancelling in-flight - * requests (e.g. on route change). Overridable per call via the config param. - */ - signal?: AbortSignal - /** - * Default timeout in milliseconds. When set, every request is aborted with a - * TimeoutError after the specified duration. Overridable per call. - */ - timeout?: number - /** - * Request interceptor called before every fetch. Receives the resolved URL and - * RequestInit; may return an object with optional url/init overrides. - * - * @example - * configureClient({ - * onRequest: ({ url, init }) => ({ init: { ...init, headers: { ...init.headers, 'X-Request-Id': crypto.randomUUID() } } }), - * }) - */ - onRequest?: (req: { - url: string - init: RequestInit - }) => - | void - | { url?: string; init?: RequestInit } - | Promise - /** - * Custom fetch implementation. Defaults to globalThis.fetch, resolved at - * call time so test mocks of globalThis.fetch are always honoured. - */ - fetch?: typeof globalThis.fetch -} - -let _config: ClientConfig = { - baseUrl: '', - credentials: 'same-origin', -} - -/** Configure the API client. Call once at app startup before making any requests. */ -export function configureClient(config: ClientConfig): void { - _config = { ..._config, ...config } -} - -/** @internal — used by generated fetch functions */ -export function getConfig(): Readonly { - return _config -} diff --git a/packages/petstore-hono/generated/client.ts b/packages/petstore-hono/generated/client.ts deleted file mode 100644 index 143f483..0000000 --- a/packages/petstore-hono/generated/client.ts +++ /dev/null @@ -1,116 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -import type { CreatePetRequest, Pet } from './models.js' -import { getConfig, type ClientConfig } from './client-config.js' -import { z } from 'zod' -import { CreatePetRequestSchema, PetSchema } from './schemas.js' - -export class ApiError extends Error { - constructor( - public readonly status: Status, - public readonly body: Body - ) { - super(`API error ${status}`) - this.name = 'ApiError' - } -} - -type _FetchResponse = Awaited> - -function _buildSignal( - signal: AbortSignal | undefined, - timeout: number | undefined -): AbortSignal | undefined { - if (timeout === undefined) return signal - const _ts = AbortSignal.timeout(timeout) - if (signal === undefined) return _ts - if (typeof AbortSignal.any === 'function') return AbortSignal.any([signal, _ts]) - const _ctrl = new AbortController() - const _abort = (s: AbortSignal) => () => { - if (!_ctrl.signal.aborted) _ctrl.abort(s.reason) - } - signal.addEventListener('abort', _abort(signal), { once: true }) - _ts.addEventListener('abort', _abort(_ts), { once: true }) - return _ctrl.signal -} - -async function _request( - method: string, - path: string, - opts: { - searchParams?: URLSearchParams - body?: unknown - signal?: AbortSignal - }, - config?: Partial -): Promise<_FetchResponse> { - const { - baseUrl, - headers, - onError, - signal: _cfgSignal, - timeout, - onRequest, - fetch: _configFetch, - } = { ...getConfig(), ...config } - const base = baseUrl ? baseUrl.replace(/\/$/, '') : '' - const qs = opts.searchParams?.toString() ?? '' - const url = qs ? `${base}${path}?${qs}` : `${base}${path}` - const fetch = _configFetch ?? globalThis.fetch - let _url = url - let _init: RequestInit = { - method, - headers: { - ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}), - ...headers, - }, - ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}), - } - if (onRequest) { - const _or = await onRequest({ url: _url, init: _init }) - if (_or) { - if (_or.url !== undefined) _url = _or.url - if (_or.init !== undefined) _init = { ..._init, ..._or.init } - } - } - const _rawSignal = opts.signal ?? _cfgSignal - const _resolvedSignal = _buildSignal(_rawSignal, timeout) - if (_resolvedSignal !== undefined) _init = { ..._init, signal: _resolvedSignal } - const res = await fetch(_url, _init) - if (!res.ok) { - const err = new ApiError(res.status, await res.json().catch(() => null)) - onError?.(err) - throw err - } - return res -} - -export async function listPets( - params?: { - species?: string - }, - config?: Partial -): Promise { - const searchParams = new URLSearchParams() - if (params?.species != null) searchParams.set('species', String(params.species)) - const res = await _request('GET', '/pets', { searchParams }, config) - return z.array(PetSchema).parse(await res.json()) -} - -export async function createPet( - body: CreatePetRequest, - config?: Partial -): Promise { - CreatePetRequestSchema.parse(body) - const res = await _request('POST', '/pets', { body }, config) - return PetSchema.parse(await res.json()) -} - -export async function getPet(id: string, config?: Partial): Promise { - const res = await _request('GET', `/pets/${encodeURIComponent(id)}`, {}, config) - return PetSchema.parse(await res.json()) -} - -export async function deletePet(id: string, config?: Partial): Promise { - await _request('DELETE', `/pets/${encodeURIComponent(id)}`, {}, config) -} diff --git a/packages/petstore-hono/generated/hooks.ts b/packages/petstore-hono/generated/hooks.ts deleted file mode 100644 index 5597d5d..0000000 --- a/packages/petstore-hono/generated/hooks.ts +++ /dev/null @@ -1,121 +0,0 @@ -// This file is auto-generated by @codewithagents/openapi-react-query — do not edit - -import { - queryOptions, - useQuery, - type UseQueryOptions, - useMutation, - type UseMutationOptions, -} from '@tanstack/react-query' -import { createPet, deletePet, getPet, listPets, type ApiError } from './client.js' - -// ── Query key factories ────────────────────────────────────── - -export const petKeys = { - all: () => ['pets'] as const, - list: (params?: Parameters[0]) => ['pets', 'list', params] as const, - detail: (id: string) => ['pets', id] as const, -} - -// ── Query options factories ────────────────────────────────── - -export function listPetsQueryOptions( - params?: Parameters[0], - options?: Omit< - UseQueryOptions>, ApiError>, - 'queryKey' | 'queryFn' - > -) { - return queryOptions>, ApiError>({ - queryKey: petKeys.list(params), - queryFn: () => listPets(params), - staleTime: 0, - gcTime: 300000, - ...options, - }) -} - -export function getPetQueryOptions( - id: string, - options?: Omit< - UseQueryOptions>, ApiError>, - 'queryKey' | 'queryFn' - > -) { - return queryOptions>, ApiError>({ - queryKey: petKeys.detail(id), - queryFn: () => getPet(id), - staleTime: 0, - gcTime: 300000, - ...options, - }) -} - -// ── Queries ────────────────────────────────────────────────── - -export function useListPets( - params?: Parameters[0], - options?: Omit< - UseQueryOptions>, ApiError>, - 'queryKey' | 'queryFn' - > -) { - return useQuery>, ApiError>({ - queryKey: petKeys.list(params), - queryFn: () => listPets(params), - staleTime: 0, - gcTime: 300000, - ...options, - }) -} - -export function useGetPet( - id: string | undefined | null, - options?: Omit< - UseQueryOptions>, ApiError>, - 'queryKey' | 'queryFn' - > -) { - return useQuery>, ApiError>({ - queryKey: petKeys.detail(id!), - queryFn: () => getPet(id!), - staleTime: 0, - gcTime: 300000, - enabled: id != null && (options?.enabled ?? true), - ...options, - }) -} - -// ── Mutations ──────────────────────────────────────────────── - -export function useCreatePet( - options?: Omit< - UseMutationOptions< - Awaited>, - ApiError, - Parameters[0] - >, - 'mutationFn' - > -) { - return useMutation< - Awaited>, - ApiError, - Parameters[0] - >({ - mutationFn: (vars) => createPet(vars), - ...options, - }) -} - -export function useDeletePet( - options?: Omit< - UseMutationOptions>, ApiError, string>, - 'mutationFn' - > -) { - return useMutation>, ApiError, string>({ - mutationFn: (id) => deletePet(id), - ...options, - }) -} diff --git a/packages/petstore-hono/generated/index.ts b/packages/petstore-hono/generated/index.ts deleted file mode 100644 index 41689a6..0000000 --- a/packages/petstore-hono/generated/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -export * from './models.js' -export * from './client-config.js' -export * from './client.js' diff --git a/packages/petstore-hono/generated/models.ts b/packages/petstore-hono/generated/models.ts deleted file mode 100644 index bffd133..0000000 --- a/packages/petstore-hono/generated/models.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This file is auto-generated by openapi-zod-ts - do not edit -import type { z } from 'zod' -import type { PetSchema, CreatePetRequestSchema } from './schemas.js' - -export type Pet = z.infer - -export type CreatePetRequest = z.infer diff --git a/packages/petstore-hono/generated/router.ts b/packages/petstore-hono/generated/router.ts deleted file mode 100644 index ad8b9c2..0000000 --- a/packages/petstore-hono/generated/router.ts +++ /dev/null @@ -1,40 +0,0 @@ -// This file is auto-generated. Do not edit manually. - -import { Hono } from 'hono' -import type { CreatePetRequest } from './models.js' -import type { PetstoreService } from './service.js' -import { z } from 'zod' -import { CreatePetRequestSchema } from './schemas.js' - -export function createRouter(service: PetstoreService): Hono { - const app = new Hono() - - app.get('/pets', async (c) => { - const params = { - species: c.req.query('species') ?? undefined, - } - return c.json(await service.listPets(params)) - }) - - app.post('/pets', async (c) => { - const body = await c.req.json() - // Validate request body: returns 422 with Zod issues on failure - const parseResult = CreatePetRequestSchema.safeParse(body) - if (!parseResult.success) { - return c.json({ error: 'Invalid request body', issues: parseResult.error.issues }, 422) - } - const validatedBody = parseResult.data - return c.json(await service.createPet(validatedBody), 201) - }) - - app.get('/pets/:id', async (c) => { - return c.json(await service.getPet(c.req.param('id'))) - }) - - app.delete('/pets/:id', async (c) => { - await service.deletePet(c.req.param('id')) - return new Response(null, { status: 204 }) - }) - - return app -} diff --git a/packages/petstore-hono/generated/service.ts b/packages/petstore-hono/generated/service.ts deleted file mode 100644 index 89af88e..0000000 --- a/packages/petstore-hono/generated/service.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file is auto-generated. Do not edit manually. - -import type { CreatePetRequest, Pet } from './models.js' - -export interface PetstoreService { - /** GET /pets */ - listPets(params?: { species?: string }): Promise - /** POST /pets */ - createPet(body: CreatePetRequest): Promise - /** GET /pets/{id} */ - getPet(id: string): Promise - /** DELETE /pets/{id} */ - deletePet(id: string): Promise -} diff --git a/packages/petstore-hono/generated/test-utils.ts b/packages/petstore-hono/generated/test-utils.ts deleted file mode 100644 index 7be9aac..0000000 --- a/packages/petstore-hono/generated/test-utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -// This file is auto-generated by @codewithagents/openapi-react-query — do not edit - -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { createElement, type ReactNode } from 'react' - -/** - * Creates a QueryClient pre-configured for testing. - * - * Differences from the production default: - * - `retry: false` — tests should not retry on failure - * - `gcTime: 0` — no garbage-collection delay between tests - */ -export function createTestQueryClient(): QueryClient { - return new QueryClient({ - defaultOptions: { - queries: { - retry: false, - gcTime: 0, - }, - mutations: { - retry: false, - }, - }, - }) -} - -/** - * Returns a wrapper component that provides a `QueryClientProvider`. - * Pass it as the `wrapper` option to `renderHook` from `@testing-library/react`. - * - * @example - * ```ts - * const queryClient = createTestQueryClient() - * const wrapper = createWrapper(queryClient) - * const { result } = renderHook(() => useListTasks(), { wrapper }) - * ``` - */ -export function createWrapper( - queryClient: QueryClient -): ({ children }: { children: ReactNode }) => ReactNode { - return function Wrapper({ children }: { children: ReactNode }): ReactNode { - return createElement(QueryClientProvider, { client: queryClient }, children) - } -} diff --git a/packages/petstore-hono/openapi-server.config.json b/packages/petstore-hono/openapi-server.config.json index 34f24f0..ac66784 100644 --- a/packages/petstore-hono/openapi-server.config.json +++ b/packages/petstore-hono/openapi-server.config.json @@ -2,5 +2,5 @@ "input_openapi": "spec/api.json", "output": "generated/", "framework": "hono", - "input_schema": "generated/schemas.ts" + "input_schema": "src/schemas.ts" } diff --git a/packages/petstore-hono/openapi-zod-ts.config.json b/packages/petstore-hono/openapi-zod-ts.config.json index 534fddd..ffa5736 100644 --- a/packages/petstore-hono/openapi-zod-ts.config.json +++ b/packages/petstore-hono/openapi-zod-ts.config.json @@ -1,5 +1,5 @@ { "input_openapi": "spec/api.json", "output": "generated/", - "input_schema": "generated/schemas.ts" + "input_schema": "src/schemas.ts" } diff --git a/packages/petstore-hono/package.json b/packages/petstore-hono/package.json index 1a3cd6b..a5d9a5f 100644 --- a/packages/petstore-hono/package.json +++ b/packages/petstore-hono/package.json @@ -5,10 +5,10 @@ "type": "module", "scripts": { "generate": "openapi-zod-ts && openapi-server && openapi-react-query", - "dev": "concurrently \"vite\" \"tsx watch src/server/index.ts\"", - "build": "vite build", + "dev": "pnpm generate && concurrently \"vite\" \"tsx watch src/server/index.ts\"", + "build": "pnpm generate && vite build", "start": "tsx src/server/index.ts", - "test:e2e": "vite build && playwright test" + "test:e2e": "pnpm generate && vite build && playwright test" }, "dependencies": { "openapi-zod-ts": "workspace:*", diff --git a/packages/petstore-hono/generated/schemas.ts b/packages/petstore-hono/src/schemas.ts similarity index 100% rename from packages/petstore-hono/generated/schemas.ts rename to packages/petstore-hono/src/schemas.ts From c6dfa10243e5b174ed05469a54c365d74d60d8e2 Mon Sep 17 00:00:00 2001 From: benjamineckstein <13351939+benjamineckstein@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:08:40 +0200 Subject: [PATCH 2/3] fix(petstore-hono): invoke generator CLIs via node to avoid missing bin shims pnpm drops bin shims at install time if the target dist file does not yet exist (ENOENT warning). In CI, generator packages have no dist/ after a fresh pnpm install --frozen-lockfile, so the openapi-zod-ts, openapi-server, and openapi-react-query shims are never created. The recursive build then fails with "openapi-zod-ts: not found" even though pnpm builds the generators before petstore-hono (topological order). Replace bare CLI names in the generate script with explicit node invocations via the workspace-symlinked dist paths. No bin shims needed; the dist files are present by the time petstore-hono runs because pnpm respects the workspace dependency order. Verified from a clean state: removed generator dists, ran pnpm install --frozen-lockfile then pnpm -r run build. All three generators ran successfully and vite build produced a 289 kB bundle. --- packages/petstore-hono/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/petstore-hono/package.json b/packages/petstore-hono/package.json index a5d9a5f..8695069 100644 --- a/packages/petstore-hono/package.json +++ b/packages/petstore-hono/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "generate": "openapi-zod-ts && openapi-server && openapi-react-query", + "generate": "node node_modules/openapi-zod-ts/dist/cli.cjs && node node_modules/@codewithagents/openapi-server/dist/cli.cjs && node node_modules/@codewithagents/openapi-react-query/dist/cli.js", "dev": "pnpm generate && concurrently \"vite\" \"tsx watch src/server/index.ts\"", "build": "pnpm generate && vite build", "start": "tsx src/server/index.ts", From 91b1505df6fd20526381fc58fa7ad32cdd9d1b8b Mon Sep 17 00:00:00 2001 From: benjamineckstein <13351939+benjamineckstein@users.noreply.github.com> Date: Mon, 15 Jun 2026 19:16:45 +0200 Subject: [PATCH 3/3] chore(petstore-express,petstore-fastify): regenerate on demand, fix schema path Both packages shared petstore-hono's schema via input_schema: "../petstore-hono/generated/schemas.ts". Moving hono's schema to src/ broke the import in their committed generated/router.ts. Apply the same regenerate-on-demand pattern as petstore-hono: - Fix input_schema in openapi-server.config.json to point at ../petstore-hono/src/schemas.ts (always committed, no build dep). - Replace bare CLI names in generate scripts with explicit node invocations to bypass the pnpm bin-shim ENOENT failure on CI. - Wire pnpm generate into test and start so generated/ is always fresh before the code runs. - Add .gitignore covering generated/ and test-results/. - git rm --cached the 12 committed generated files. Verified clean-room: removed generator dists, ran pnpm install --frozen-lockfile then pnpm -r run build then pnpm -r run test. All 2,458 tests passed across all packages including petstore-express (5 tests) and petstore-fastify (5 tests). Regenerated routers now import from ../../petstore-hono/src/schemas.js. Zero remaining references to petstore-hono/generated in the repo. --- packages/petstore-express/.gitignore | 6 + .../generated/client-config.ts | 74 ------------ packages/petstore-express/generated/client.ts | 113 ------------------ packages/petstore-express/generated/index.ts | 5 - packages/petstore-express/generated/models.ts | 12 -- packages/petstore-express/generated/router.ts | 43 ------- .../petstore-express/generated/service.ts | 14 --- .../openapi-server.config.json | 2 +- packages/petstore-express/package.json | 6 +- packages/petstore-fastify/.gitignore | 6 + .../generated/client-config.ts | 74 ------------ packages/petstore-fastify/generated/client.ts | 113 ------------------ packages/petstore-fastify/generated/index.ts | 5 - packages/petstore-fastify/generated/models.ts | 12 -- packages/petstore-fastify/generated/router.ts | 37 ------ .../petstore-fastify/generated/service.ts | 14 --- .../openapi-server.config.json | 2 +- packages/petstore-fastify/package.json | 6 +- 18 files changed, 20 insertions(+), 524 deletions(-) create mode 100644 packages/petstore-express/.gitignore delete mode 100644 packages/petstore-express/generated/client-config.ts delete mode 100644 packages/petstore-express/generated/client.ts delete mode 100644 packages/petstore-express/generated/index.ts delete mode 100644 packages/petstore-express/generated/models.ts delete mode 100644 packages/petstore-express/generated/router.ts delete mode 100644 packages/petstore-express/generated/service.ts create mode 100644 packages/petstore-fastify/.gitignore delete mode 100644 packages/petstore-fastify/generated/client-config.ts delete mode 100644 packages/petstore-fastify/generated/client.ts delete mode 100644 packages/petstore-fastify/generated/index.ts delete mode 100644 packages/petstore-fastify/generated/models.ts delete mode 100644 packages/petstore-fastify/generated/router.ts delete mode 100644 packages/petstore-fastify/generated/service.ts diff --git a/packages/petstore-express/.gitignore b/packages/petstore-express/.gitignore new file mode 100644 index 0000000..b2bad0d --- /dev/null +++ b/packages/petstore-express/.gitignore @@ -0,0 +1,6 @@ +# Generated output — regenerated on demand, never committed. +# Run `pnpm generate` (or `pnpm test`) to recreate. +generated/ + +# Test artifacts +test-results/ diff --git a/packages/petstore-express/generated/client-config.ts b/packages/petstore-express/generated/client-config.ts deleted file mode 100644 index 34704fa..0000000 --- a/packages/petstore-express/generated/client-config.ts +++ /dev/null @@ -1,74 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -export interface ClientConfig { - /** Base URL for all API requests (e.g. 'https://api.example.com') */ - baseUrl: string - /** - * Bearer token or a function that returns one (supports async refresh). - * When provided, adds `Authorization: Bearer ` to every request. - */ - token?: string | (() => string | Promise) - /** - * Fetch credentials mode. Use 'include' for cookie-based auth. - * Defaults to 'same-origin'. - */ - credentials?: RequestCredentials - /** Additional headers sent with every request */ - headers?: Record - /** - * Global error hook called with every non-2xx response error before it is - * thrown. Use for logging, monitoring, or triggering auth refresh flows. - * - * @example - * configureClient({ - * onError: (err) => Sentry.captureException(err), - * }) - */ - onError?: (err: Error) => void - /** - * Default AbortSignal for every request. Useful for cancelling in-flight - * requests (e.g. on route change). Overridable per call via the config param. - */ - signal?: AbortSignal - /** - * Default timeout in milliseconds. When set, every request is aborted with a - * TimeoutError after the specified duration. Overridable per call. - */ - timeout?: number - /** - * Request interceptor called before every fetch. Receives the resolved URL and - * RequestInit; may return an object with optional url/init overrides. - * - * @example - * configureClient({ - * onRequest: ({ url, init }) => ({ init: { ...init, headers: { ...init.headers, 'X-Request-Id': crypto.randomUUID() } } }), - * }) - */ - onRequest?: (req: { - url: string - init: RequestInit - }) => - | void - | { url?: string; init?: RequestInit } - | Promise - /** - * Custom fetch implementation. Defaults to globalThis.fetch, resolved at - * call time so test mocks of globalThis.fetch are always honoured. - */ - fetch?: typeof globalThis.fetch -} - -let _config: ClientConfig = { - baseUrl: '', - credentials: 'same-origin', -} - -/** Configure the API client. Call once at app startup before making any requests. */ -export function configureClient(config: ClientConfig): void { - _config = { ..._config, ...config } -} - -/** @internal — used by generated fetch functions */ -export function getConfig(): Readonly { - return _config -} diff --git a/packages/petstore-express/generated/client.ts b/packages/petstore-express/generated/client.ts deleted file mode 100644 index 8647866..0000000 --- a/packages/petstore-express/generated/client.ts +++ /dev/null @@ -1,113 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -import type { CreatePetRequest, Pet } from './models.js' -import { getConfig, type ClientConfig } from './client-config.js' - -export class ApiError extends Error { - constructor( - public readonly status: Status, - public readonly body: Body - ) { - super(`API error ${status}`) - this.name = 'ApiError' - } -} - -type _FetchResponse = Awaited> - -function _buildSignal( - signal: AbortSignal | undefined, - timeout: number | undefined -): AbortSignal | undefined { - if (timeout === undefined) return signal - const _ts = AbortSignal.timeout(timeout) - if (signal === undefined) return _ts - if (typeof AbortSignal.any === 'function') return AbortSignal.any([signal, _ts]) - const _ctrl = new AbortController() - const _abort = (s: AbortSignal) => () => { - if (!_ctrl.signal.aborted) _ctrl.abort(s.reason) - } - signal.addEventListener('abort', _abort(signal), { once: true }) - _ts.addEventListener('abort', _abort(_ts), { once: true }) - return _ctrl.signal -} - -async function _request( - method: string, - path: string, - opts: { - searchParams?: URLSearchParams - body?: unknown - signal?: AbortSignal - }, - config?: Partial -): Promise<_FetchResponse> { - const { - baseUrl, - headers, - onError, - signal: _cfgSignal, - timeout, - onRequest, - fetch: _configFetch, - } = { ...getConfig(), ...config } - const base = baseUrl ? baseUrl.replace(/\/$/, '') : '' - const qs = opts.searchParams?.toString() ?? '' - const url = qs ? `${base}${path}?${qs}` : `${base}${path}` - const fetch = _configFetch ?? globalThis.fetch - let _url = url - let _init: RequestInit = { - method, - headers: { - ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}), - ...headers, - }, - ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}), - } - if (onRequest) { - const _or = await onRequest({ url: _url, init: _init }) - if (_or) { - if (_or.url !== undefined) _url = _or.url - if (_or.init !== undefined) _init = { ..._init, ..._or.init } - } - } - const _rawSignal = opts.signal ?? _cfgSignal - const _resolvedSignal = _buildSignal(_rawSignal, timeout) - if (_resolvedSignal !== undefined) _init = { ..._init, signal: _resolvedSignal } - const res = await fetch(_url, _init) - if (!res.ok) { - const err = new ApiError(res.status, await res.json().catch(() => null)) - onError?.(err) - throw err - } - return res -} - -export async function listPets( - params?: { - species?: string - }, - config?: Partial -): Promise { - const searchParams = new URLSearchParams() - if (params?.species != null) searchParams.set('species', String(params.species)) - const res = await _request('GET', '/pets', { searchParams }, config) - return res.json() -} - -export async function createPet( - body: CreatePetRequest, - config?: Partial -): Promise { - const res = await _request('POST', '/pets', { body }, config) - return res.json() -} - -export async function getPet(id: string, config?: Partial): Promise { - const res = await _request('GET', `/pets/${encodeURIComponent(id)}`, {}, config) - return res.json() -} - -export async function deletePet(id: string, config?: Partial): Promise { - await _request('DELETE', `/pets/${encodeURIComponent(id)}`, {}, config) -} diff --git a/packages/petstore-express/generated/index.ts b/packages/petstore-express/generated/index.ts deleted file mode 100644 index 41689a6..0000000 --- a/packages/petstore-express/generated/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -export * from './models.js' -export * from './client-config.js' -export * from './client.js' diff --git a/packages/petstore-express/generated/models.ts b/packages/petstore-express/generated/models.ts deleted file mode 100644 index 953707b..0000000 --- a/packages/petstore-express/generated/models.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file is auto-generated by openapi-zod-ts - do not edit - -export interface Pet { - id: string - name: string - species: string -} - -export interface CreatePetRequest { - name: string - species: string -} diff --git a/packages/petstore-express/generated/router.ts b/packages/petstore-express/generated/router.ts deleted file mode 100644 index 0ff60b2..0000000 --- a/packages/petstore-express/generated/router.ts +++ /dev/null @@ -1,43 +0,0 @@ -// This file is auto-generated. Do not edit manually. -// Express: apply express.json() middleware before mounting this router so req.body is populated. - -import { Router } from 'express' -import type { Request, Response } from 'express' -import type { CreatePetRequest } from './models.js' -import type { PetstoreService } from './service.js' -import { z } from 'zod' -import { CreatePetRequestSchema } from '../../petstore-hono/generated/schemas.js' - -export function createRouter(service: PetstoreService): Router { - const router = Router() - - router.get('/pets', async (req: Request, res: Response) => { - const params = { - species: req.query['species'] as string | undefined, - } - res.json(await service.listPets(params)) - }) - - router.post('/pets', async (req: Request, res: Response) => { - // Validate request body: returns 422 with Zod issues on failure - const parseResult = CreatePetRequestSchema.safeParse(req.body) - if (!parseResult.success) { - return void res - .status(422) - .json({ error: 'Invalid request body', issues: parseResult.error.issues }) - } - const validatedBody = parseResult.data - res.status(201).json(await service.createPet(validatedBody)) - }) - - router.get('/pets/:id', async (req: Request, res: Response) => { - res.json(await service.getPet(req.params['id']!)) - }) - - router.delete('/pets/:id', async (req: Request, res: Response) => { - await service.deletePet(req.params['id']!) - res.status(204).end() - }) - - return router -} diff --git a/packages/petstore-express/generated/service.ts b/packages/petstore-express/generated/service.ts deleted file mode 100644 index 89af88e..0000000 --- a/packages/petstore-express/generated/service.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file is auto-generated. Do not edit manually. - -import type { CreatePetRequest, Pet } from './models.js' - -export interface PetstoreService { - /** GET /pets */ - listPets(params?: { species?: string }): Promise - /** POST /pets */ - createPet(body: CreatePetRequest): Promise - /** GET /pets/{id} */ - getPet(id: string): Promise - /** DELETE /pets/{id} */ - deletePet(id: string): Promise -} diff --git a/packages/petstore-express/openapi-server.config.json b/packages/petstore-express/openapi-server.config.json index f5fa2bb..37dd981 100644 --- a/packages/petstore-express/openapi-server.config.json +++ b/packages/petstore-express/openapi-server.config.json @@ -2,5 +2,5 @@ "input_openapi": "../petstore-hono/spec/api.json", "output": "generated/", "framework": "express", - "input_schema": "../petstore-hono/generated/schemas.ts" + "input_schema": "../petstore-hono/src/schemas.ts" } diff --git a/packages/petstore-express/package.json b/packages/petstore-express/package.json index c69e1fe..2c448eb 100644 --- a/packages/petstore-express/package.json +++ b/packages/petstore-express/package.json @@ -4,9 +4,9 @@ "private": true, "type": "module", "scripts": { - "generate": "openapi-zod-ts && openapi-server", - "start": "tsx src/server/index.ts", - "test": "vitest run" + "generate": "node node_modules/openapi-zod-ts/dist/cli.cjs && node node_modules/@codewithagents/openapi-server/dist/cli.cjs", + "start": "pnpm generate && tsx src/server/index.ts", + "test": "pnpm generate && vitest run" }, "dependencies": { "express": "^5.0.0", diff --git a/packages/petstore-fastify/.gitignore b/packages/petstore-fastify/.gitignore new file mode 100644 index 0000000..b2bad0d --- /dev/null +++ b/packages/petstore-fastify/.gitignore @@ -0,0 +1,6 @@ +# Generated output — regenerated on demand, never committed. +# Run `pnpm generate` (or `pnpm test`) to recreate. +generated/ + +# Test artifacts +test-results/ diff --git a/packages/petstore-fastify/generated/client-config.ts b/packages/petstore-fastify/generated/client-config.ts deleted file mode 100644 index 34704fa..0000000 --- a/packages/petstore-fastify/generated/client-config.ts +++ /dev/null @@ -1,74 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -export interface ClientConfig { - /** Base URL for all API requests (e.g. 'https://api.example.com') */ - baseUrl: string - /** - * Bearer token or a function that returns one (supports async refresh). - * When provided, adds `Authorization: Bearer ` to every request. - */ - token?: string | (() => string | Promise) - /** - * Fetch credentials mode. Use 'include' for cookie-based auth. - * Defaults to 'same-origin'. - */ - credentials?: RequestCredentials - /** Additional headers sent with every request */ - headers?: Record - /** - * Global error hook called with every non-2xx response error before it is - * thrown. Use for logging, monitoring, or triggering auth refresh flows. - * - * @example - * configureClient({ - * onError: (err) => Sentry.captureException(err), - * }) - */ - onError?: (err: Error) => void - /** - * Default AbortSignal for every request. Useful for cancelling in-flight - * requests (e.g. on route change). Overridable per call via the config param. - */ - signal?: AbortSignal - /** - * Default timeout in milliseconds. When set, every request is aborted with a - * TimeoutError after the specified duration. Overridable per call. - */ - timeout?: number - /** - * Request interceptor called before every fetch. Receives the resolved URL and - * RequestInit; may return an object with optional url/init overrides. - * - * @example - * configureClient({ - * onRequest: ({ url, init }) => ({ init: { ...init, headers: { ...init.headers, 'X-Request-Id': crypto.randomUUID() } } }), - * }) - */ - onRequest?: (req: { - url: string - init: RequestInit - }) => - | void - | { url?: string; init?: RequestInit } - | Promise - /** - * Custom fetch implementation. Defaults to globalThis.fetch, resolved at - * call time so test mocks of globalThis.fetch are always honoured. - */ - fetch?: typeof globalThis.fetch -} - -let _config: ClientConfig = { - baseUrl: '', - credentials: 'same-origin', -} - -/** Configure the API client. Call once at app startup before making any requests. */ -export function configureClient(config: ClientConfig): void { - _config = { ..._config, ...config } -} - -/** @internal — used by generated fetch functions */ -export function getConfig(): Readonly { - return _config -} diff --git a/packages/petstore-fastify/generated/client.ts b/packages/petstore-fastify/generated/client.ts deleted file mode 100644 index 8647866..0000000 --- a/packages/petstore-fastify/generated/client.ts +++ /dev/null @@ -1,113 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -import type { CreatePetRequest, Pet } from './models.js' -import { getConfig, type ClientConfig } from './client-config.js' - -export class ApiError extends Error { - constructor( - public readonly status: Status, - public readonly body: Body - ) { - super(`API error ${status}`) - this.name = 'ApiError' - } -} - -type _FetchResponse = Awaited> - -function _buildSignal( - signal: AbortSignal | undefined, - timeout: number | undefined -): AbortSignal | undefined { - if (timeout === undefined) return signal - const _ts = AbortSignal.timeout(timeout) - if (signal === undefined) return _ts - if (typeof AbortSignal.any === 'function') return AbortSignal.any([signal, _ts]) - const _ctrl = new AbortController() - const _abort = (s: AbortSignal) => () => { - if (!_ctrl.signal.aborted) _ctrl.abort(s.reason) - } - signal.addEventListener('abort', _abort(signal), { once: true }) - _ts.addEventListener('abort', _abort(_ts), { once: true }) - return _ctrl.signal -} - -async function _request( - method: string, - path: string, - opts: { - searchParams?: URLSearchParams - body?: unknown - signal?: AbortSignal - }, - config?: Partial -): Promise<_FetchResponse> { - const { - baseUrl, - headers, - onError, - signal: _cfgSignal, - timeout, - onRequest, - fetch: _configFetch, - } = { ...getConfig(), ...config } - const base = baseUrl ? baseUrl.replace(/\/$/, '') : '' - const qs = opts.searchParams?.toString() ?? '' - const url = qs ? `${base}${path}?${qs}` : `${base}${path}` - const fetch = _configFetch ?? globalThis.fetch - let _url = url - let _init: RequestInit = { - method, - headers: { - ...(opts.body !== undefined ? { 'Content-Type': 'application/json' } : {}), - ...headers, - }, - ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}), - } - if (onRequest) { - const _or = await onRequest({ url: _url, init: _init }) - if (_or) { - if (_or.url !== undefined) _url = _or.url - if (_or.init !== undefined) _init = { ..._init, ..._or.init } - } - } - const _rawSignal = opts.signal ?? _cfgSignal - const _resolvedSignal = _buildSignal(_rawSignal, timeout) - if (_resolvedSignal !== undefined) _init = { ..._init, signal: _resolvedSignal } - const res = await fetch(_url, _init) - if (!res.ok) { - const err = new ApiError(res.status, await res.json().catch(() => null)) - onError?.(err) - throw err - } - return res -} - -export async function listPets( - params?: { - species?: string - }, - config?: Partial -): Promise { - const searchParams = new URLSearchParams() - if (params?.species != null) searchParams.set('species', String(params.species)) - const res = await _request('GET', '/pets', { searchParams }, config) - return res.json() -} - -export async function createPet( - body: CreatePetRequest, - config?: Partial -): Promise { - const res = await _request('POST', '/pets', { body }, config) - return res.json() -} - -export async function getPet(id: string, config?: Partial): Promise { - const res = await _request('GET', `/pets/${encodeURIComponent(id)}`, {}, config) - return res.json() -} - -export async function deletePet(id: string, config?: Partial): Promise { - await _request('DELETE', `/pets/${encodeURIComponent(id)}`, {}, config) -} diff --git a/packages/petstore-fastify/generated/index.ts b/packages/petstore-fastify/generated/index.ts deleted file mode 100644 index 41689a6..0000000 --- a/packages/petstore-fastify/generated/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -// This file is auto-generated by openapi-zod-ts — do not edit - -export * from './models.js' -export * from './client-config.js' -export * from './client.js' diff --git a/packages/petstore-fastify/generated/models.ts b/packages/petstore-fastify/generated/models.ts deleted file mode 100644 index 953707b..0000000 --- a/packages/petstore-fastify/generated/models.ts +++ /dev/null @@ -1,12 +0,0 @@ -// This file is auto-generated by openapi-zod-ts - do not edit - -export interface Pet { - id: string - name: string - species: string -} - -export interface CreatePetRequest { - name: string - species: string -} diff --git a/packages/petstore-fastify/generated/router.ts b/packages/petstore-fastify/generated/router.ts deleted file mode 100644 index fa07002..0000000 --- a/packages/petstore-fastify/generated/router.ts +++ /dev/null @@ -1,37 +0,0 @@ -// This file is auto-generated. Do not edit manually. - -import type { FastifyInstance } from 'fastify' -import type { CreatePetRequest } from './models.js' -import type { PetstoreService } from './service.js' -import { z } from 'zod' -import { CreatePetRequestSchema } from '../../petstore-hono/generated/schemas.js' - -export function createRouter(app: FastifyInstance, service: PetstoreService): void { - app.get<{ Querystring: { species?: string } }>('/pets', async (req, reply) => { - const params = { - species: req.query.species, - } - return service.listPets(params) - }) - - app.post<{ Body: CreatePetRequest }>('/pets', async (req, reply) => { - // Validate request body: returns 422 with Zod issues on failure - const parseResult = CreatePetRequestSchema.safeParse(req.body) - if (!parseResult.success) { - return reply - .status(422) - .send({ error: 'Invalid request body', issues: parseResult.error.issues }) - } - reply.status(201) - return service.createPet(parseResult.data) - }) - - app.get<{ Params: { id: string } }>('/pets/:id', async (req, reply) => { - return service.getPet(req.params.id) - }) - - app.delete<{ Params: { id: string } }>('/pets/:id', async (req, reply) => { - await service.deletePet(req.params.id) - reply.status(204).send() - }) -} diff --git a/packages/petstore-fastify/generated/service.ts b/packages/petstore-fastify/generated/service.ts deleted file mode 100644 index 89af88e..0000000 --- a/packages/petstore-fastify/generated/service.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file is auto-generated. Do not edit manually. - -import type { CreatePetRequest, Pet } from './models.js' - -export interface PetstoreService { - /** GET /pets */ - listPets(params?: { species?: string }): Promise - /** POST /pets */ - createPet(body: CreatePetRequest): Promise - /** GET /pets/{id} */ - getPet(id: string): Promise - /** DELETE /pets/{id} */ - deletePet(id: string): Promise -} diff --git a/packages/petstore-fastify/openapi-server.config.json b/packages/petstore-fastify/openapi-server.config.json index 161b28e..e248481 100644 --- a/packages/petstore-fastify/openapi-server.config.json +++ b/packages/petstore-fastify/openapi-server.config.json @@ -2,5 +2,5 @@ "input_openapi": "../petstore-hono/spec/api.json", "output": "generated/", "framework": "fastify", - "input_schema": "../petstore-hono/generated/schemas.ts" + "input_schema": "../petstore-hono/src/schemas.ts" } diff --git a/packages/petstore-fastify/package.json b/packages/petstore-fastify/package.json index 302bf6f..ec60df0 100644 --- a/packages/petstore-fastify/package.json +++ b/packages/petstore-fastify/package.json @@ -4,9 +4,9 @@ "private": true, "type": "module", "scripts": { - "generate": "openapi-zod-ts && openapi-server", - "start": "tsx src/server/index.ts", - "test": "vitest run" + "generate": "node node_modules/openapi-zod-ts/dist/cli.cjs && node node_modules/@codewithagents/openapi-server/dist/cli.cjs", + "start": "pnpm generate && tsx src/server/index.ts", + "test": "pnpm generate && vitest run" }, "dependencies": { "fastify": "^5.0.0",