Compare commits

...

2 Commits

Author SHA1 Message Date
hitchhiker
f271813f14 Documentation updates 2025-04-17 15:45:43 +02:00
hitchhiker
ba6e2bd3ff Add api-generator package to README 2025-04-17 15:45:20 +02:00
8 changed files with 408 additions and 1 deletions

View File

@ -8,6 +8,7 @@ This monorepo contains common TypeScript packages used across Generation One pro
g1-ts-common-packages/ g1-ts-common-packages/
├── packages/ # All packages are stored here ├── packages/ # All packages are stored here
│ ├── sse-client/ # Server-Sent Events client package │ ├── sse-client/ # Server-Sent Events client package
│ ├── api-generator/ # OpenAPI TypeScript client generator
│ └── [future-packages]/ # Additional packages will be added here │ └── [future-packages]/ # Additional packages will be added here
├── scripts/ # Utility scripts for the repository ├── scripts/ # Utility scripts for the repository
├── docs/ # Documentation ├── docs/ # Documentation
@ -24,9 +25,18 @@ A custom Server-Sent Events (SSE) client that supports headers and bypasses cert
- Handles reconnection with exponential backoff - Handles reconnection with exponential backoff
- Works in both browser and Node.js environments - Works in both browser and Node.js environments
- Provides connection management utilities - Provides connection management utilities
[View SSE Client Documentation](packages/sse-client/README.md) [View SSE Client Documentation](packages/sse-client/README.md)
### API Generator (`@g1/api-generator`)
A command-line tool for generating TypeScript API clients from OpenAPI schemas.
- Downloads OpenAPI schema from a specified URL
- Generates TypeScript interfaces and API client code
- Post-processes generated code to fix common issues
- Supports HTTPS with option to skip TLS verification
[View API Generator Documentation](packages/api-generator/README.md)
## Getting Started ## Getting Started
### Prerequisites ### Prerequisites
@ -89,6 +99,8 @@ pnpm add /path/to/g1-sse-client-0.2.0.tgz
### Example Usage ### Example Usage
#### SSE Client
```typescript ```typescript
import { SSEClient } from '@g1/sse-client'; import { SSEClient } from '@g1/sse-client';
@ -111,6 +123,27 @@ client.on('message', (event) => {
client.close(); client.close();
``` ```
#### API Generator
```bash
# Generate API client from OpenAPI schema
g1-api-generator https://api.example.com/openapi.json ./src/api
```
```typescript
// Use the generated API client
import { ApiClient } from './src/api';
const api = new ApiClient({
BASE: 'https://api.example.com',
TOKEN: 'your-token'
});
// Call API methods
const users = await api.users.getUsers();
console.log(users);
```
## Development ## Development
### Adding a New Package ### Adding a New Package

4
packages/api-generator/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
dist/
*.log
.DS_Store

View File

@ -0,0 +1,4 @@
src/
tsconfig.json
.gitignore
node_modules/

View File

@ -0,0 +1,82 @@
# G1 API Generator
A command-line tool for generating TypeScript API clients from OpenAPI schemas.
## Features
- Downloads OpenAPI schema from a specified URL
- Generates TypeScript interfaces and API client code
- Post-processes generated code to fix common issues
- Supports HTTPS with option to skip TLS verification
- Detects and warns about duplicate DTOs in the schema
## Installation
```bash
# Install from G1 package registry
npm install @g1/api-generator --save-dev
# Or with pnpm
pnpm add @g1/api-generator -D
```
## Usage
### Command Line
```bash
# Basic usage
g1-api-generator https://your-api-server/openapi/schema.json ./src/lib/api
# Skip TLS verification (useful for local development with self-signed certificates)
g1-api-generator https://localhost:7205/openapi/schema.json ./src/lib/api --skip-tls-verify
```
### In package.json scripts
```json
{
"scripts": {
"api:generate": "g1-api-generator https://localhost:7205/openapi/schema.json src/lib/api"
}
}
```
### Programmatic Usage
```typescript
import { generateApiClient } from '@g1/api-generator';
async function generateApi() {
await generateApiClient({
schemaUrl: 'https://your-api-server/openapi/schema.json',
outputDir: './src/lib/api',
skipTlsVerify: true, // Optional, default: false
runPostProcessing: true // Optional, default: true
});
}
generateApi().catch(console.error);
```
## Generated Code Structure
The generated code is organized as follows:
- `models/` - TypeScript interfaces for API models
- `services/` - API client services for making requests
- `core/` - Core functionality for the API client
- `open-api.json` - The downloaded and processed OpenAPI schema
## Post-Processing
The tool automatically applies the following post-processing to the generated code:
1. Removes import statements for `void` type
2. Replaces usages of `void` as a type with `any`
3. Detects and replaces self-referencing DTOs with `any`
4. Adds auto-generated comments to files
## License
UNLICENSED - Private package for Generation One use only.

View File

@ -0,0 +1,33 @@
{
"name": "@g1/api-generator",
"version": "0.1.0",
"description": "API client generator for OpenAPI schemas",
"main": "dist/index.js",
"bin": {
"g1-api-generator": "./dist/cli.js"
},
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build"
},
"keywords": [
"openapi",
"api",
"generator",
"typescript"
],
"author": "Generation One",
"license": "UNLICENSED",
"private": true,
"dependencies": {
"axios": "^1.8.4",
"openapi-typescript-codegen": "^0.29.0"
},
"devDependencies": {
"@types/node": "^20",
"typescript": "^5"
},
"publishConfig": {
"registry": "https://git.generation.one/api/packages/GenerationOne/npm/"
}
}

View File

@ -0,0 +1,41 @@
#!/usr/bin/env node
import { generateApiClient } from './index';
// Parse command line arguments
function parseArgs(): { schemaUrl: string; outputDir: string; skipTlsVerify: boolean } {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: g1-api-generator <schemaUrl> <outputDir> [--skip-tls-verify]');
process.exit(1);
}
const schemaUrl = args[0];
const outputDir = args[1];
const skipTlsVerify = args.includes('--skip-tls-verify');
return { schemaUrl, outputDir, skipTlsVerify };
}
// Main CLI function
async function main() {
try {
const { schemaUrl, outputDir, skipTlsVerify } = parseArgs();
await generateApiClient({
schemaUrl,
outputDir,
skipTlsVerify,
runPostProcessing: true
});
process.exit(0);
} catch (error) {
console.error('Error:', error instanceof Error ? error.message : error);
process.exit(1);
}
}
// Run the CLI
main();

