UNPKG

@ayanaware/bento

Version:

Modular runtime framework designed to solve complex tasks

186 lines 7.31 kB
"use strict"; // findEntity is the reason, ill try to tighten this down in the future /* eslint-disable @typescript-eslint/no-unsafe-member-access */ Object.defineProperty(exports, "__esModule", { value: true }); exports.FSEntityLoader = void 0; const fs_1 = require("fs"); const path = require("path"); const url_1 = require("url"); const errors_1 = require("@ayanaware/errors"); const logger_api_1 = require("@ayanaware/logger-api"); const Entity_1 = require("../../entities/interfaces/Entity"); const EntityLoadError_1 = require("../../errors/EntityLoadError"); const EntityLoader_1 = require("./EntityLoader"); const log = logger_api_1.Logger.get(); /** * We have to do this because TS automatically converts * import(file) to Promise.resolve().then(() => require(file)) * which doesn't work with ESM. * * This can be removed once https://github.com/microsoft/TypeScript/issues/43329 is solved. */ // eslint-disable-next-line @typescript-eslint/no-implied-eval const importDynamic = new Function('modulePath', 'return import(modulePath)'); /** * FSEntityLoader is a recursive entity loader for Bento. * Be sure to carefully read function documentation for potential gotchas */ class FSEntityLoader extends EntityLoader_1.EntityLoader { constructor() { super(...arguments); this.name = 'FSEntityLoader'; this.files = new Set(); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types findEntity(item) { let object = null; // import always seems to return an object // default is ESM default or CJS module.exports // handle default.default export let itemDefault = item.default; if (typeof item.default === 'object' && item.default.default) itemDefault = item.default.default; if (this.isEntitylike(itemDefault)) { // default export object = itemDefault; } else { // named export, look for EntityLike // eslint-disable-next-line @typescript-eslint/no-unsafe-argument for (const obj of Object.values(item)) { if (this.isEntitylike(obj)) { if (object != null) { throw new errors_1.ProcessingError('findEntity(): ESModule defines multiple exports and no default'); } object = obj; } } } return object; } async getFileList(directory, recursive = true, accululator = []) { const files = await fs_1.promises.readdir(directory); for (let file of files) { // TODO: Add support for .fselignore/.bentoignore // ignore node_modules & .git if (/node_modules|\.git/i.exec(file)) continue; file = path.resolve(directory, file); const stat = await fs_1.promises.stat(file); if (stat.isDirectory() && recursive) accululator = await this.getFileList(file, recursive, accululator); else if (stat.isFile()) accululator.push(file); } return accululator; } async checkFile(file) { if (!(/\.(js|mjs|cjs|ts)$/i.exec(file))) return false; if (/\.d\.ts$/i.exec(file)) return false; // .e.js and .e.ts are considerend entities if (/\.e\.(js|ts)$/i.exec(file)) return true; // Check file try { const content = await fs_1.promises.readFile(file, { encoding: 'utf8' }); // @fs-entity-ignore if (/@fs-entity-ignore/mi.exec(content)) return false; // @ayanaware/bento and @fs-entity if (/@ayanaware\/bento|@fs-entity/mi.exec(content)) return true; } catch (e) { log.warn(`checkFile(): Failed to read "${file}". ${e}`); } // Check .d.ts file try { const dts = file.replace(/\.(js|mjs|cjs)$/i, '.d.ts'); const stat = await fs_1.promises.stat(dts); if (stat.isFile()) { const content = await fs_1.promises.readFile(dts, { encoding: 'utf8' }); if (/@ayanaware\/bento/mi.exec(content)) return true; } } catch { /* NO-OP */ } return false; } /** * Bulk Instantiate Files and add them to Bento * @param files Path Array * @param type EntityType */ async addFiles(files, type = Entity_1.EntityType.COMPONENT) { for (const file of files) await this.addFile(file, type); } /** * Instantiate File and add it to Bento * @param file Path * @param type EntityType */ async addFile(file, type = Entity_1.EntityType.COMPONENT) { if (Array.isArray(file)) file = path.resolve(...file); if (this.files.has(file)) throw new errors_1.IllegalStateError(`addFile(): File "${file}" has already been added`); let nodeModule; try { nodeModule = await importDynamic((0, url_1.pathToFileURL)(file)); } catch (e) { throw new EntityLoadError_1.EntityLoadError(file, `addFile(): Failed to require "${file}"`).setCause(e); } // Get the first index of the imported object: const entity = this.findEntity(nodeModule); if (!entity) { log.warn(`addFile(): Could not find entity in "${file}"`); return; } await this.addEntity(entity, type); this.files.add(file); } /** * Bulk addDirectory(), please see that function documentation for more details * @param directories Array of Directory Paths * @param type EntityType * @param recursive recursive? */ async addDirectories(directories, type = Entity_1.EntityType.COMPONENT, recursive = true) { for (const directory of directories) await this.addDirectory(directory, type, recursive); } /** * Find Entity Files in a directory and add them to Bento * * A file will be eligiable for loading in the following circumstances: * * - Extension ends with `.e.ts` or `.e.js` * - File contents include `@fs-entity` * - File contents include `@ayanaware/bento` * - Associated types file `.d.ts` contents include `@ayanaware/bento` * * If none of these conditions are met then the file will be skipped. * * **If all else fails, add the comment `// @fs-entity` and it will be loaded** * * **To prevent loading add the comment `// @fs-entity-ignore`** * * @param directory Directory Path * @param type EntityType * @param recursive recursive? */ async addDirectory(directory, type = Entity_1.EntityType.COMPONENT, recursive = true) { if (Array.isArray(directory)) directory = path.resolve(...directory); const files = await this.getFileList(directory, recursive); for (const file of files) { if (!(await this.checkFile(file))) continue; await this.addFile(file, type); } } } exports.FSEntityLoader = FSEntityLoader; //# sourceMappingURL=FSEntityLoader.js.map