@apistudio/apim-cli
Version:
CLI for API Management Products
289 lines (238 loc) • 9.3 kB
JavaScript
/**
* 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