typescript-scaffolder
Version:
 ### Unit Test Coverage: 97.12%
104 lines (103 loc) • 4.3 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractInterfaceKeysFromFile = extractInterfaceKeysFromFile;
exports.stripSurroundingQuotes = stripSurroundingQuotes;
exports.generateEnum = generateEnum;
exports.isValidIdentifier = isValidIdentifier;
exports.generateEnums = generateEnums;
exports.generateEnumsFromPath = generateEnumsFromPath;
const file_system_1 = require("../utils/file-system");
const fs_1 = __importDefault(require("fs"));
const node_path_1 = __importDefault(require("node:path"));
const logger_1 = require("../utils/logger");
const ts_morph_1 = require("ts-morph");
/**
* Extracts interface names and keys from a TypeScript file using ts-morph.
*/
function extractInterfaceKeysFromFile(filePath) {
const funcName = 'extractInterfaceKeysFromFile';
logger_1.Logger.debug(funcName, `Parsing interfaces from: ${filePath}`);
const project = new ts_morph_1.Project();
const sourceFile = project.addSourceFileAtPath(filePath);
const interfaces = [];
sourceFile.getInterfaces().forEach((iface) => {
const name = iface.getName();
const keys = Array.from(new Set(iface
.getProperties()
.map(prop => stripSurroundingQuotes(prop.getName()))));
interfaces.push({ name, keys });
});
return interfaces;
}
/**
* If ts-morph returns a string-literal property name (e.g. "\"Card Number\""),
* remove the surrounding quotes so downstream quotation is applied exactly once.
*/
function stripSurroundingQuotes(str) {
const m = str.match(/^(["'`])(.*)\1$/);
return m ? m[2] : str;
}
/**
* Generates TypeScript enum string from interface key names.
*/
function generateEnum(name, keys) {
const enumName = `${name}Keys`;
const body = keys
.map(original => {
const raw = stripSurroundingQuotes(original);
const namePart = isValidIdentifier(raw) ? raw : JSON.stringify(raw);
const valuePart = JSON.stringify(raw);
return ` ${namePart} = ${valuePart}`;
})
.join(',\n');
return `export enum ${enumName} {\n${body}\n}\n`;
}
/**
* Tests to make sure the identifier is a valid string using regex
* @param str
*/
function isValidIdentifier(str) {
return /^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(str);
}
/**
* Processes a single file and writes enums for interfaces. Keeping async for now
* To resolve race condition issues on the thread
*/
async function generateEnums(filePath, relativePath, outputBaseDir) {
const funcName = 'generateEnums';
logger_1.Logger.debug(funcName, `Generating enums for ${filePath}`);
const outputDir = node_path_1.default.join(outputBaseDir, node_path_1.default.dirname(relativePath));
(0, file_system_1.ensureDir)(outputDir);
const parsed = extractInterfaceKeysFromFile(filePath);
if (parsed.length === 0) {
logger_1.Logger.warn(funcName, `No interfaces found in ${filePath}`);
return;
}
const enumsContent = parsed.map(i => generateEnum(i.name, i.keys)).join('\n');
const outPath = node_path_1.default.join(outputDir, node_path_1.default.basename(filePath, '.ts') + '.enums.ts');
fs_1.default.writeFileSync(outPath, enumsContent, 'utf-8');
logger_1.Logger.debug(funcName, `Generated: ${outPath}`);
}
/**
* Parses a file structure housing interfaces and regenerates the directory tree with enums.
* Only does TypeScript Interfaces for now. This needs to be async for proper chaining as the previous
* Executions should save first
* @param schemaDir
* @param outputDir
* @param ext
*/
async function generateEnumsFromPath(schemaDir, outputDir, ext = '.ts') {
const funcName = 'generateEnumsFromPath';
logger_1.Logger.debug(funcName, `Walking directory for enums: ${schemaDir}`);
(0, file_system_1.ensureDir)(outputDir);
const tasks = [];
(0, file_system_1.walkDirectory)(schemaDir, (filePath, relativePath) => {
// Wrap in Promise.resolve in case generateEnums becomes async later
const result = Promise.resolve(generateEnums(filePath, relativePath, outputDir));
tasks.push(result);
}, ext);
await Promise.all(tasks);
}
;