|
| 1 | +import path from 'node:path' |
| 2 | +import fsPromises from 'node:fs/promises' |
| 3 | + |
| 4 | +type TypeDirEntry = { |
| 5 | + // Root directory of the module |
| 6 | + moduleDir: string |
| 7 | + // Types directory, if different from moduleDir |
| 8 | + // Eg: '@types/react' |
| 9 | + typesDir?: string |
| 10 | + // Dist directory, if different from moduleDir |
| 11 | + distDir?: string |
| 12 | + // // Entry file, if different from index.d.ts |
| 13 | + // entry?: string |
| 14 | + // Only read these files |
| 15 | + include?: string[] |
| 16 | +} |
| 17 | + |
| 18 | +const typeDirs: TypeDirEntry[] = [ |
| 19 | + { |
| 20 | + moduleDir: 'react', |
| 21 | + typesDir: '@types/react', |
| 22 | + include: ['index.d.ts', 'global.d.ts'], |
| 23 | + }, |
| 24 | + { |
| 25 | + moduleDir: 'react-babylonjs', |
| 26 | + // distDir: 'dist', |
| 27 | + include: ['dist/', 'package.json'], |
| 28 | + // entry: 'react-babylonjs.d.ts', |
| 29 | + }, |
| 30 | + { moduleDir: '@babylonjs/core' }, |
| 31 | + { moduleDir: '@babylonjs/gui' }, |
| 32 | +] |
| 33 | + |
| 34 | +// For script testing |
| 35 | +// const typesMap = await getTypeDeclarationsMap() |
| 36 | +// console.log(Object.keys(typesMap)) |
| 37 | + |
| 38 | +type FilePath = string |
| 39 | +type FileContent = string |
| 40 | +type TypeDeclarationsMap = Record<FilePath, FileContent> |
| 41 | + |
| 42 | +/** |
| 43 | + * Builds a map of d.ts files content: `{ "path/to/index.d.ts": fileContentString }` |
| 44 | + * |
| 45 | + * Type declarations are needed for intellisense to work in MonacoEditor |
| 46 | + */ |
| 47 | +export async function getTypeDeclarationsMap(): Promise<Record<FilePath, FileContent>> { |
| 48 | + const typesMap: TypeDeclarationsMap = {} |
| 49 | + |
| 50 | + for (const entry of typeDirs) { |
| 51 | + const dirTypesMap = await getDirTypeDeclarations(entry) |
| 52 | + |
| 53 | + Object.assign(typesMap, dirTypesMap) |
| 54 | + } |
| 55 | + |
| 56 | + return typesMap |
| 57 | +} |
| 58 | + |
| 59 | +async function getDirTypeDeclarations(params: TypeDirEntry) { |
| 60 | + const filePromises = await getFilePromises(params) |
| 61 | + |
| 62 | + const filePromiseResults = await Promise.allSettled(filePromises) |
| 63 | + |
| 64 | + return filePromiseResults.reduce<TypeDeclarationsMap>((acc, result) => { |
| 65 | + if (result.status === 'fulfilled') { |
| 66 | + const [moduleFilePath, content] = result.value |
| 67 | + |
| 68 | + acc[moduleFilePath] = content |
| 69 | + } else { |
| 70 | + console.error(result.reason) |
| 71 | + } |
| 72 | + |
| 73 | + return acc |
| 74 | + }, {}) |
| 75 | +} |
| 76 | + |
| 77 | +async function getFilePromises(params: TypeDirEntry) { |
| 78 | + const dtsAbsolutePaths = await getDtsAbsolutePaths(params) |
| 79 | + |
| 80 | + return dtsAbsolutePaths.flatMap((absolutePath) => { |
| 81 | + const moduleFilePath = prepareModulePath(absolutePath, params) |
| 82 | + |
| 83 | + if (!moduleFilePath) return [] |
| 84 | + |
| 85 | + return fsPromises.readFile(absolutePath, 'utf8').then((content) => [moduleFilePath, content]) |
| 86 | + }) |
| 87 | +} |
| 88 | + |
| 89 | +function prepareModulePath(absolutePath: string, params: TypeDirEntry) { |
| 90 | + // E.g.: |
| 91 | + // absolutePath = "/react-babylonjs/website/node_modules/@types/react/index.d.ts" |
| 92 | + // moduleFilePath = "@types/react/index.d.ts" |
| 93 | + let moduleFilePath = absolutePath.match(/node_modules.*/)?.[0] |
| 94 | + |
| 95 | + if (!moduleFilePath) return |
| 96 | + |
| 97 | + const { moduleDir, typesDir = moduleDir } = params |
| 98 | + |
| 99 | + if (moduleDir !== typesDir) { |
| 100 | + // E.g.: @types/react -> react |
| 101 | + moduleFilePath = moduleFilePath.replace(typesDir, moduleDir) |
| 102 | + } |
| 103 | + |
| 104 | + // if (entry && moduleFilePath === path.join(moduleDir, entry)) { |
| 105 | + // // E.g.: react-babylonjs/react-babylonjs.d.ts -> react-babylonjs/index.d.ts |
| 106 | + // // Otherwise, intellisense wouldn't figure it out, as it doesn't have access to package.json |
| 107 | + // // Can we maybe copy package.json too? |
| 108 | + // moduleFilePath = moduleFilePath.replace(entry, 'index.d.ts') |
| 109 | + // } |
| 110 | + |
| 111 | + return moduleFilePath |
| 112 | +} |
| 113 | + |
| 114 | +async function getDtsAbsolutePaths(params: TypeDirEntry) { |
| 115 | + try { |
| 116 | + const { moduleDir, typesDir = moduleDir, include = [] } = params |
| 117 | + |
| 118 | + // grab types from the root node_modules to ensure that babylonjs and react-babylonjs types are aligned |
| 119 | + const typesDirPath = path.join(process.cwd(), '../node_modules', typesDir) |
| 120 | + |
| 121 | + const entries = await fsPromises.readdir(typesDirPath, { |
| 122 | + recursive: true, |
| 123 | + withFileTypes: true, |
| 124 | + }) |
| 125 | + |
| 126 | + const includedPaths = include.map((includedPath) => path.join(typesDirPath, includedPath)) |
| 127 | + |
| 128 | + const dtsFiles = entries.flatMap((entry) => { |
| 129 | + if (!entry.isFile() || !(entry.name.endsWith('.d.ts') || entry.name === 'package.json')) |
| 130 | + return [] |
| 131 | + |
| 132 | + const absolutePath = path.join(entry.parentPath, entry.name) |
| 133 | + |
| 134 | + if ( |
| 135 | + includedPaths.length && |
| 136 | + !includedPaths.some((includedPath) => absolutePath.includes(includedPath)) |
| 137 | + ) { |
| 138 | + return [] |
| 139 | + } |
| 140 | + |
| 141 | + return absolutePath |
| 142 | + }) |
| 143 | + |
| 144 | + return dtsFiles |
| 145 | + } catch (error) { |
| 146 | + console.error('Error:', error) |
| 147 | + |
| 148 | + return [] |
| 149 | + } |
| 150 | +} |
0 commit comments