Documentation updates
This commit is contained in:
parent
ba6e2bd3ff
commit
f271813f14
4
packages/api-generator/.gitignore
vendored
Normal file
4
packages/api-generator/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.DS_Store
|
4
packages/api-generator/.npmignore
Normal file
4
packages/api-generator/.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
src/
|
||||
tsconfig.json
|
||||
.gitignore
|
||||
node_modules/
|
82
packages/api-generator/README.md
Normal file
82
packages/api-generator/README.md
Normal 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.
|
33
packages/api-generator/package.json
Normal file
33
packages/api-generator/package.json
Normal 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/"
|
||||
}
|
||||
}
|
41
packages/api-generator/src/cli.ts
Normal file
41
packages/api-generator/src/cli.ts
Normal 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();
|
195
packages/api-generator/src/index.ts
Normal file
195
packages/api-generator/src/index.ts
Normal 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;
|
||||
}
|
||||
}
|
15
packages/api-generator/tsconfig.json
Normal file
15
packages/api-generator/tsconfig.json
Normal 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"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user