@simplyhomes/sos-sdk
Version:
TypeScript SDK for Simply Homes SoS API v4
206 lines (205 loc) ⢠7.7 kB
JavaScript
import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
import { parseConfig } from './config-parser';
/**
* Recursively get all .ts files in a directory
*/
function getAllTsFiles(dir) {
const files = [];
const entries = readdirSync(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
files.push(...getAllTsFiles(fullPath));
}
else if (entry.endsWith('.ts')) {
files.push(fullPath);
}
}
return files;
}
/**
* Extract all enums matching a pattern from a file
*/
function extractEnums(content, pattern) {
const enums = [];
// Match: export enum EnumName { ... }
// Handles multi-line enums with various formatting
const enumRegex = /export enum (\w+) \{([^}]+)\}/g;
let match;
while ((match = enumRegex.exec(content)) !== null) {
const enumName = match[1];
const enumBody = match[2];
// Check if enum name matches the pattern
if (pattern.test(enumName)) {
enums.push({
name: enumName,
fullText: match[0],
values: enumBody.trim()
});
}
}
return enums;
}
/**
* Check if two enums have identical values
*/
function haveIdenticalValues(enum1, enum2) {
// Normalize whitespace and compare
const normalize = (str) => str.replace(/\s+/g, ' ').trim();
return normalize(enum1.values) === normalize(enum2.values);
}
/**
* Process a single file for enum consolidation
*/
function consolidateEnumsInFile(filePath, rule, canonicalEnum) {
let content = readFileSync(filePath, 'utf-8');
const originalContent = content;
const pattern = new RegExp(rule.pattern);
const enums = extractEnums(content, pattern);
if (enums.length === 0) {
return { modified: false, canonicalEnum, aliasesCreated: 0 };
}
let aliasesCreated = 0;
let newCanonicalEnum = canonicalEnum;
// If we don't have a canonical enum yet, use the first one found
if (!canonicalEnum && enums.length > 0) {
newCanonicalEnum = enums[0];
const originalName = newCanonicalEnum.name;
// Rename it to the canonical name
const enumDeclaration = `export enum ${originalName}`;
const renamedDeclaration = `export enum ${rule.canonicalName}`;
content = content.replace(enumDeclaration, renamedDeclaration);
// Create a type alias for the original name to maintain backward compatibility
const typeAlias = `export type ${originalName} = ${rule.canonicalName};`;
// Find the closing brace of the enum and insert the type alias after it
const enumPattern = new RegExp(`export enum ${rule.canonicalName} \\{[^}]+\\}`, 's');
const enumMatch = content.match(enumPattern);
if (enumMatch) {
content = content.replace(enumMatch[0], `${enumMatch[0]}\n${typeAlias}`);
aliasesCreated++;
console.log(` š Using '${originalName}' as base, renamed to '${rule.canonicalName}' (type alias created)`);
}
else {
console.log(` š Using '${originalName}' as base, renamed to '${rule.canonicalName}'`);
}
}
// Process all enums
for (const enumMatch of enums) {
// Skip the canonical enum itself
if (canonicalEnum && enumMatch.name === canonicalEnum.name) {
continue;
}
// Skip if this is the new canonical enum (first occurrence)
if (!canonicalEnum && enumMatch.name === enums[0].name) {
continue;
}
// Verify values match
if (newCanonicalEnum && !haveIdenticalValues(enumMatch, newCanonicalEnum)) {
console.warn(` ā ļø Enum '${enumMatch.name}' has different values, skipping`);
continue;
}
// Replace enum with type alias
const typeAlias = `export type ${enumMatch.name} = ${rule.canonicalName};`;
content = content.replace(enumMatch.fullText, typeAlias);
aliasesCreated++;
console.log(` ā ${enumMatch.name} ā type alias`);
}
// Write back if modified
const modified = content !== originalContent;
if (modified) {
writeFileSync(filePath, content, 'utf-8');
}
return {
modified,
canonicalEnum: newCanonicalEnum,
aliasesCreated
};
}
/**
* Process all files for a single consolidation rule
*/
async function processRule(rule, baseDir) {
console.log(`\nš Processing rule: ${rule.pattern} ā ${rule.canonicalName}`);
if (rule.description) {
console.log(` ${rule.description}`);
}
const stats = {
filesProcessed: 0,
filesModified: 0,
enumsConsolidated: 0,
aliasesCreated: 0
};
// Determine directories to process based on scope
const dirs = [];
if (rule.scope === 'apis' || rule.scope === 'both') {
dirs.push(join(baseDir, 'src', 'generated', 'src', 'apis'));
}
if (rule.scope === 'models' || rule.scope === 'both') {
dirs.push(join(baseDir, 'src', 'generated', 'src', 'models'));
}
let canonicalEnum = null;
for (const dir of dirs) {
const files = getAllTsFiles(dir);
for (const file of files) {
stats.filesProcessed++;
const result = consolidateEnumsInFile(file, rule, canonicalEnum);
if (result.modified) {
stats.filesModified++;
}
if (result.canonicalEnum && !canonicalEnum) {
canonicalEnum = result.canonicalEnum;
stats.enumsConsolidated++;
}
stats.aliasesCreated += result.aliasesCreated;
stats.enumsConsolidated += result.aliasesCreated;
}
}
return stats;
}
/**
* Main consolidation function
*/
async function main() {
console.log('š Starting enum consolidation...\n');
// Parse configuration
const config = await parseConfig('sdk.config.yml');
const rules = config.postProcessing?.enumConsolidation?.rules || [];
if (rules.length === 0) {
console.log('ā ļø No enum consolidation rules found in sdk.config.yml');
console.log(' Add rules to postProcessing.enumConsolidation.rules\n');
process.exit(0);
}
console.log(`š Found ${rules.length} consolidation rule(s)\n`);
const baseDir = process.cwd();
const allStats = {
filesProcessed: 0,
filesModified: 0,
enumsConsolidated: 0,
aliasesCreated: 0
};
// Process each rule
for (const rule of rules) {
const stats = await processRule(rule, baseDir);
allStats.filesProcessed += stats.filesProcessed;
allStats.filesModified += stats.filesModified;
allStats.enumsConsolidated += stats.enumsConsolidated;
allStats.aliasesCreated += stats.aliasesCreated;
}
console.log('\n⨠Enum consolidation complete!\n');
console.log('š Summary:');
console.log(` ⢠Files processed: ${allStats.filesProcessed}`);
console.log(` ⢠Files modified: ${allStats.filesModified}`);
console.log(` ⢠Enums consolidated: ${allStats.enumsConsolidated}`);
console.log(` ⢠Type aliases created: ${allStats.aliasesCreated}\n`);
if (allStats.filesModified === 0) {
console.log('ā ļø No files were modified. The enums may already be consolidated.\n');
}
process.exit(0);
}
main().catch((error) => {
console.error('\nā Enum consolidation failed:\n');
console.error(error);
process.exit(1);
});