create-type-stubs.mjs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #!/usr/bin/env node
  2. /**
  3. * Analyzes TypeScript errors and creates stub modules with proper named exports.
  4. * Run: node scripts/create-type-stubs.mjs
  5. */
  6. import { execSync } from 'child_process';
  7. import { writeFileSync, existsSync, mkdirSync } from 'fs';
  8. import { dirname, join } from 'path';
  9. const ROOT = '/Users/konghayao/code/ai/claude-code';
  10. // Run tsc and capture errors (tsc exits non-zero on type errors, that's expected)
  11. let errors;
  12. try {
  13. errors = execSync('npx tsc --noEmit 2>&1', { encoding: 'utf-8', cwd: ROOT });
  14. } catch (e) {
  15. errors = e.stdout || '';
  16. }
  17. // Map: resolved file path -> Set of needed named exports
  18. const stubExports = new Map();
  19. // Map: resolved file path -> Set of needed default export names
  20. const defaultExports = new Map();
  21. for (const line of errors.split('\n')) {
  22. // TS2614: Module '"X"' has no exported member 'Y'. Did you mean to use 'import Y from "X"' instead?
  23. let m = line.match(/error TS2614: Module '"(.+?)"' has no exported member '(.+?)'\. Did you mean to use 'import .* from/);
  24. if (m) {
  25. const [, mod, member] = m;
  26. if (!defaultExports.has(mod)) defaultExports.set(mod, new Set());
  27. defaultExports.get(mod).add(member);
  28. continue;
  29. }
  30. // TS2305: Module '"X"' has no exported member 'Y'
  31. m = line.match(/error TS2305: Module '"(.+?)"' has no exported member '(.+?)'/);
  32. if (m) {
  33. const [, mod, member] = m;
  34. if (!stubExports.has(mod)) stubExports.set(mod, new Set());
  35. stubExports.get(mod).add(member);
  36. }
  37. // TS2724: '"X"' has no exported member named 'Y'. Did you mean 'Z'?
  38. m = line.match(/error TS2724: '"(.+?)"' has no exported member named '(.+?)'/);
  39. if (m) {
  40. const [, mod, member] = m;
  41. if (!stubExports.has(mod)) stubExports.set(mod, new Set());
  42. stubExports.get(mod).add(member);
  43. }
  44. // TS2306: File 'X' is not a module
  45. m = line.match(/error TS2306: File '(.+?)' is not a module/);
  46. if (m) {
  47. const filePath = m[1];
  48. if (!stubExports.has(filePath)) stubExports.set(filePath, new Set());
  49. }
  50. // TS2307: Cannot find module 'X'
  51. m = line.match(/^(.+?)\(\d+,\d+\): error TS2307: Cannot find module '(.+?)'/);
  52. if (m) {
  53. const [srcFile, mod] = [m[1], m[2]];
  54. if (mod.endsWith('.md')) continue;
  55. if (!mod.startsWith('.') && !mod.startsWith('src/')) continue;
  56. // Will be resolved below
  57. const srcDir = dirname(srcFile);
  58. const resolved = join(ROOT, srcDir, mod).replace(/\.js$/, '.ts');
  59. if (resolved.startsWith(ROOT + '/') && !existsSync(resolved)) {
  60. if (!stubExports.has(resolved)) stubExports.set(resolved, new Set());
  61. }
  62. }
  63. }
  64. // Also parse actual import statements from source files to find what's needed
  65. import { readFileSync } from 'fs';
  66. const allSourceFiles = execSync('find src -name "*.ts" -o -name "*.tsx"', { encoding: 'utf-8', cwd: ROOT }).trim().split('\n');
  67. for (const file of allSourceFiles) {
  68. const content = readFileSync(join(ROOT, file), 'utf-8');
  69. const srcDir = dirname(file);
  70. // Find all import { X, Y } from 'module'
  71. const importRegex = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"](.+?)['"]/g;
  72. let match;
  73. while ((match = importRegex.exec(content)) !== null) {
  74. const members = match[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
  75. let mod = match[2];
  76. if (!mod.startsWith('.') && !mod.startsWith('src/')) continue;
  77. const resolved = join(ROOT, srcDir, mod).replace(/\.js$/, '.ts');
  78. if (resolved.startsWith(ROOT + '/') && !existsSync(resolved)) {
  79. if (!stubExports.has(resolved)) stubExports.set(resolved, new Set());
  80. for (const member of members) {
  81. stubExports.get(resolved).add(member);
  82. }
  83. }
  84. }
  85. }
  86. // Now create/update all stub files
  87. let created = 0;
  88. for (const [filePath, exports] of stubExports) {
  89. const relPath = filePath.replace(ROOT + '/', '');
  90. const dir = dirname(filePath);
  91. if (!existsSync(dir)) {
  92. mkdirSync(dir, { recursive: true });
  93. }
  94. const lines = ['// Auto-generated type stub — replace with real implementation'];
  95. for (const exp of exports) {
  96. lines.push(`export type ${exp} = any;`);
  97. }
  98. // Check if there are default exports needed
  99. for (const [mod, defs] of defaultExports) {
  100. // Match the module path
  101. const modNorm = mod.replace(/\.js$/, '').replace(/^src\//, '');
  102. const filePathNorm = relPath.replace(/\.ts$/, '');
  103. if (modNorm === filePathNorm || mod === relPath) {
  104. for (const def of defs) {
  105. lines.push(`export type ${def} = any;`);
  106. }
  107. }
  108. }
  109. // Ensure at least export {}
  110. if (exports.size === 0) {
  111. lines.push('export {};');
  112. }
  113. writeFileSync(filePath, lines.join('\n') + '\n');
  114. created++;
  115. }
  116. console.log(`Created/updated ${created} stub files`);
  117. console.log(`Total named exports resolved: ${[...stubExports.values()].reduce((a, b) => a + b.size, 0)}`);