@ayanaware/bento
Version:
Modular runtime framework designed to solve complex tasks
186 lines • 7.31 kB
JavaScript
;
// 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