View File

@ -0,0 +1,195 @@
import axios from 'axios';
import fs from 'fs';
import path from 'path';
import { execSync } from 'child_process';
/**
* Options for the API generator
*/
export interface ApiGeneratorOptions {
/**
* URL to the OpenAPI schema
*/
schemaUrl: string;
/**
* Output directory for the generated API client
*/
outputDir: string;
/**
* Whether to skip TLS verification (useful for local development)
* @default false
*/
skipTlsVerify?: boolean;
/**
* Whether to run post-processing on the generated files
* @default true
*/
runPostProcessing?: boolean;
}
/**
* Downloads the OpenAPI schema from the specified URL
*/
export async function downloadSchema(options: ApiGeneratorOptions): Promise<string> {
const { schemaUrl, outputDir, skipTlsVerify = false } = options;
const outPath = path.join(outputDir, 'open-api.json');
console.log(`Downloading schema from ${schemaUrl}...`);
try {
// Create output directory if it doesn't exist
fs.mkdirSync(path.dirname(outPath), { recursive: true });
// Download schema
const axiosConfig = skipTlsVerify ? { httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) } : {};
const response = await axios.get(schemaUrl, axiosConfig);
// Process schema
const schema = response.data;
// Remove servers array to avoid hardcoded URLs
if (schema.servers) {
delete schema.servers;
}
// Write schema to file
fs.writeFileSync(outPath, JSON.stringify(schema, null, 2));
// Check for duplicate DTOs
console.log('Checking for duplicate DTOs in schema...');
const schemaContent = fs.readFileSync(outPath, 'utf8');
const duplicateDtoRefRegex = /"\$ref":\s*"#\/components\/schemas\/\w+2"/;
if (duplicateDtoRefRegex.test(schemaContent)) {
throw new Error(
"The schema contains duplicate DTOs (pattern: $ref: '#/components/schemas/...Name2'). " +
"This is a transient error, usually due to a bug in OpenAPI generators. " +
"Try restarting the API server."
);
}
console.log('Schema downloaded and processed successfully.');
return outPath;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(`Failed to download schema: ${error.message}`);
}
throw error;
}
}
/**
* Generates the API client using openapi-typescript-codegen
*/
export function generateClient(schemaPath: string, outputDir: string): void {
console.log('Generating API client...');
const codegenCmd = [
'npx openapi-typescript-codegen',
'--input', schemaPath,
'--output', outputDir,
'--client', 'axios',
'--useOptions'
].join(' ');
execSync(codegenCmd, { stdio: 'inherit' });
// Add auto-generated comment to each file
const files = fs.readdirSync(outputDir);
for (const file of files) {
const filePath = path.join(outputDir, file);
if (file.startsWith('openapi') && file.endsWith('.ts')) {
const content = fs.readFileSync(filePath, 'utf8');
const modifiedContent = `/* AUTOGENERATED - DO NOT EDIT */\n\n${content}`;
fs.writeFileSync(filePath, modifiedContent);
}
}
console.log('API client generated successfully.');
}
/**
* Post-processes the generated API client files
*/
export function postProcessFiles(outputDir: string): void {
console.log('Post-processing generated files...');
const modelsDir = path.join(outputDir, 'models');
const servicesDir = path.join(outputDir, 'services');
const directoriesToProcess = [
modelsDir,
servicesDir
];
function processFile(filePath: string): void {
let fileContent = fs.readFileSync(filePath, 'utf8');
// Remove import statements for 'void'
fileContent = fileContent.replace(/import type \{ void \} from '.*';/g, '');
// Replace usages of 'void' as a type with 'any'
fileContent = fileContent.replace(/: void,/g, ': any,');
fileContent = fileContent.replace(/: void \{/g, ': any {');
fileContent = fileContent.replace(/: void;/g, ': any;');
// Detect and replace self-referencing DTOs with 'any'
const typeDefinitionRegex = /export type (\w+) = (.*?);/gs;
let match;
while ((match = typeDefinitionRegex.exec(fileContent)) !== null) {
const typeName = match[1];
const typeDefinition = match[2];
if (typeDefinition.includes(typeName)) {
const newTypeDefinition = typeDefinition.replace(new RegExp(`\\b${typeName}\\b`, 'g'), 'any');
fileContent = fileContent.replace(typeDefinition, newTypeDefinition);
}
}
fs.writeFileSync(filePath, fileContent, 'utf8');
}
function processDirectory(dirPath: string): void {
if (!fs.existsSync(dirPath)) {
return;
}
const files = fs.readdirSync(dirPath);
files.forEach(file => {
const filePath = path.join(dirPath, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
processDirectory(filePath);
} else if (path.extname(file) === '.ts') {
processFile(filePath);
}
});
}
directoriesToProcess.forEach(dir => {
processDirectory(dir);
});
console.log('Post-processing complete.');
}
/**
* Main function to generate the API client
*/
export async function generateApiClient(options: ApiGeneratorOptions): Promise<void> {
try {
const schemaPath = await downloadSchema(options);
generateClient(schemaPath, options.outputDir);
if (options.runPostProcessing !== false) {
postProcessFiles(options.outputDir);
}
console.log(`API client successfully generated in ${options.outputDir}`);
} catch (error) {
console.error('Error generating API client:', error);
throw error;
}
}

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "CommonJS",
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}