This commit is contained in:
159
src/utils/typeGenerator.ts
Normal file
159
src/utils/typeGenerator.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as prettier from 'prettier';
|
||||
import { EnvSchema, ParsedEnv } from '../core/types';
|
||||
|
||||
export function generateTypeScriptInterface(
|
||||
schema: EnvSchema,
|
||||
parsedEnv: ParsedEnv,
|
||||
interfaceName: string = 'EnvVariables'
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push(`export interface ${interfaceName} {`);
|
||||
|
||||
for (const [key, config] of Object.entries(schema.variables)) {
|
||||
const isRequired = config.required !== false;
|
||||
const optionalMarker = isRequired ? '' : '?';
|
||||
const typeScriptType = getTypeScriptType(config.type, parsedEnv.flat[key]);
|
||||
const comment = config.desc ? ` /** ${config.desc} */` : '';
|
||||
|
||||
let defaultValueStr = '';
|
||||
if (config.default !== undefined) {
|
||||
const formattedDefault = formatDefaultValue(config.default, config.type);
|
||||
defaultValueStr = ` // Default: ${formattedDefault}`;
|
||||
}
|
||||
|
||||
lines.push(comment);
|
||||
lines.push(` ${key}${optionalMarker}: ${typeScriptType};`);
|
||||
if (defaultValueStr) {
|
||||
lines.push(defaultValueStr);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('}');
|
||||
lines.push('');
|
||||
|
||||
const nestedInterfaces = generateNestedInterfaces(parsedEnv.nested);
|
||||
lines.push(nestedInterfaces);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function getTypeScriptType(type: string, value?: string): string {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
return 'string';
|
||||
case 'number':
|
||||
case 'int':
|
||||
case 'port':
|
||||
return 'number';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'email':
|
||||
case 'url':
|
||||
return 'string';
|
||||
case 'json':
|
||||
return 'Record<string, unknown>';
|
||||
case 'enum': {
|
||||
const enumValues = value ? `'${value}'` : 'string';
|
||||
return enumValues;
|
||||
}
|
||||
default:
|
||||
return 'string';
|
||||
}
|
||||
}
|
||||
|
||||
function formatDefaultValue(defaultVal: string | number | boolean, type: string): string {
|
||||
if (typeof defaultVal === 'string') {
|
||||
if (type === 'json') {
|
||||
try {
|
||||
const parsed = JSON.parse(defaultVal);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch {
|
||||
return `'${defaultVal}'`;
|
||||
}
|
||||
}
|
||||
return `'${defaultVal}'`;
|
||||
}
|
||||
return String(defaultVal);
|
||||
}
|
||||
|
||||
function generateNestedInterfaces(obj: unknown, prefix: string = ''): string {
|
||||
if (obj === null || obj === undefined || typeof obj !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const typedObj = obj as Record<string, unknown>;
|
||||
const keys = Object.keys(typedObj);
|
||||
|
||||
if (keys.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
const interfaceName = prefix ? toPascalCase(prefix) : 'NestedEnv';
|
||||
|
||||
lines.push(`export interface ${interfaceName} {`);
|
||||
|
||||
for (const key of keys) {
|
||||
const value = typedObj[key];
|
||||
const propertyName = toCamelCase(key);
|
||||
|
||||
if (value !== null && value !== undefined && typeof value === 'object' && !Array.isArray(value)) {
|
||||
const nestedInterface = generateNestedInterfaces(value, `${prefix}_${key}`);
|
||||
lines.push(` ${propertyName}?: ${toPascalCase(key)};`);
|
||||
lines.push('');
|
||||
lines.push(nestedInterface);
|
||||
} else {
|
||||
const type = inferTypeFromValue(value);
|
||||
lines.push(` ${propertyName}?: ${type};`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('}');
|
||||
lines.push('');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function toPascalCase(str: string): string {
|
||||
return str
|
||||
.split('_')
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
||||
.join('');
|
||||
}
|
||||
|
||||
function toCamelCase(str: string): string {
|
||||
const parts = str.split('_');
|
||||
if (parts.length === 1) return str;
|
||||
return parts[0].toLowerCase() + parts.slice(1).map(p => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join('');
|
||||
}
|
||||
|
||||
function inferTypeFromValue(value: unknown): string {
|
||||
if (value === null || value === undefined) return 'unknown';
|
||||
if (typeof value === 'string') return 'string';
|
||||
if (typeof value === 'number') return 'number';
|
||||
if (typeof value === 'boolean') return 'boolean';
|
||||
if (Array.isArray(value)) return 'unknown[]';
|
||||
if (typeof value === 'object') return 'Record<string, unknown>';
|
||||
return 'string';
|
||||
}
|
||||
|
||||
export async function formatCode(code: string): Promise<string> {
|
||||
try {
|
||||
const formatted = await prettier.format(code, {
|
||||
parser: 'typescript',
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 100
|
||||
});
|
||||
return formatted;
|
||||
} catch {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user