website cleanup

This commit is contained in:
2025-12-24 21:44:58 +01:00
parent 9b683a59d3
commit d78854a4c6
277 changed files with 6141 additions and 2693 deletions

View File

@@ -173,6 +173,10 @@ function collectDependencies(schema: any, deps: Set<string>, allSchemas: Record<
collectDependencies(propSchema, deps, allSchemas);
}
}
if (schema.additionalProperties) {
collectDependencies(schema.additionalProperties, deps, allSchemas);
}
if (schema.oneOf) {
for (const subSchema of schema.oneOf) {
@@ -206,6 +210,11 @@ function schemaToTypeString(schema: any): string {
}
if (schema.type === 'object') {
if (schema.additionalProperties) {
const valueType = schemaToTypeString(schema.additionalProperties);
return `Record<string, ${valueType}>`;
}
if (schema.properties) {
// Inline object type
const props = Object.entries(schema.properties)
@@ -243,4 +252,4 @@ function schemaToTypeString(schema: any): string {
}
}
generateTypes().catch(console.error);
generateTypes().catch(console.error);

View File

@@ -18,6 +18,7 @@ interface OpenAPISchema {
$ref?: string;
items?: OpenAPISchema;
properties?: Record<string, OpenAPISchema>;
additionalProperties?: OpenAPISchema;
required?: string[];
enum?: string[];
nullable?: boolean;
@@ -78,13 +79,50 @@ async function generateSpec() {
async function processDTOFile(filePath: string, schemas: Record<string, OpenAPISchema>) {
const content = await fs.readFile(filePath, 'utf-8');
// Find class definitions with DTO suffix
const classRegex = /export\s+class\s+(\w+(?:DTO|Dto))\s*(?:extends\s+\w+)?\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gs;
let classMatch;
// Find exported class definitions with DTO suffix.
// NOTE: We cannot use a naive regex to capture the full class body because
// decorators often contain object literals with braces (e.g. @ApiProperty({ ... })).
// Instead, we locate the opening brace and then parse using a simple brace counter.
const classRegex = /export\s+class\s+(\w+)\b/g;
let classMatch: RegExpExecArray | null;
while ((classMatch = classRegex.exec(content)) !== null) {
const className = classMatch[1];
const classBody = classMatch[2];
if (!className.endsWith('DTO') && !className.endsWith('Dto')) {
continue;
}
const declStartIndex = classMatch.index;
const braceOpenIndex = content.indexOf('{', classRegex.lastIndex);
if (braceOpenIndex === -1) {
continue;
}
// Walk forward to find the matching closing brace.
// This is intentionally simple: it counts braces and does not attempt to fully
// understand strings/comments. It's sufficient for our DTO files where braces
// are primarily used for TS blocks and decorator object literals.
let depth = 0;
let i = braceOpenIndex;
let braceCloseIndex = -1;
for (; i < content.length; i++) {
const ch = content[i];
if (ch === '{') depth++;
if (ch === '}') depth--;
if (depth === 0) {
braceCloseIndex = i;
break;
}
}
if (braceCloseIndex === -1) {
console.warn(` ⚠️ Could not find closing brace for ${className} in ${filePath}`);
continue;
}
const classBody = content.slice(braceOpenIndex + 1, braceCloseIndex);
// Normalize class name to always use DTO suffix (not Dto)
const normalizedName = className.endsWith('Dto') ?
@@ -133,6 +171,11 @@ function extractSchemaFromClassBody(classBody: string, fullContent: string): Ope
if (propertyMatch) {
const [, propName, optional, propType] = propertyMatch;
// Strip any default initializer from the "type" capture.
// Example: "sponsor: SponsorDTO = new SponsorDTO();" -> "SponsorDTO"
// Without this, later mapping may fall back to "string" and corrupt the schema.
const cleanedType = propType.split('=')[0].trim();
// Skip if propName is a TypeScript/decorator keyword
if (['constructor', 'private', 'public', 'protected', 'static', 'readonly'].includes(propName)) {
currentDecorators = [];
@@ -150,7 +193,7 @@ function extractSchemaFromClassBody(classBody: string, fullContent: string): Ope
}
// Extract type from @ApiProperty decorator if present
let schema = extractTypeFromDecorators(currentDecorators, propType);
let schema = extractTypeFromDecorators(currentDecorators, cleanedType);
properties[propName] = schema;
currentDecorators = [];
@@ -174,7 +217,8 @@ function extractTypeFromDecorators(decorators: string[], tsType: string): OpenAP
const decoratorStr = decorators.join(' ');
// Check for @ApiProperty type specification
const apiPropertyMatch = decoratorStr.match(/@ApiProperty\s*\(\s*\{([^}]*)\}\s*\)/s);
// NOTE: Avoid the RegExp dotAll (/s) flag to keep compatibility with older TS targets.
const apiPropertyMatch = decoratorStr.match(/@ApiProperty\s*\(\s*\{([\s\S]*?)\}\s*\)/);
if (apiPropertyMatch) {
const apiPropertyContent = apiPropertyMatch[1];
@@ -223,6 +267,28 @@ function mapTypeToSchema(type: string): OpenAPISchema {
// Clean up the type
type = type.replace(/[;!]/g, '').trim();
const normalizeDtoName = (name: string) => (name.endsWith('Dto') ? name.slice(0, -3) + 'DTO' : name);
// Handle Record<string, T>
// eslint-disable-next-line no-useless-escape
const recordMatch = type.match(/^Record<\s*string\s*,\s*(.+)\s*>$/);
if (recordMatch) {
return {
type: 'object',
additionalProperties: mapTypeToSchema(recordMatch[1]),
};
}
// Handle index signature object types: { [key: string]: T }
// eslint-disable-next-line no-useless-escape
const indexSigMatch = type.match(/^\{\s*\[\s*\w+\s*:\s*(string|number)\s*\]\s*:\s*(.+)\s*\}$/);
if (indexSigMatch) {
return {
type: 'object',
additionalProperties: mapTypeToSchema(indexSigMatch[2]),
};
}
// Handle union with null
if (type.includes('| null') || type.includes('null |')) {
const baseType = type.replace(/\|\s*null/g, '').replace(/null\s*\|/g, '').trim();
@@ -274,11 +340,11 @@ function mapTypeToSchema(type: string): OpenAPISchema {
// Handle DTO references
if (type.endsWith('DTO') || type.endsWith('Dto')) {
return { $ref: `#/components/schemas/${type}` };
return { $ref: `#/components/schemas/${normalizeDtoName(type)}` };
}
// Default to string for unknown types
return { type: 'string' };
}
generateSpec().catch(console.error);
generateSpec().catch(console.error);