Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion src/import/crd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,74 @@ const SUPPORTED_API_VERSIONS = [

type CustomResourceDefinitionVersion = { name: string; schema?: any };

/**
* TypeScript built-in utility types that may conflict with CRD kind names.
* When a CRD's kind matches one of these, we need to post-process the generated
* code to avoid naming conflicts.
*/
const TYPESCRIPT_BUILTIN_TYPES = new Set([
'Record',
'Map',
'Set',
'Array',
'Object',
'String',
'Number',
'Boolean',
'Symbol',
'Function',
'Promise',
'Pick',
'Omit',
'Partial',
'Required',
'Readonly',
'Extract',
'Exclude',
'NonNullable',
'Parameters',
'ReturnType',
'InstanceType',
'ThisType',
'Awaited',
]);

/**
* Post-processes generated TypeScript code to fix naming conflicts with
* TypeScript built-in utility types.
*
* When a CRD's kind matches a TypeScript built-in (e.g., "Record"), the generated
* code will have a class named "Record" which shadows the built-in Record<K, V>
* utility type. This function replaces usages of the built-in type with an alias.
*
* @param renderedCode The TypeScript code generated by TypeGenerator
* @param kind The CRD kind name
* @returns Post-processed code with conflicts resolved
*/
function fixBuiltinTypeConflicts(renderedCode: string, kind: string): string {
if (!TYPESCRIPT_BUILTIN_TYPES.has(kind)) {
return renderedCode;
}

// Replace usages of the built-in type with the alias
// Match patterns like "Record<string, any>" but not "RecordProps" or "Record.GVK"
const pattern = new RegExp(`\\b${kind}<([^>]+)>`, 'g');
return renderedCode.replace(pattern, `Json${kind}<$1>`);
}

/**
* Generates the type alias needed to fix naming conflicts with TypeScript built-in types.
*
* @param kind The CRD kind name
* @returns The type alias declaration, or empty string if not needed
*/
function getBuiltinTypeAlias(kind: string): string {
if (!TYPESCRIPT_BUILTIN_TYPES.has(kind)) {
return '';
}
return `// Type alias to avoid collision with the ${kind} class defined below\ntype Json${kind}<K extends keyof any, T> = { [P in K]: T };\n\n`;
}

export class CustomResourceDefinition {

private readonly kind: string;
Expand Down Expand Up @@ -95,6 +163,12 @@ export class CustomResourceDefinition {

public async generateTypeScript(code: CodeMaker, options: GenerateOptions) {

// Add type alias once at the beginning if needed for built-in type conflicts
const typeAlias = getBuiltinTypeAlias(this.kind);
if (typeAlias) {
code.line(typeAlias);
}

for (let i = 0; i < this.versions.length; i++) {

const version = this.versions[i];
Expand All @@ -116,7 +190,9 @@ export class CustomResourceDefinition {
suffix,
});

code.line(types.render());
// Post-process to fix any TypeScript built-in type conflicts
const renderedCode = fixBuiltinTypeConflicts(types.render(), this.kind);
code.line(renderedCode);
}
}
}
Expand Down