fix-missing-exports.mjs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env node
  2. /**
  3. * Fixes TS2339 "Property X does not exist on type 'typeof import(...)'"
  4. * by adding missing exports to the stub module files.
  5. * Also re-runs TS2305/TS2724 fixes.
  6. */
  7. import { execSync } from 'child_process';
  8. import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
  9. import { dirname, join } from 'path';
  10. const ROOT = '/Users/konghayao/code/ai/claude-code';
  11. // Run tsc and capture errors
  12. let errors;
  13. try {
  14. errors = execSync('npx tsc --noEmit 2>&1', { encoding: 'utf-8', cwd: ROOT, maxBuffer: 50 * 1024 * 1024 });
  15. } catch (e) {
  16. errors = e.stdout || '';
  17. }
  18. // ============================================================
  19. // 1. Fix TS2339 on typeof import(...) - add missing exports
  20. // ============================================================
  21. // Map: module file path -> Set<property name>
  22. const missingExports = new Map();
  23. for (const line of errors.split('\n')) {
  24. // TS2339: Property 'X' does not exist on type 'typeof import("path")'
  25. let m = line.match(/error TS2339: Property '(\w+)' does not exist on type 'typeof import\("(.+?)"\)'/);
  26. if (m) {
  27. const [, prop, modPath] = m;
  28. let filePath;
  29. if (modPath.startsWith('/')) {
  30. filePath = modPath;
  31. } else {
  32. continue; // skip non-absolute paths for now
  33. }
  34. // Try .ts then .tsx
  35. for (const ext of ['.ts', '.tsx']) {
  36. const fp = filePath + ext;
  37. if (existsSync(fp)) {
  38. if (!missingExports.has(fp)) missingExports.set(fp, new Set());
  39. missingExports.get(fp).add(prop);
  40. break;
  41. }
  42. }
  43. }
  44. // TS2339 on type '{ default: typeof import("...") }' (namespace import)
  45. m = line.match(/error TS2339: Property '(\w+)' does not exist on type '\{ default: typeof import\("(.+?)"\)/);
  46. if (m) {
  47. const [, prop, modPath] = m;
  48. for (const ext of ['.ts', '.tsx']) {
  49. const fp = (modPath.startsWith('/') ? modPath : join(ROOT, modPath)) + ext;
  50. if (existsSync(fp)) {
  51. if (!missingExports.has(fp)) missingExports.set(fp, new Set());
  52. missingExports.get(fp).add(prop);
  53. break;
  54. }
  55. }
  56. }
  57. }
  58. console.log(`Found ${missingExports.size} modules needing export additions for TS2339`);
  59. let ts2339Fixed = 0;
  60. for (const [filePath, props] of missingExports) {
  61. const content = readFileSync(filePath, 'utf-8');
  62. const existingExports = new Set();
  63. // Parse existing exports
  64. const exportRegex = /export\s+(?:type|const|function|class|let|var|default)\s+(\w+)/g;
  65. let em;
  66. while ((em = exportRegex.exec(content)) !== null) {
  67. existingExports.add(em[1]);
  68. }
  69. const newExports = [];
  70. for (const prop of props) {
  71. if (!existingExports.has(prop) && !content.includes(`export { ${prop}`) && !content.includes(`, ${prop}`)) {
  72. newExports.push(`export const ${prop}: any = (() => {}) as any;`);
  73. ts2339Fixed++;
  74. }
  75. }
  76. if (newExports.length > 0) {
  77. const newContent = content.trimEnd() + '\n' + newExports.join('\n') + '\n';
  78. writeFileSync(filePath, newContent);
  79. }
  80. }
  81. console.log(`Added ${ts2339Fixed} missing exports for TS2339`);
  82. // ============================================================
  83. // 2. Fix TS2305 - Module has no exported member
  84. // ============================================================
  85. const ts2305Fixes = new Map();
  86. for (const line of errors.split('\n')) {
  87. let m = line.match(/^(.+?)\(\d+,\d+\): error TS2305: Module '"(.+?)"' has no exported member '(.+?)'/);
  88. if (!m) continue;
  89. const [, srcFile, mod, member] = m;
  90. // Resolve module path
  91. let resolvedPath;
  92. if (mod.startsWith('.') || mod.startsWith('src/')) {
  93. const base = mod.startsWith('.') ? join(dirname(srcFile), mod) : mod;
  94. const resolved = join(ROOT, base).replace(/\.js$/, '');
  95. for (const ext of ['.ts', '.tsx']) {
  96. if (existsSync(resolved + ext)) {
  97. resolvedPath = resolved + ext;
  98. break;
  99. }
  100. }
  101. }
  102. if (resolvedPath) {
  103. if (!ts2305Fixes.has(resolvedPath)) ts2305Fixes.set(resolvedPath, new Set());
  104. ts2305Fixes.get(resolvedPath).add(member);
  105. }
  106. }
  107. let ts2305Fixed = 0;
  108. for (const [filePath, members] of ts2305Fixes) {
  109. const content = readFileSync(filePath, 'utf-8');
  110. const newExports = [];
  111. for (const member of members) {
  112. if (!content.includes(`export type ${member}`) && !content.includes(`export const ${member}`) && !content.includes(`export function ${member}`)) {
  113. newExports.push(`export type ${member} = any;`);
  114. ts2305Fixed++;
  115. }
  116. }
  117. if (newExports.length > 0) {
  118. writeFileSync(filePath, content.trimEnd() + '\n' + newExports.join('\n') + '\n');
  119. }
  120. }
  121. console.log(`Added ${ts2305Fixed} missing exports for TS2305`);
  122. // ============================================================
  123. // 3. Fix TS2724 - no exported member named X. Did you mean Y?
  124. // ============================================================
  125. const ts2724Fixes = new Map();
  126. for (const line of errors.split('\n')) {
  127. let m = line.match(/^(.+?)\(\d+,\d+\): error TS2724: '"(.+?)"' has no exported member named '(.+?)'/);
  128. if (!m) continue;
  129. const [, srcFile, mod, member] = m;
  130. let resolvedPath;
  131. if (mod.startsWith('.') || mod.startsWith('src/')) {
  132. const base = mod.startsWith('.') ? join(dirname(srcFile), mod) : mod;
  133. const resolved = join(ROOT, base).replace(/\.js$/, '');
  134. for (const ext of ['.ts', '.tsx']) {
  135. if (existsSync(resolved + ext)) {
  136. resolvedPath = resolved + ext;
  137. break;
  138. }
  139. }
  140. }
  141. if (resolvedPath) {
  142. if (!ts2724Fixes.has(resolvedPath)) ts2724Fixes.set(resolvedPath, new Set());
  143. ts2724Fixes.get(resolvedPath).add(member);
  144. }
  145. }
  146. let ts2724Fixed = 0;
  147. for (const [filePath, members] of ts2724Fixes) {
  148. const content = readFileSync(filePath, 'utf-8');
  149. const newExports = [];
  150. for (const member of members) {
  151. if (!content.includes(`export type ${member}`) && !content.includes(`export const ${member}`)) {
  152. newExports.push(`export type ${member} = any;`);
  153. ts2724Fixed++;
  154. }
  155. }
  156. if (newExports.length > 0) {
  157. writeFileSync(filePath, content.trimEnd() + '\n' + newExports.join('\n') + '\n');
  158. }
  159. }
  160. console.log(`Added ${ts2724Fixed} missing exports for TS2724`);
  161. // ============================================================
  162. // 4. Fix TS2307 - Cannot find module (create stub files)
  163. // ============================================================
  164. let ts2307Fixed = 0;
  165. for (const line of errors.split('\n')) {
  166. let m = line.match(/^(.+?)\(\d+,\d+\): error TS2307: Cannot find module '(.+?)'/);
  167. if (!m) continue;
  168. const [, srcFile, mod] = m;
  169. if (mod.endsWith('.md') || mod.endsWith('.css')) continue;
  170. if (!mod.startsWith('.') && !mod.startsWith('src/')) continue;
  171. const srcDir = dirname(srcFile);
  172. let resolved;
  173. if (mod.startsWith('.')) {
  174. resolved = join(ROOT, srcDir, mod).replace(/\.js$/, '.ts');
  175. } else {
  176. resolved = join(ROOT, mod).replace(/\.js$/, '.ts');
  177. }
  178. if (!existsSync(resolved) && resolved.startsWith(ROOT + '/src/')) {
  179. const dir = dirname(resolved);
  180. if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
  181. // Collect imports from the source file for this module
  182. const srcContent = readFileSync(join(ROOT, srcFile), 'utf-8');
  183. const importRegex = new RegExp(`import\\s+(?:type\\s+)?\\{([^}]+)\\}\\s+from\\s+['"]${mod.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`, 'g');
  184. const members = new Set();
  185. let im;
  186. while ((im = importRegex.exec(srcContent)) !== null) {
  187. im[1].split(',').map(s => s.trim().replace(/^type\s+/, '').split(/\s+as\s+/)[0].trim()).filter(Boolean).forEach(m => members.add(m));
  188. }
  189. const lines = ['// Auto-generated stub'];
  190. for (const member of members) {
  191. lines.push(`export type ${member} = any;`);
  192. }
  193. if (members.size === 0) lines.push('export {};');
  194. writeFileSync(resolved, lines.join('\n') + '\n');
  195. ts2307Fixed++;
  196. }
  197. }
  198. console.log(`Created ${ts2307Fixed} new stub files for TS2307`);