persona-community-1/scripts/generate-client.sh
jordan 4004f88f4a
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/manual/woodpecker Pipeline was successful
Initialize project from skeleton template
2026-02-23 10:20:59 +00:00

132 lines
3.2 KiB
Bash

#!/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<string, string>;
onError?: (error: Error) => void;
}
/**
* Create a typed API client
*/
export function createClient(config: ClientConfig) {
const { baseUrl, apiKey, bearerToken, headers = {}, onError } = config;
async function request<T>(
method: string,
path: string,
options: {
body?: unknown;
params?: Record<string, string | number | boolean | undefined>;
headers?: Record<string, string>;
} = {}
): Promise<T> {
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<string, string> = {
'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: <T>(path: string, params?: Record<string, string | number | boolean | undefined>) =>
request<T>('GET', path, { params }),
post: <T>(path: string, body?: unknown) =>
request<T>('POST', path, { body }),
put: <T>(path: string, body?: unknown) =>
request<T>('PUT', path, { body }),
patch: <T>(path: string, body?: unknown) =>
request<T>('PATCH', path, { body }),
delete: <T>(path: string) =>
request<T>('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"