UNPKG

docorm

Version:

Persistence layer with ORM features for JSON documents

131 lines 5.52 kB
/** * Utilities for dynamically importing JavaScript modules from a directory * * @module lib/import-dir.ts */ import { readdir, stat } from 'fs/promises'; import path from 'path'; const DEFAULT_DIRECTORY_IMPORT_OPTIONS = { recurse: false, extensions: ['js'] }; /** * Extract the extension from a filename. * * Any text following the last period in the filename is considered to be the extension. * * @param filename The filename. * @return The extension, or TODO the empty string if there is no extension. */ function filenameExtension(filename) { return filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1); } /** * Catalogue the JavaScript modules contained in a directory. * * This is typically used with an absolute path, as in the following example: * * <code> * import path, {dirname} from 'path' * import {fileURLToPath} from 'url' * import {importDirectory} from '../lib/import-dir.js' * const __filename = fileURLToPath(import.meta.url) * const __dirname = dirname(__filename) * const availableModules = await catalogueDirectoryImports(path.join(__dirname, '../some/directory'), {recurse: true}) * </code> * * @param directoryPath The path of the directory to catalogue. * @param options Import options. The assertType property must be set when importing JSON files. * @return An object whose property keys are the module names (filenames without extensions) and property values are the * paths of the module files. Paths begin with directoryPath, so they are absolute or relative according to whether * directoryPath is absolute or relative. */ export async function catalogueDirectoryImports(directoryPath, options = DEFAULT_DIRECTORY_IMPORT_OPTIONS) { const { extensions, recurse } = Object.assign({}, DEFAULT_DIRECTORY_IMPORT_OPTIONS, options); const result = {}; const filenames = (await isDirectory(directoryPath)) ? (await readdir(directoryPath)) : []; await Promise.all(filenames.map(async (filename) => { const fullPath = path.join(directoryPath, filename); const submoduleName = path.basename(filename, path.extname(filename)); const stats = await stat(fullPath); if (stats.isDirectory()) { if (recurse) { result[submoduleName] = await catalogueDirectoryImports(fullPath, options); } } else { const extension = filenameExtension(filename); if (!extensions || extensions.includes(extension)) { result[submoduleName] = fullPath; } } })); return result; } /** * Import all JavaScript modules contained in a directory. * * This is typically used with an absolute path, as in the following example: * * <code> * import path, {dirname} from 'path' * import {fileURLToPath} from 'url' * import {importDirectory} from '../lib/import-dir.js' * const __filename = fileURLToPath(import.meta.url) * const __dirname = dirname(__filename) * const importedModules = await importDirectory(path.join(__dirname, '../some/directory'), {recurse: true}) * </code> * * @param directoryPath - The path of the directory to import. * @param options - Import options, including an import assert * @return - An object whose property keys are the module names (filenames without extensions) and property * values are the default exports of each module. If the import is recursive, there is also a key for each * subdirectory, whose value is another object of the same sort. */ export async function importDirectory(directoryPath, options = DEFAULT_DIRECTORY_IMPORT_OPTIONS) { const { extensions, recurse } = Object.assign({}, DEFAULT_DIRECTORY_IMPORT_OPTIONS, options); const result = {}; const filenames = (await isDirectory(directoryPath)) ? (await readdir(directoryPath)) : []; // TODO Order of filenames should not matter, but is being applied here to ensure entity types are loaded // in the same order each time, resulting in consistent calculated concrete schemas. for (const filename of filenames.sort().reverse()) { const fullPath = path.join(directoryPath, filename); const submoduleName = path.basename(filename, path.extname(filename)); const stats = await stat(fullPath); if (stats.isDirectory()) { if (recurse) { result[submoduleName] = await importDirectory(fullPath, options); } } else { const extension = filenameExtension(filename); if (!extensions || extensions.includes(extension)) { // console.log(`Importing file ${fullPath}`) // Using file:// makes this work with Windows paths that begin with drive letters. const importOptions = {}; if (options.assertType) { importOptions.assert = { type: options.assertType }; } const { default: defaultExport } = await import(`file://${fullPath}`, importOptions); result[submoduleName] = defaultExport; } } } return result; } /** * Determine whether a path names a directory in the filesystem. * * @param path - The path to check. * @returns - True if the path is a directory, false otherwise. */ async function isDirectory(path) { try { const stats = await stat(path); return stats.isDirectory(); } catch { return false; } } //# sourceMappingURL=import-dir.js.map