#!/bin/bash # Generate TypeScript API client from OpenAPI spec # # Usage: # ./scripts/generate-client.sh [SPEC_URL] # # Environment: # SPEC_URL - OpenAPI spec URL (default: http://localhost:8080/openapi.json) set -e SPEC_URL="${1:-${SPEC_URL:-http://localhost:8080/openapi.json}}" OUTPUT_DIR="packages/api-client/src" echo "Generating TypeScript client from: $SPEC_URL" # Ensure output directory exists mkdir -p "$OUTPUT_DIR" # Generate TypeScript types from OpenAPI spec npx openapi-typescript "$SPEC_URL" -o "$OUTPUT_DIR/schema.d.ts" echo "Generated: $OUTPUT_DIR/schema.d.ts" # Create client wrapper if it doesn't exist if [ ! -f "$OUTPUT_DIR/client.ts" ]; then cat > "$OUTPUT_DIR/client.ts" << 'EOF' import type { paths } from './schema'; export type { paths }; /** * API Client Configuration */ export interface ClientConfig { baseUrl: string; apiKey?: string; bearerToken?: string; headers?: Record; onError?: (error: Error) => void; } /** * Create a typed API client */ export function createClient(config: ClientConfig) { const { baseUrl, apiKey, bearerToken, headers = {}, onError } = config; async function request( method: string, path: string, options: { body?: unknown; params?: Record; headers?: Record; } = {} ): Promise { const url = new URL(path, baseUrl); // Add query params if (options.params) { for (const [key, value] of Object.entries(options.params)) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } } // Build headers const requestHeaders: Record = { 'Content-Type': 'application/json', ...headers, ...options.headers, }; if (apiKey) { requestHeaders['X-API-Key'] = apiKey; } if (bearerToken) { requestHeaders['Authorization'] = `Bearer ${bearerToken}`; } const response = await fetch(url.toString(), { method, headers: requestHeaders, body: options.body ? JSON.stringify(options.body) : undefined, }); if (!response.ok) { const error = new Error(`API error: ${response.status}`); if (onError) { onError(error); } throw error; } // Handle no-content responses if (response.status === 204) { return undefined as T; } return response.json(); } return { get: (path: string, params?: Record) => request('GET', path, { params }), post: (path: string, body?: unknown) => request('POST', path, { body }), put: (path: string, body?: unknown) => request('PUT', path, { body }), patch: (path: string, body?: unknown) => request('PATCH', path, { body }), delete: (path: string) => request('DELETE', path), }; } EOF echo "Created: $OUTPUT_DIR/client.ts" fi # Create index if it doesn't exist if [ ! -f "$OUTPUT_DIR/index.ts" ]; then cat > "$OUTPUT_DIR/index.ts" << 'EOF' export * from './client'; export type { paths, components, operations } from './schema'; EOF echo "Created: $OUTPUT_DIR/index.ts" fi echo "Done! Client generated in: $OUTPUT_DIR"