diff --git a/ts/build-dts.ts b/ts/build-dts.ts new file mode 100644 index 0000000..cec210c --- /dev/null +++ b/ts/build-dts.ts @@ -0,0 +1,156 @@ +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}`); \ No newline at end of file diff --git a/ts/bun.lock b/ts/bun.lock index 940f010..2fcf62b 100644 --- a/ts/bun.lock +++ b/ts/bun.lock @@ -21,7 +21,7 @@ "@types/mithril": ["@types/mithril@2.2.8", "", {}, "sha512-FN9Tv1+Nlr0LNPGnIL/xOxLJfu5WW2n8HAFeo4yxF+/O0per/8g080xlXoo+xj8baowAcfsNI3k80DxyLY34gQ=="], - "@types/node": ["@types/node@25.7.0", "", { "dependencies": { "undici-types": "~7.21.0" } }, "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg=="], + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], @@ -29,6 +29,6 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici-types": ["undici-types@7.21.0", "", {}, "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ=="], + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], } } diff --git a/ts/package.json b/ts/package.json index 813f3d4..b9156a9 100644 --- a/ts/package.json +++ b/ts/package.json @@ -13,7 +13,7 @@ "build": "bun run buildcjs && bun run buildmjs && bun run builddts", "buildcjs": "bun build src/index.ts --target browser --format cjs --outfile dist/index.cjs", "buildmjs": "bun build src/index.ts --target browser --format esm --outfile dist/index.mjs", - "builddts": "bun build src/index.ts --dts --outfile dist/index.d.ts", + "builddts": "bun run build-dts.ts", "test": "bun test" }, "devDependencies": { diff --git a/ts/src/index.ts b/ts/src/index.ts index 744cb7b..65173bf 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -51,9 +51,16 @@ export * from "./types"; -export { BinaryReader, packPixels, unpackPixels, packedSize, pad32 } from "./helper"; -export { MonoDisplayParser, MONOFORMAT_MAGIC_HEADER } from "./parser"; -export { MonoDisplayRenderer } from "./renderer"; -export { type MonoDisplayDriverOptions, MonoDisplayDriver } from "./driver"; -export { MonoDisplayFile, loadBinFile, buildBinBuffer } from "./file"; -export { cycleTheme } from "./themes" \ No newline at end of file +export type * from "./types"; +export * from "./helper"; +export type * from "./helper"; +export * from "./parser"; +export type * from "./parser"; +export * from "./renderer"; +export type * from "./renderer"; +export * from "./driver"; +export type * from "./driver"; +export * from "./file"; +export type * from "./file"; +export * from "./themes"; +export type * from "./themes"; \ No newline at end of file