Skip to content

Commit ef37939

Browse files
ochafikclaude
andcommitted
feat: add export-symbols script for API surface comparison
Uses TypeScript compiler API to list all exported symbols from types.ts. Useful for verifying no exports are removed between branches. Usage: npx tsx scripts/export-symbols.ts [--json] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2ca16dc commit ef37939

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

scripts/export-symbols.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env npx tsx
2+
/**
3+
* List all exported symbols from src/types.ts using TypeScript compiler API.
4+
*
5+
* Usage:
6+
* npx tsx scripts/export-symbols.ts
7+
* npx tsx scripts/export-symbols.ts --json
8+
*/
9+
10+
import * as ts from 'typescript';
11+
import * as path from 'path';
12+
13+
const typesPath = path.resolve(import.meta.dirname, '../src/types.ts');
14+
const jsonOutput = process.argv.includes('--json');
15+
16+
// Create a program with the types file
17+
const program = ts.createProgram([typesPath], {
18+
target: ts.ScriptTarget.ESNext,
19+
module: ts.ModuleKind.ESNext,
20+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
21+
strict: true,
22+
skipLibCheck: true,
23+
resolveJsonModule: true,
24+
});
25+
26+
const checker = program.getTypeChecker();
27+
const sourceFile = program.getSourceFile(typesPath);
28+
29+
if (!sourceFile) {
30+
console.error('Could not find source file:', typesPath);
31+
process.exit(1);
32+
}
33+
34+
interface ExportInfo {
35+
name: string;
36+
kind: string;
37+
isType: boolean;
38+
}
39+
40+
const exports: ExportInfo[] = [];
41+
42+
// Get the module symbol
43+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
44+
if (moduleSymbol) {
45+
const exportedSymbols = checker.getExportsOfModule(moduleSymbol);
46+
47+
for (const symbol of exportedSymbols) {
48+
const name = symbol.getName();
49+
const declarations = symbol.getDeclarations();
50+
51+
let kind = 'unknown';
52+
let isType = false;
53+
54+
if (declarations && declarations.length > 0) {
55+
const decl = declarations[0];
56+
57+
if (ts.isTypeAliasDeclaration(decl)) {
58+
kind = 'type';
59+
isType = true;
60+
} else if (ts.isInterfaceDeclaration(decl)) {
61+
kind = 'interface';
62+
isType = true;
63+
} else if (ts.isClassDeclaration(decl)) {
64+
kind = 'class';
65+
} else if (ts.isFunctionDeclaration(decl)) {
66+
kind = 'function';
67+
} else if (ts.isVariableDeclaration(decl)) {
68+
kind = 'const';
69+
} else if (ts.isEnumDeclaration(decl)) {
70+
kind = 'enum';
71+
} else if (ts.isExportSpecifier(decl)) {
72+
// Re-exported symbol - check the original
73+
const originalSymbol = checker.getAliasedSymbol(symbol);
74+
const origDecls = originalSymbol.getDeclarations();
75+
if (origDecls && origDecls.length > 0) {
76+
const origDecl = origDecls[0];
77+
if (ts.isTypeAliasDeclaration(origDecl)) {
78+
kind = 'type';
79+
isType = true;
80+
} else if (ts.isInterfaceDeclaration(origDecl)) {
81+
kind = 'interface';
82+
isType = true;
83+
} else if (ts.isVariableDeclaration(origDecl)) {
84+
kind = 'const';
85+
} else if (ts.isEnumDeclaration(origDecl)) {
86+
kind = 'enum';
87+
} else if (ts.isFunctionDeclaration(origDecl)) {
88+
kind = 'function';
89+
}
90+
}
91+
}
92+
}
93+
94+
exports.push({ name, kind, isType });
95+
}
96+
}
97+
98+
// Sort by name
99+
exports.sort((a, b) => a.name.localeCompare(b.name));
100+
101+
if (jsonOutput) {
102+
console.log(JSON.stringify(exports, null, 2));
103+
} else {
104+
// Group by kind
105+
const byKind: Record<string, string[]> = {};
106+
for (const exp of exports) {
107+
const key = exp.kind;
108+
if (!byKind[key]) byKind[key] = [];
109+
byKind[key].push(exp.name);
110+
}
111+
112+
console.log(`Total exports: ${exports.length}\n`);
113+
114+
for (const kind of ['type', 'interface', 'const', 'enum', 'function', 'class', 'unknown']) {
115+
if (byKind[kind]) {
116+
console.log(`${kind} (${byKind[kind].length}):`);
117+
for (const name of byKind[kind].sort()) {
118+
console.log(` ${name}`);
119+
}
120+
console.log();
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)