UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

289 lines (238 loc) 9.3 kB
#!/usr/bin/env node /** * Script to sync exports from api-model-kinds_generated.ts to api-model-kinds-exports.ts * * Usage: * node packages/inventory/src/resources/scripts/sync-exports.js * * Or add to package.json scripts: * "sync-exports": "node src/resources/scripts/sync-exports.js" */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // Get __dirname equivalent in ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Configuration const CONFIG = { sourceFile: path.join(__dirname, '../../api-model-kinds_generated.ts'), targetFile: path.join(__dirname, '../../api-model-kinds-exports.ts'), }; /** * Extract schema names from the generated TypeScript file * @param {string} filePath - Path to the source file * @returns {string[]} Array of schema names */ function extractSchemaNames(filePath) { try { const content = fs.readFileSync(filePath, 'utf-8'); // Find the schemas section - look for "schemas: {" and capture until the matching closing brace const lines = content.split('\n'); const schemaNames = []; let inSchemas = false; let braceCount = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Check if we're entering the schemas section if (line.trim().startsWith('schemas:')) { inSchemas = true; braceCount = 0; continue; } if (inSchemas) { // Count braces to track nesting for (const char of line) { if (char === '{') braceCount++; if (char === '}') braceCount--; } // If we're back to 0 braces, we've exited the schemas section if (braceCount < 0) { break; } // Look for schema definitions at the top level (indentation level 8 spaces or 2 tabs) // Pattern: " SchemaName: {" or " SchemaName: {" const match = line.match(/^\s{8}(\w+):\s*\{/); if (match && braceCount === 1) { schemaNames.push(match[1]); } } } return schemaNames.sort(); } catch (error) { console.error(`Error reading file ${filePath}:`, error.message); throw error; } } /** * Extract schema names from @apic/smith-inventory package * Reads from the TypeScript definition file since it's a type-only export * @returns {string[]} Array of schema names from the external package */ async function extractExternalSchemaNames() { try { // Look for the package in root node_modules (monorepo structure) // Try multiple possible locations const possiblePaths = [ path.join(__dirname, '../../../../../node_modules/@apic/smith-inventory'), path.join(process.cwd(), '../../node_modules/@apic/smith-inventory'), path.join(process.cwd(), 'node_modules/@apic/smith-inventory'), ]; let packagePath = null; for (const p of possiblePaths) { if (fs.existsSync(p)) { packagePath = p; break; } } if (!packagePath) { console.warn('⚠️ Package @apic/smith-inventory not found in node_modules'); console.warn(' Tried paths:', possiblePaths); return []; } // Read the TypeScript definition file directly const dtsFilePath = path.join(packagePath, 'dist/api-model-kinds_generated.d.ts'); if (!fs.existsSync(dtsFilePath)) { console.warn('⚠️ Type definition file not found:', dtsFilePath); return []; } const content = fs.readFileSync(dtsFilePath, 'utf-8'); const schemaNames = []; // Parse the schemas from the TypeScript definition // Look for pattern: "SchemaName: {" within the schemas section const lines = content.split('\n'); let inSchemas = false; let braceCount = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Check if we're entering the schemas section if (line.trim() === 'schemas: {') { inSchemas = true; braceCount = 0; continue; } if (inSchemas) { // Look for schema definitions at the top level (8 spaces indentation) BEFORE counting braces // Pattern: " SchemaName: {" const match = line.match(/^\s{8}(\w+):\s*\{/); if (match && braceCount === 0) { schemaNames.push(match[1]); } // Count braces to track nesting for (const char of line) { if (char === '{') braceCount++; if (char === '}') braceCount--; } // If we're back to negative braces, we've exited the schemas section if (braceCount < 0) { break; } } } return schemaNames.sort(); } catch (error) { console.warn('⚠️ Could not read schemas from @apic/smith-inventory:', error.message); console.warn(' External schema exports will be skipped.'); return []; } } /** * Generate the exports file content * @param {string[]} schemaNames - Array of schema names from local file * @param {string[]} externalSchemaNames - Array of schema names from @apic/smith-inventory * @returns {string} Generated file content */ function generateExportsContent(schemaNames, externalSchemaNames = []) { const header = `/** * Auto-generated exports from api-model-kinds_generated.ts * * This file is automatically generated by sync-exports.js * Do not edit manually - run 'npm run sync-exports' to regenerate * * Generated on: ${new Date().toISOString()} */ import { components } from './api-model-kinds_generated.js'; ${externalSchemaNames.length > 0 ? `import {components as nanocomponents} from '@apic/smith-inventory';\n` : ''} // Export individual schema types `; const typeExports = schemaNames .map(name => `export type ${name} = components['schemas']['${name}'];`) .join('\n'); const schemasObject = ` // Export a convenience object that contains all schemas export const Schemas = { ${schemaNames.map(name => ` ${name}: {} as ${name},`).join('\n')} }; `; // Filter out duplicates - only export external schemas that don't exist locally const uniqueExternalSchemas = externalSchemaNames.filter(name => !schemaNames.includes(name)); const externalTypeExports = uniqueExternalSchemas.length > 0 ? ` // Export types from @apic/smith-inventory (excluding duplicates with local schemas) ${uniqueExternalSchemas.map(name => `export type ${name} = nanocomponents['schemas']['${name}'];`).join('\n')} ` : ''; return header + typeExports + schemasObject + externalTypeExports; } /** * Write content to the target file * @param {string} filePath - Path to the target file * @param {string} content - Content to write */ function writeExportsFile(filePath, content) { try { // Create directory if it doesn't exist const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, content, 'utf-8'); console.log(`✅ Successfully wrote exports to: ${filePath}`); } catch (error) { console.error(`Error writing file ${filePath}:`, error.message); throw error; } } /** * Main function to sync exports */ async function syncExports() { console.log('🔄 Starting export sync...\n'); console.log(`📖 Reading schemas from: ${CONFIG.sourceFile}`); const schemaNames = extractSchemaNames(CONFIG.sourceFile); if (schemaNames.length === 0) { console.error('❌ No schemas found in the source file'); process.exit(1); } console.log(`✨ Found ${schemaNames.length} local schemas:\n`); schemaNames.forEach((name, index) => { console.log(` ${index + 1}. ${name}`); }); console.log(`\n📖 Reading schemas from @apic/smith-inventory...`); const externalSchemaNames = await extractExternalSchemaNames(); if (externalSchemaNames.length > 0) { console.log(`✨ Found ${externalSchemaNames.length} external schemas:\n`); externalSchemaNames.forEach((name, index) => { console.log(` ${index + 1}. ${name}`); }); } else { console.log('⚠️ No external schemas found or package not available'); } const uniqueExternalSchemas = externalSchemaNames.filter(name => !schemaNames.includes(name)); const duplicateCount = externalSchemaNames.length - uniqueExternalSchemas.length; if (duplicateCount > 0) { console.log(`\n⚠️ Found ${duplicateCount} duplicate schema(s) between local and external - will use local versions`); } console.log(`\n📝 Generating exports file...`); const content = generateExportsContent(schemaNames, externalSchemaNames); console.log(`💾 Writing to: ${CONFIG.targetFile}`); writeExportsFile(CONFIG.targetFile, content); console.log('\n✅ Export sync completed successfully!'); console.log(` - Local schemas: ${schemaNames.length}`); console.log(` - External schemas: ${externalSchemaNames.length} (${uniqueExternalSchemas.length} unique)`); } // Run the script syncExports().catch(error => { console.error('❌ Error during export sync:', error); process.exit(1); }); export { syncExports, extractSchemaNames, generateExportsContent }; // Made with Bob