import { readFileSync, writeFileSync } from "fs"; import { resolve, dirname, relative } from "path"; import ts from "typescript"; const ENTRY = resolve("src/index.ts"); const OUT = resolve("dist/index.d.ts"); const configFile = ts.findConfigFile("./", ts.sys.fileExists, "tsconfig.json"); const configHost: ts.ParseConfigFileHost = { ...ts.sys, onUnRecoverableConfigFileDiagnostic: (d) => { throw new Error(ts.flattenDiagnosticMessageText(d.messageText, "\n")); }, }; const parsed = ts.getParsedCommandLineOfConfigFile( configFile!, { emitDeclarationOnly: true, declaration: true, noEmit: false }, configHost )!; const program = ts.createProgram([ENTRY], parsed.options); const checker = program.getTypeChecker(); const emitted = new Set(); // dedupe across files const lines: string[] = ["// Auto-generated by build-dts.ts", ""]; function relativePath(fullfilename: string): string { return relative(process.cwd(), fullfilename); } function fileText(sf: ts.SourceFile, node: ts.Node): string { return sf.text.slice(node.getFullStart(), node.getEnd()).trim(); } function tryResolveType(sf: ts.SourceFile, node: ts.TypeAliasDeclaration): string | null { try { const type = checker.getTypeAtLocation(node.name); const resolved = checker.typeToString( type, undefined, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseFullyQualifiedType ); if (resolved === node.name.text) return null; return `export type ${node.name.text} = ${resolved};`; } catch { return null; } } function visitFile(sf: ts.SourceFile) { function visit(node: ts.Node) { const isExported = (n: ts.Node) => (n as any).modifiers?.some((m: ts.Modifier) => m.kind === ts.SyntaxKind.ExportKeyword); // export * from "./x" or export type * from "./x" — follow and inline if (ts.isExportDeclaration(node)) { const modSpec = node.moduleSpecifier; if (modSpec && ts.isStringLiteral(modSpec)) { const resolved = ts.resolveModuleName( modSpec.text, sf.fileName, parsed.options, ts.sys ).resolvedModule; if (resolved && !resolved.isExternalLibraryImport) { const targetSf = program.getSourceFile(resolved.resolvedFileName); if (targetSf) { visitFile(targetSf); // recurse into the re-exported file return; } } } // external or unresolved — copy verbatim const text = fileText(sf, node); if (!emitted.has(text)) { emitted.add(text); lines.push(`// ${relativePath(sf.fileName)}\n`); lines.push(text); } return; } // export type Foo = ... if (ts.isTypeAliasDeclaration(node) && isExported(node)) { const resolved = tryResolveType(sf, node); const text = resolved ?? fileText(sf, node); if (!emitted.has(node.name.text)) { emitted.add(node.name.text); lines.push(`// ${relativePath(sf.fileName)}\n`); lines.push(text); } return; } // export interface, enum, const enum if ( (ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)) && isExported(node) ) { const text = fileText(sf, node); if (!emitted.has((node as any).name.text)) { emitted.add((node as any).name.text); lines.push(`// ${relativePath(sf.fileName)}\n`); lines.push(text); } return; } // export class if (ts.isClassDeclaration(node) && isExported(node) && node.name) { const text = fileText(sf, node); if (!emitted.has(node.name.text)) { emitted.add(node.name.text); lines.push(`// ${relativePath(sf.fileName)}\n`); lines.push(text); } return; } // export function / export const if ( (ts.isFunctionDeclaration(node) || ts.isVariableStatement(node)) && isExported(node) ) { try { if (ts.isFunctionDeclaration(node) && node.name) { const sym = checker.getSymbolAtLocation(node.name); if (sym && !emitted.has(sym.name)) { emitted.add(sym.name); const type = checker.getTypeOfSymbolAtLocation(sym, node); for (const sig of type.getCallSignatures()) { lines.push(`export declare function ${sym.name}${checker.signatureToString(sig)};`); } return; } } } catch { } const text = fileText(sf, node); if (!emitted.has(text)) { emitted.add(text); lines.push(text); } return; } ts.forEachChild(node, visit); } ts.forEachChild(sf, visit); } const entryFile = program.getSourceFile(ENTRY)!; visitFile(entryFile); lines.push(""); writeFileSync(OUT, lines.join("\n")); // console.log(`✓ wrote ${OUT}`);