UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

235 lines (194 loc) 7.57 kB
/** * Script to generate TypeScript interfaces for all schemas in combined-source.json */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); const __dirname = "../"; // Read the combined-source.json file const combinedSourcePath = path.join(__dirname, 'generated', 'combined-source.json'); const defaultVersionPath = path.join(__dirname, 'generated', 'defaultVersion.json'); // Check if files exist if (!fs.existsSync(combinedSourcePath)) { console.error(`Error: ${combinedSourcePath} does not exist`); process.exit(1); } if (!fs.existsSync(defaultVersionPath)) { console.error(`Error: ${defaultVersionPath} does not exist`); process.exit(1); } // Read the files const combinedSource = JSON.parse(fs.readFileSync(combinedSourcePath, 'utf8')); const defaultVersions = JSON.parse(fs.readFileSync(defaultVersionPath, 'utf8')); // Output file path const outputPath = path.join(__dirname, 'src', 'api-model-kinds.ts'); // Helper function to convert JSON schema type to TypeScript type function convertType(schemaType, format) { if (schemaType === 'string') { if (format === 'date-time') return 'Date'; return 'string'; } if (schemaType === 'integer' || schemaType === 'number') return 'number'; if (schemaType === 'boolean') return 'boolean'; if (schemaType === 'array') return 'any[]'; if (schemaType === 'object') return 'Record<string, any>'; return 'any'; } // Helper function to make a valid TypeScript property name function makeValidPropertyName(propName) { // If the property name contains special characters, wrap it in quotes if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName) && !propName.includes('-')) { return propName; } else { return `'${propName}'`; } } // Helper function to convert schema property to TypeScript property function convertProperty(propName, propSchema, required = []) { const isRequired = required.includes(propName); const questionMark = isRequired ? '' : '?'; // Handle property names with special characters const safePropName = makeValidPropertyName(propName); if (propSchema.type === 'array') { if (propSchema.items && propSchema.items.type) { return `${safePropName}${questionMark}: ${convertType(propSchema.items.type)}[]`; } return `${safePropName}${questionMark}: any[]`; } if (propSchema.type === 'object') { if (propSchema.properties) { const nestedProps = Object.entries(propSchema.properties).map(([nestedName, nestedSchema]) => { const nestedRequired = propSchema.required || []; return convertProperty(nestedName, nestedSchema, nestedRequired); }).join(';\n '); return `${safePropName}${questionMark}: {\n ${nestedProps}\n }`; } return `${safePropName}${questionMark}: Record<string, any>`; } return `${safePropName}${questionMark}: ${convertType(propSchema.type, propSchema.format)}`; } // Helper function to make a valid TypeScript interface name function makeValidInterfaceName(kind, apiVersion) { // Replace dots, slashes, and hyphens with underscores const safeKind = kind.replace(/[^a-zA-Z0-9_]/g, '_'); const safeApiVersion = apiVersion.replace(/[^a-zA-Z0-9_]/g, '_'); return `${safeKind}_${safeApiVersion}`; } // Generate TypeScript interfaces from schemas function generateInterfaces() { const interfaces = []; const kinds = new Set(); const interfaceNameMap = new Map(); // Map to track original to safe interface names // Process each schema for (const [schemaKey, schema] of Object.entries(combinedSource)) { // Skip non-object schemas if (schema.type !== 'object') continue; // Extract kind and apiVersion from schema key const match = schemaKey.match(/^(.+)_(.+)\.json$/); if (!match) continue; const apiVersion = match[1].replace(/_/g, '/'); const kindLower = match[2]; // Find the actual kind name with correct casing let kind = null; for (const k of Object.keys(defaultVersions)) { if (k.toLowerCase() === kindLower) { kind = k; kinds.add(k); break; } } if (!kind) continue; // Generate interface name - replace dots and slashes with underscores to make it valid TypeScript const safeInterfaceName = makeValidInterfaceName(kind, apiVersion); interfaceNameMap.set(`${kind}_${apiVersion}`, safeInterfaceName); // Generate interface properties let properties = ''; if (schema.properties) { // Ensure kind property is typed as KindType properties = ` kind: KindType;\n apiVersion: string;\n`; // Add other properties const otherProps = Object.entries(schema.properties) .filter(([propName]) => propName !== 'kind' && propName !== 'apiVersion') .map(([propName, propSchema]) => { const required = schema.required || []; return ` ${convertProperty(propName, propSchema, required)}`; }) .join(';\n'); if (otherProps) { properties += otherProps + ';'; } } // Create interface const interfaceCode = `export interface ${safeInterfaceName} {\n${properties}\n}`; interfaces.push(interfaceCode); } // Generate union type for all kinds const kindsArray = Array.from(kinds); const kindUnionType = `export type KindType = ${kindsArray.map(k => `'${k}'`).join(' | ')};`; // Generate KindApiVersionType union const kindApiVersionUnion = `export type KindApiVersionType = ${Array.from(interfaceNameMap.values()).map(name => name).join(' | ')};`; // Generate base ApiResource interface const apiResourceInterface = `export interface ApiResource { kind: KindType; apiVersion: string; metadata: { name: string; version: string; description?: string; namespace?: string; tags?: string[]; type?: string; labels?: { gatewayTypes?: string[]; }; }; spec: Record<string, any>; }`; // Generate utility functions const utilityFunctions = ` /** * Type guard to check if a resource is of a specific kind and API version */ export function isResourceOfType<T extends ApiResource>( resource: ApiResource, kind: KindType, apiVersion: string ): resource is T { return resource.kind === kind && resource.apiVersion === apiVersion; } /** * Get the TypeScript interface name for a given kind and API version */ export function getInterfaceName(kind: KindType, apiVersion: string): string { // Replace dots, slashes, and hyphens with underscores for valid TypeScript interface name const safeKind = kind.replace(/[^a-zA-Z0-9_]/g, '_'); const safeApiVersion = apiVersion.replace(/[^a-zA-Z0-9_]/g, '_'); return \`\${safeKind}_\${safeApiVersion}\`; } /** * Map of original kind_apiVersion to TypeScript interface names */ export const interfaceNameMap = { ${Array.from(interfaceNameMap.entries()).map(([original, safe]) => ` "${original}": "${safe}"`).join(',\n')} }; `; // Combine all code const code = `/** * Generated TypeScript interfaces for API resources * DO NOT EDIT MANUALLY - This file is generated */ ${kindUnionType} ${apiResourceInterface} ${kindApiVersionUnion} ${interfaces.join('\n\n')} ${utilityFunctions} `; // Write to file fs.writeFileSync(outputPath, code); console.log(`Generated TypeScript interfaces for ${interfaces.length} schemas in ${outputPath}`); } // Run the generator generateInterfaces(); // Made with Bob