import fs from 'node:fs'; import upath from 'upath'; // TODO: move to `test/util.ts` or `test/modules.ts` function relatePath(here: string, there: string): string { const thereParts = upath.normalizeTrim(there).split('/'); const hereParts = upath.normalizeTrim(here).split('/'); let idx = 0; while ( typeof thereParts[idx] === 'string' && typeof hereParts[idx] === 'string' && thereParts[idx] === hereParts[idx] ) { idx += 1; } const result: string[] = []; for (let x = 0; x < hereParts.length - idx; x += 1) { result.push('..'); } for (let y = idx; y < thereParts.length; y += 1) { result.push(thereParts[y]); } return result.join('/'); } export async function loadModules<T>( dirname: string, validate?: (module: T, moduleName: string) => boolean, filter: (moduleName: string) => boolean = () => true, ): Promise<Record<string, T>> { const result: Record<string, T> = {}; const moduleNames: string[] = fs .readdirSync(dirname, { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) .filter((name) => !name.startsWith('__')) .filter(filter) .sort(); for (const moduleName of moduleNames) { const modulePath = upath.join(relatePath(__dirname, dirname), moduleName); const module = await import(modulePath); // istanbul ignore if if (!module || (validate && !validate(module, moduleName))) { throw new Error(`Invalid module: ${modulePath}`); } result[moduleName] = module as T; } return result; }