name-export.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. #!/usr/bin/env node
  2. /**
  3. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  4. * SPDX-License-Identifier: MIT
  5. */
  6. const fs = require('fs');
  7. const path = require('path');
  8. /**
  9. * Convert wildcard exports to named exports in index.ts files across multiple folders
  10. * This script analyzes each exported file and extracts their named exports
  11. */
  12. const folders = ['components', 'hooks', 'plugins', 'shared', 'validate', 'form-plugins', 'effects'];
  13. const SRC_DIR = path.join(__dirname, '..', 'src');
  14. /**
  15. * Extract all named exports from a file, distinguishing between values and types
  16. * @param {string} filePath - Path of the file to analyze
  17. * @returns {{values: string[], types: string[]}} - Object containing value exports and type exports
  18. */
  19. function extractNamedExports(filePath) {
  20. try {
  21. const content = fs.readFileSync(filePath, 'utf-8');
  22. const valueExports = [];
  23. const typeExports = [];
  24. // Collect all type definition names
  25. const typeDefinitions = new Set();
  26. const typePatterns = [
  27. /\b(?:type|interface)\s+(\w+)/g,
  28. /\bexport\s+(?:type|interface)\s+(\w+)/g,
  29. ];
  30. let match;
  31. for (const pattern of typePatterns) {
  32. while ((match = pattern.exec(content)) !== null) {
  33. typeDefinitions.add(match[1]);
  34. }
  35. }
  36. // Match various export patterns
  37. const exportPatterns = [
  38. // export const/var/let/function/class/type/interface
  39. /\bexport\s+(const|var|let|function|class|type|interface)\s+(\w+)/g,
  40. // export { name1, name2 }
  41. /\bexport\s*\{([^}]+)\}/g,
  42. // export { name as alias }
  43. /\bexport\s*\{[^}]*\b(\w+)\s+as\s+(\w+)[^}]*\}/g,
  44. // export default function name()
  45. /\bexport\s+default\s+(?:function|class)\s+(\w+)/g,
  46. // export type { Type1, Type2 }
  47. /\bexport\s+type\s*\{([^}]+)\}/g,
  48. // export type { Original as Alias }
  49. /\bexport\s+type\s*\{[^}]*\b(\w+)\s+as\s+(\w+)[^}]*\}/g,
  50. ];
  51. // Handle first pattern: export const/var/let/function/class/type/interface
  52. exportPatterns[0].lastIndex = 0;
  53. while ((match = exportPatterns[0].exec(content)) !== null) {
  54. const [, kind, name] = match;
  55. if (kind === 'type' || kind === 'interface' || typeDefinitions.has(name)) {
  56. typeExports.push(name);
  57. } else {
  58. valueExports.push(name);
  59. }
  60. }
  61. // Handle second pattern: export { name1, name2 }
  62. exportPatterns[1].lastIndex = 0;
  63. while ((match = exportPatterns[1].exec(content)) !== null) {
  64. const exportsList = match[1]
  65. .split(',')
  66. .map((item) => item.trim())
  67. .filter((item) => item && !item.includes(' as '));
  68. for (const name of exportsList) {
  69. if (typeDefinitions.has(name)) {
  70. typeExports.push(name);
  71. } else {
  72. valueExports.push(name);
  73. }
  74. }
  75. }
  76. // Handle third pattern: export { name as alias }
  77. exportPatterns[2].lastIndex = 0;
  78. while ((match = exportPatterns[2].exec(content)) !== null) {
  79. const [, original, alias] = match;
  80. if (typeDefinitions.has(original)) {
  81. typeExports.push(alias);
  82. } else {
  83. valueExports.push(alias);
  84. }
  85. }
  86. // Handle fourth pattern: export default function name()
  87. exportPatterns[3].lastIndex = 0;
  88. while ((match = exportPatterns[3].exec(content)) !== null) {
  89. const name = match[1];
  90. if (typeDefinitions.has(name)) {
  91. typeExports.push(name);
  92. } else {
  93. valueExports.push(name);
  94. }
  95. }
  96. // Handle fifth pattern: export type { Type1, Type2 }
  97. exportPatterns[4].lastIndex = 0;
  98. while ((match = exportPatterns[4].exec(content)) !== null) {
  99. const exportsList = match[1]
  100. .split(',')
  101. .map((item) => item.trim())
  102. .filter((item) => item && !item.includes(' as '));
  103. for (const name of exportsList) {
  104. typeExports.push(name);
  105. }
  106. }
  107. // Handle sixth pattern: export type { Original as Alias }
  108. exportPatterns[5].lastIndex = 0;
  109. while ((match = exportPatterns[5].exec(content)) !== null) {
  110. const [, original, alias] = match;
  111. typeExports.push(alias);
  112. }
  113. // Deduplicate and sort
  114. return {
  115. values: [...new Set(valueExports)].sort(),
  116. types: [...new Set(typeExports)].sort(),
  117. };
  118. } catch (error) {
  119. console.error(`Failed to read file: ${filePath}`, error.message);
  120. return { values: [], types: [] };
  121. }
  122. }
  123. /**
  124. * Process named export conversion for a single folder
  125. * @param {string} folderName - Folder name
  126. * @param {string} baseDir - Base directory
  127. */
  128. function processFolder(folderName, baseDir = SRC_DIR) {
  129. const folderPath = path.join(baseDir, folderName);
  130. const indexFile = path.join(folderPath, 'index.ts');
  131. console.log(`🔍 Processing folder: ${folderName}`);
  132. try {
  133. // Check if folder exists
  134. if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
  135. console.warn(`⚠️ Folder does not exist: ${folderName}`);
  136. return;
  137. }
  138. // Generate new named export content
  139. let newContent = '';
  140. // Collect all subdirectory exports
  141. const subDirs = fs
  142. .readdirSync(folderPath, { withFileTypes: true })
  143. .filter((item) => item.isDirectory() && !item.name.startsWith('.'))
  144. .map((item) => item.name);
  145. const namedExportsList = [];
  146. // Process all subdirectories
  147. for (const subDir of subDirs) {
  148. const subDirPath = path.join(folderPath, subDir);
  149. const subPossiblePaths = [
  150. path.join(subDirPath, 'index.ts'),
  151. path.join(subDirPath, 'index.tsx'),
  152. path.join(subDirPath, `${subDir}.ts`),
  153. path.join(subDirPath, `${subDir}.tsx`),
  154. ];
  155. const subFullPath = subPossiblePaths.find(fs.existsSync);
  156. if (!subFullPath) continue;
  157. const { values: subValues, types: subTypes } = extractNamedExports(subFullPath);
  158. if (subValues.length === 0 && subTypes.length === 0) continue;
  159. namedExportsList.push({ importPath: `./${subDir}`, values: subValues, types: subTypes });
  160. console.log(
  161. `✅ Found exports in ${folderName}/${subDir}:\n (${subValues.length} values and ${subTypes.length} types)`
  162. );
  163. }
  164. // Generate import statements
  165. for (const { importPath, values, types } of namedExportsList) {
  166. const imports = [];
  167. if (values.length > 0) {
  168. imports.push(...values);
  169. }
  170. if (types.length > 0) {
  171. imports.push(...types.map((type) => `type ${type}`));
  172. }
  173. if (imports.length > 0) {
  174. newContent += `export { ${imports.join(', ')} } from '${importPath}';
  175. `;
  176. }
  177. }
  178. // Write new content
  179. fs.writeFileSync(indexFile, newContent);
  180. console.log(`✅ Successfully updated ${folderName}/index.ts\n\n`);
  181. } catch (error) {
  182. console.error(`❌ Failed to process ${folderName}:`, error.message);
  183. console.error(error.stack);
  184. }
  185. }
  186. /**
  187. * Main function: Process all configured folders
  188. */
  189. function convertAllFolders() {
  190. console.log('🚀 Starting to process all configured folders...\n');
  191. for (const folder of folders) {
  192. processFolder(folder);
  193. }
  194. console.log('\n🎉 All folders processed successfully!');
  195. processFolder('.');
  196. console.log('\n🎉 Index of form materials is updated!');
  197. }
  198. // If this script is run directly
  199. if (require.main === module) {
  200. convertAllFolders();
  201. }
  202. module.exports = { convertAllFolders, extractNamedExports };