|
3 | 3 |
|
4 | 4 | import * as path from 'node:path'; |
5 | 5 |
|
6 | | -import { FileSystem, Import, Path } from '@rushstack/node-core-library'; |
| 6 | +import type { IRunScriptOptions } from '@rushstack/heft'; |
| 7 | +import { Async, FileSystem, type FolderItem, Import, JsonFile, Path } from '@rushstack/node-core-library'; |
7 | 8 |
|
8 | | -function generateLibFilesRecursively(options: { |
| 9 | +interface IGenerateOptions { |
9 | 10 | parentSourcePath: string; |
10 | 11 | parentTargetPath: string; |
11 | 12 | parentSrcImportPathWithSlash: string; |
12 | 13 | libShimIndexPath: string; |
13 | | -}): void { |
14 | | - for (const folderItem of FileSystem.readFolderItems(options.parentSourcePath)) { |
15 | | - const sourcePath: string = path.join(options.parentSourcePath, folderItem.name); |
16 | | - const targetPath: string = path.join(options.parentTargetPath, folderItem.name); |
| 14 | +} |
| 15 | + |
| 16 | +interface IFileTask { |
| 17 | + type: 'dts' | 'js'; |
| 18 | + sourcePath: string; |
| 19 | + targetPath: string; |
| 20 | + srcImportPath?: string; |
| 21 | + shimPathLiteral?: string; |
| 22 | +} |
| 23 | + |
| 24 | +async function* collectFileTasksAsync(options: IGenerateOptions): AsyncGenerator<IFileTask> { |
| 25 | + const { parentSourcePath, parentTargetPath, parentSrcImportPathWithSlash, libShimIndexPath } = options; |
| 26 | + const folderItems: FolderItem[] = await FileSystem.readFolderItemsAsync(options.parentSourcePath); |
| 27 | + |
| 28 | + for (const folderItem of folderItems) { |
| 29 | + const itemName: string = folderItem.name; |
| 30 | + const sourcePath: string = `${parentSourcePath}/${itemName}`; |
| 31 | + const targetPath: string = `${parentTargetPath}/${itemName}`; |
17 | 32 |
|
18 | 33 | if (folderItem.isDirectory()) { |
19 | | - // create destination folder |
20 | | - FileSystem.ensureEmptyFolder(targetPath); |
21 | | - generateLibFilesRecursively({ |
| 34 | + // Ensure destination folder exists |
| 35 | + await FileSystem.ensureFolderAsync(targetPath); |
| 36 | + // Recursively yield tasks from subdirectory |
| 37 | + yield* collectFileTasksAsync({ |
22 | 38 | parentSourcePath: sourcePath, |
23 | 39 | parentTargetPath: targetPath, |
24 | | - parentSrcImportPathWithSlash: options.parentSrcImportPathWithSlash + folderItem.name + '/', |
25 | | - libShimIndexPath: options.libShimIndexPath |
| 40 | + parentSrcImportPathWithSlash: parentSrcImportPathWithSlash + itemName + '/', |
| 41 | + libShimIndexPath |
26 | 42 | }); |
27 | | - } else { |
28 | | - if (folderItem.name.endsWith('.d.ts')) { |
29 | | - FileSystem.copyFile({ |
30 | | - sourcePath: sourcePath, |
31 | | - destinationPath: targetPath |
32 | | - }); |
33 | | - } else if (folderItem.name.endsWith('.js')) { |
34 | | - const srcImportPath: string = options.parentSrcImportPathWithSlash + path.parse(folderItem.name).name; |
35 | | - const shimPath: string = path.relative(options.parentTargetPath, options.libShimIndexPath); |
36 | | - const shimPathLiteral: string = JSON.stringify(Path.convertToSlashes(shimPath)); |
37 | | - const srcImportPathLiteral: string = JSON.stringify(srcImportPath); |
38 | | - |
39 | | - FileSystem.writeFile( |
40 | | - targetPath, |
41 | | - // Example: |
42 | | - // module.exports = require("../../../lib-shim/index")._rushSdk_loadInternalModule("logic/policy/GitEmailPolicy"); |
43 | | - `module.exports = require(${shimPathLiteral})._rushSdk_loadInternalModule(${srcImportPathLiteral});` |
44 | | - ); |
| 43 | + } else if (folderItem.name.endsWith('.d.ts')) { |
| 44 | + yield { |
| 45 | + type: 'dts', |
| 46 | + sourcePath, |
| 47 | + targetPath |
| 48 | + }; |
| 49 | + } else if (folderItem.name.endsWith('.js')) { |
| 50 | + const srcImportPath: string = parentSrcImportPathWithSlash + path.parse(folderItem.name).name; |
| 51 | + const shimPath: string = path.relative(parentTargetPath, libShimIndexPath); |
| 52 | + const shimPathLiteral: string = JSON.stringify(Path.convertToSlashes(shimPath)); |
| 53 | + |
| 54 | + yield { |
| 55 | + type: 'js', |
| 56 | + sourcePath, |
| 57 | + targetPath, |
| 58 | + srcImportPath, |
| 59 | + shimPathLiteral |
| 60 | + }; |
| 61 | + } |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +async function processFileTaskAsync(task: IFileTask): Promise<void> { |
| 66 | + const { type, sourcePath, targetPath, srcImportPath, shimPathLiteral } = task; |
| 67 | + if (type === 'dts') { |
| 68 | + await FileSystem.copyFileAsync({ |
| 69 | + sourcePath, |
| 70 | + destinationPath: targetPath |
| 71 | + }); |
| 72 | + } else { |
| 73 | + const srcImportPathLiteral: string = JSON.stringify(srcImportPath); |
| 74 | + |
| 75 | + let namedExportsAssignment: string = ''; |
| 76 | + try { |
| 77 | + // Read the sidecar .exports.json file generated by DeepImportsPlugin to get module exports |
| 78 | + const exportsJsonPath: string = sourcePath.slice(0, -'.js'.length) + '.exports.json'; |
| 79 | + const { moduleExports }: { moduleExports: string[] } = await JsonFile.loadAsync(exportsJsonPath); |
| 80 | + if (moduleExports.length > 0) { |
| 81 | + // Assign named exports after module.exports to ensure they're properly exposed for ESM imports |
| 82 | + namedExportsAssignment = |
| 83 | + '\n' + moduleExports.map((exportName) => `exports.${exportName} = _m.${exportName};`).join('\n'); |
| 84 | + } |
| 85 | + } catch (e) { |
| 86 | + if (!FileSystem.isNotExistError(e)) { |
| 87 | + throw e; |
45 | 88 | } |
46 | 89 | } |
| 90 | + |
| 91 | + await FileSystem.writeFileAsync( |
| 92 | + targetPath, |
| 93 | + // Example: |
| 94 | + // ``` |
| 95 | + // const _m = require("../../../lib-shim/index")._rushSdk_loadInternalModule("logic/policy/GitEmailPolicy"); |
| 96 | + // module.exports = _m; |
| 97 | + // exports.GitEmailPolicy = _m.GitEmailPolicy; |
| 98 | + // ``` |
| 99 | + `const _m = require(${shimPathLiteral})._rushSdk_loadInternalModule(${srcImportPathLiteral});\nmodule.exports = _m;${namedExportsAssignment}\n` |
| 100 | + ); |
47 | 101 | } |
48 | 102 | } |
49 | 103 |
|
50 | 104 | // Entry point invoked by "runScript" action from config/heft.json |
51 | | -export async function runAsync(): Promise<void> { |
| 105 | +export async function runAsync(options: IRunScriptOptions): Promise<void> { |
| 106 | + const { |
| 107 | + heftConfiguration: { buildFolderPath }, |
| 108 | + heftTaskSession: { |
| 109 | + logger: { terminal } |
| 110 | + } |
| 111 | + } = options; |
| 112 | + |
52 | 113 | const rushLibFolder: string = Import.resolvePackage({ |
53 | 114 | baseFolderPath: __dirname, |
54 | 115 | packageName: '@microsoft/rush-lib', |
55 | 116 | useNodeJSResolver: true |
56 | 117 | }); |
57 | 118 |
|
58 | | - const stubsTargetPath: string = path.resolve(__dirname, '../lib'); |
59 | | - // eslint-disable-next-line no-console |
60 | | - console.log('generate-stubs: Generating stub files under: ' + stubsTargetPath); |
61 | | - generateLibFilesRecursively({ |
62 | | - parentSourcePath: path.join(rushLibFolder, 'lib'), |
| 119 | + const stubsTargetPath: string = `${buildFolderPath}/lib`; |
| 120 | + terminal.writeLine('generate-stubs: Generating stub files under: ' + stubsTargetPath); |
| 121 | + |
| 122 | + // Ensure the target folder exists |
| 123 | + await FileSystem.ensureFolderAsync(stubsTargetPath); |
| 124 | + |
| 125 | + // Collect and process file tasks in parallel with controlled concurrency |
| 126 | + const tasks: AsyncGenerator<IFileTask> = collectFileTasksAsync({ |
| 127 | + parentSourcePath: `${rushLibFolder}/lib`, |
63 | 128 | parentTargetPath: stubsTargetPath, |
64 | 129 | parentSrcImportPathWithSlash: '', |
65 | | - libShimIndexPath: path.join(__dirname, '../lib-shim/index') |
| 130 | + libShimIndexPath: `${buildFolderPath}/lib-shim/index.js` |
66 | 131 | }); |
67 | | - // eslint-disable-next-line no-console |
68 | | - console.log('generate-stubs: Completed successfully.'); |
| 132 | + await Async.forEachAsync(tasks, processFileTaskAsync, { concurrency: 50 }); |
| 133 | + |
| 134 | + terminal.writeLine('generate-stubs: Completed successfully.'); |
69 | 135 | } |
0 commit comments