/** * TypeScript declaration file generator — produces index.d.ts with all resource classes. * * Output includes: * - Resource classes with typed constructors and readonly attributes * - Property type classes * - Enum union types * - Static content (intrinsics, pseudo-parameters, common interfaces) */ import type { SchemaParseResult, ParsedPropertyType } from "../spec/parse"; import { cfnShortName } from "../spec/parse"; import type { NamingStrategy } from "./naming"; import { propertyTypeName, extractDefName } from "./naming"; import { writeResourceClass, writePropertyClass, writeEnumType, type DtsProperty, type DtsAttribute, } from "@intentius/chant/codegen/generate-typescript"; interface ResourceEntry { tsName: string; properties: DtsProperty[]; attributes: DtsAttribute[]; remap: Map; } interface PropertyEntry { tsName: string; properties: DtsProperty[]; remap: Map; } interface EnumEntry { tsName: string; values: string[]; } /** * Generate the complete .d.ts file content. */ export function generateTypeScriptDeclarations( results: SchemaParseResult[], naming: NamingStrategy, ): string { const lines: string[] = []; lines.push("// Code generated by chant aws generate. DO NOT EDIT generated sections."); const resources: ResourceEntry[] = []; const properties: PropertyEntry[] = []; const enums: EnumEntry[] = []; for (const r of results) { const cfnType = r.resource.typeName; const tsName = naming.resolve(cfnType); if (!tsName) continue; // Build remap from parser property type names to resolved names const shortName = cfnShortName(cfnType); const remap = new Map(); for (const pt of r.propertyTypes) { const defName = extractDefName(pt.name, shortName); remap.set(pt.name, propertyTypeName(tsName, defName)); } for (const e of r.enums) { const defName = extractDefName(e.name, shortName); remap.set(e.name, propertyTypeName(tsName, defName)); } // Map ParsedProperty/ParsedAttribute → DtsProperty/DtsAttribute const dtsProps: DtsProperty[] = r.resource.properties.map((p) => ({ name: p.name, type: isPolicyDocumentProperty(p.name, p.tsType) ? "PolicyDocument" : p.tsType, required: p.required, description: p.description, })); const dtsAttrs: DtsAttribute[] = r.resource.attributes.map((a) => ({ name: a.name.replace(/\./g, "_").replace(/\*/g, "Item"), // Subscribers.*.Status → Subscribers_Item_Status type: a.tsType, })); resources.push({ tsName, properties: dtsProps, attributes: dtsAttrs, remap }); // Alias entries as separate resource entries for (const alias of naming.aliases(cfnType)) { resources.push({ tsName: alias, properties: dtsProps, attributes: dtsAttrs, remap }); } const ptAliases = naming.propertyTypeAliases(cfnType); for (const pt of r.propertyTypes) { const defName = extractDefName(pt.name, shortName); const ptName = propertyTypeName(tsName, defName); const ptProps: DtsProperty[] = pt.properties.map((p) => ({ name: p.name, type: isPolicyDocumentProperty(p.name, p.tsType) ? "PolicyDocument" : p.tsType, required: p.required, description: p.description, })); properties.push({ tsName: ptName, properties: ptProps, remap }); // Alias class declaration for globally unique property types if (ptAliases) { const aliasName = ptAliases.get(defName); if (aliasName) { properties.push({ tsName: aliasName, properties: ptProps, remap }); } } } for (const e of r.enums) { const defName = extractDefName(e.name, shortName); const eName = propertyTypeName(tsName, defName); enums.push({ tsName: eName, values: e.values }); } } // Sort all entries alphabetically resources.sort((a, b) => a.tsName.localeCompare(b.tsName)); properties.sort((a, b) => a.tsName.localeCompare(b.tsName)); enums.sort((a, b) => a.tsName.localeCompare(b.tsName)); // Section 1: Resource classes lines.push(""); lines.push("// --- Resource classes ---"); for (const re of resources) { writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap, "CFResourceAttributes"); } // Section 2: Property classes lines.push(""); lines.push("// --- Property classes ---"); for (const pe of properties) { writePropertyClass(lines, pe.tsName, pe.properties, pe.remap); } // Section 3: Enum types if (enums.length > 0) { lines.push(""); lines.push("// --- Enum types ---"); for (const ee of enums) { writeEnumType(lines, ee.tsName, ee.values); } } // Section 4: Static content lines.push(""); lines.push(staticTypeScript()); lines.push(""); return lines.join("\n"); } // --- PolicyDocument type overrides --- const policyDocumentPropertyNames = new Set([ "assumerolepolicydocument", "policydocument", "permissionspolicydocument", "resourcepolicydocument", ]); function isPolicyDocumentProperty(name: string, tsType: string): boolean { return tsType === "Record" && policyDocumentPropertyNames.has(name.toLowerCase()); } // --- Static content --- function staticTypeScript(): string { const lines: string[] = []; // Intrinsic functions lines.push("// --- Intrinsic functions ---"); lines.push(""); const intrinsics: [string, string][] = [ ["And", "(conditions: unknown[]): any"], ["Base64", "(value: unknown): any"], ["Cidr", "(ipBlock: unknown, count: unknown, cidrBits?: unknown): any"], ["Condition", "(condition: string): any"], ["Equals", "(a: unknown, b: unknown): any"], ["FindInMap", "(mapName: string, firstKey: unknown, secondKey: unknown): any"], ["GetAtt", "(logicalName: string, attribute: string): any"], ["GetAZs", "(region?: string): any"], ["If", "(condition: string, valueIfTrue: unknown, valueIfFalse: unknown): any"], ["ImportValue", "(sharedValue: unknown): any"], ["Join", "(delimiter: string, values: unknown[]): any"], ["Not", "(condition: unknown): any"], ["Or", "(conditions: unknown[]): any"], ["Ref", "(logicalName: string): any"], ["Select", "(index: number, values: unknown[]): any"], ["Split", "(delimiter: string, source: unknown): any"], ["Sub", "(parts: TemplateStringsArray, ...values: unknown[]): any"], ["Transform", "(name: string, parameters: Record): any"], ]; intrinsics.sort((a, b) => a[0].localeCompare(b[0])); for (const [name, sig] of intrinsics) { lines.push(`export declare function ${name}${sig};`); } // Pseudo-parameters lines.push(""); lines.push("// --- Pseudo-parameters ---"); lines.push(""); lines.push("export declare const AWS: {"); const pseudoParams = [ "AccountId", "NotificationARNs", "NoValue", "Partition", "Region", "StackId", "StackName", "URLSuffix", ]; for (const p of pseudoParams) { lines.push(` readonly ${p}: any;`); } lines.push("};"); // Type interfaces lines.push(""); lines.push("// --- Type interfaces ---"); lines.push(""); lines.push("export interface Declarable {"); lines.push(" readonly entityType: string;"); lines.push("}"); lines.push(""); lines.push("export interface CFResourceAttributes {"); lines.push(" DependsOn?: Declarable | Declarable[] | string | string[];"); lines.push(" Condition?: string;"); lines.push(' DeletionPolicy?: "Delete" | "Retain" | "RetainExceptOnCreate" | "Snapshot";'); lines.push(' UpdateReplacePolicy?: "Delete" | "Retain" | "Snapshot";'); lines.push(" UpdatePolicy?: {"); lines.push(" AutoScalingReplacingUpdate?: { WillReplace?: boolean };"); lines.push(" AutoScalingRollingUpdate?: {"); lines.push(" MaxBatchSize?: number;"); lines.push(" MinInstancesInService?: number;"); lines.push(" PauseTime?: string;"); lines.push(" WaitOnResourceSignals?: boolean;"); lines.push(" };"); lines.push(" };"); lines.push(" CreationPolicy?: {"); lines.push(" ResourceSignal?: { Count?: number; Timeout?: string };"); lines.push(" };"); lines.push(" Metadata?: Record;"); lines.push("}"); lines.push(""); lines.push("export interface PolicyDocument {"); lines.push(' Version?: "2012-10-17" | "2008-10-17";'); lines.push(" Id?: string;"); lines.push(" Statement: IamPolicyStatement | IamPolicyStatement[];"); lines.push("}"); lines.push(""); lines.push("export interface IamPolicyStatement {"); lines.push(" Sid?: string;"); lines.push(' Effect: "Allow" | "Deny";'); lines.push(" Principal?: IamPolicyPrincipal;"); lines.push(" NotPrincipal?: IamPolicyPrincipal;"); lines.push(" Action?: string | string[];"); lines.push(" NotAction?: string | string[];"); lines.push(" Resource?: string | string[];"); lines.push(" NotResource?: string | string[];"); lines.push(" Condition?: Record>;"); lines.push("}"); lines.push(""); lines.push('export type IamPolicyPrincipal = "*" | {'); lines.push(" AWS?: string | string[];"); lines.push(" Service?: string | string[];"); lines.push(" Federated?: string | string[];"); lines.push("};") lines.push(""); lines.push("export interface Tag {"); lines.push(" key: string;"); lines.push(" value: string;"); lines.push("}"); return lines.join("\n"); }