From 545c3561dce2aac462d912ecefbf2d492ed93afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lenon?= Date: Wed, 6 Jul 2022 15:21:29 -0300 Subject: [PATCH] feat(module): add new helper Module --- README.md | 75 ++++++++++++++++ package-lock.json | 6 +- package.json | 4 +- src/Helpers/Exec.js | 16 ---- src/Helpers/Module.js | 188 +++++++++++++++++++++++++++++++++++++++ src/index.d.ts | 31 ++++++- src/index.js | 1 + tests/Unit/ExecTest.js | 12 +-- tests/Unit/ModuleTest.js | 83 +++++++++++++++++ 9 files changed, 381 insertions(+), 35 deletions(-) create mode 100644 src/Helpers/Module.js create mode 100644 tests/Unit/ModuleTest.js diff --git a/README.md b/README.md index 80f49b4..f3f4fad 100644 --- a/README.md +++ b/README.md @@ -481,6 +481,81 @@ console.log(sortedValue) // a, b or c --- +### Module + +> Use Module to resolve modules exports, import modules using hrefs' ensuring compatibility between OS's, creating +> aliases for your modules exports and creating __filename and __dirname properties. + +```ts +import { Module } from '@secjs/utils' + +const module = await Module.get(import('#src/Helpers/Options')) + +console.log(module.name) // Options +``` + +```ts +import { Module } from '@secjs/utils' + +const modules = await Module.getAll([import('#src/Helpers/Number'), import('#src/Helpers/Options')]) + +console.log(modules[0].name) // Number +console.log(modules[1].name) // Options +``` + +```ts +import { Module } from '@secjs/utils' + +const modules = await Module.getAllWithAlias([ + import('#src/Helpers/Number'), + import('#src/Helpers/Options') +], 'App/Helpers') + +console.log(modules[0].module.name) // Number +console.log(modules[0].alias) // 'App/Helpers/Number' + +console.log(modules[1].module.name) // Options +console.log(modules[1].alias) // 'App/Helpers/Options' +``` + +```ts +import { Path, Module } from '@secjs/utils' + +const module = await Module.getFrom(Path.config('app.js')) + +console.log(module.name) // Athenna +console.log(module.description) // Athenna application +console.log(module.environment) // production +``` + +```ts +import { Path, Module } from '@secjs/utils' + +const modules = await Module.getAllFromWithAlias(Path.config(), 'App/Configs') +const appConfigFile = module[0].module +const appConfigAlias = module[0].alias + +console.log(appConfigAlias) // App/Configs/App +console.log(appConfigFile.name) // Athenna +console.log(appConfigFile.description) // Athenna application +console.log(appConfigFile.environment) // production +``` + +```ts +import { Module } from '@secjs/utils' + +const setInGlobalTrue = true +const setInGlobalFalse = false + +const dirname = Module.createDirname(setInGlobalFalse) +const filename = Module.createFilename(setInGlobalTrue) + +console.log(__dirname) // Error! __dirname is not defined in global +console.log(__filename) // '/Users/...' +``` + +--- + ### Route > Use Route to manipulate paths, getParams, getQueryParams, create route matcher RegExp etc. diff --git a/package-lock.json b/package-lock.json index 7aa647a..dba81a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@secjs/utils", - "version": "1.8.9", + "version": "1.9.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@secjs/utils", - "version": "1.8.9", + "version": "1.9.5", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -33,7 +33,7 @@ "@japa/run-failed-tests": "1.0.7", "@japa/runner": "2.0.7", "@japa/spec-reporter": "1.1.12", - "@otris/jsdoc-tsd": "^2.0.11", + "@otris/jsdoc-tsd": "2.0.11", "c8": "7.11.2", "commitizen": "4.2.4", "cross-env": "7.0.3", diff --git a/package.json b/package.json index f3f512f..29174fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@secjs/utils", - "version": "1.9.4", + "version": "1.9.5", "description": "Utils functions and classes for Node.js", "license": "MIT", "author": "João Lenon ", @@ -64,7 +64,7 @@ "@japa/run-failed-tests": "1.0.7", "@japa/runner": "2.0.7", "@japa/spec-reporter": "1.1.12", - "@otris/jsdoc-tsd": "^2.0.11", + "@otris/jsdoc-tsd": "2.0.11", "c8": "7.11.2", "commitizen": "4.2.4", "cross-env": "7.0.3", diff --git a/src/Helpers/Exec.js b/src/Helpers/Exec.js index acb79c9..9bef37a 100644 --- a/src/Helpers/Exec.js +++ b/src/Helpers/Exec.js @@ -146,20 +146,4 @@ export class Exec { return { meta, links, data } } - - /** - * Get the module first export match or default. - * - * @param {any,Promise} module - * @return {Promise} - */ - static async getModule(module) { - module = await module - - if (module.default) { - return module.default - } - - return module[Object.keys(module)[0]] - } } diff --git a/src/Helpers/Module.js b/src/Helpers/Module.js new file mode 100644 index 0000000..28da78f --- /dev/null +++ b/src/Helpers/Module.js @@ -0,0 +1,188 @@ +import { dirname } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +import { Folder } from '#src/index' + +export class Module { + /** + * Get the module first export match or default. + * + * @param {any|Promise} module + * @return {Promise} + */ + static async get(module) { + module = await module + + if (module.default) { + return module.default + } + + return module[Object.keys(module)[0]] + } + + /** + * Get the module first export match or default with alias. + * + * @param {any|Promise} module + * @param {string} subAlias + * @return {Promise<{ alias: string, module: any }>} + */ + static async getWithAlias(module, subAlias) { + module = await Module.get(module) + + if (!subAlias.endsWith('/')) { + subAlias = subAlias.concat('/') + } + + const alias = subAlias.concat(module.name) + + return { alias, module } + } + + /** + * Get all modules first export match or default and return + * as array. + * + * @param {any[]|Promise} modules + * @return {Promise} + */ + static async getAll(modules) { + const promises = modules.map(m => Module.get(m)) + + return Promise.all(promises) + } + + /** + * Get all modules first export match or default with alias and return + * as array. + * + * @param {any[]|Promise} modules + * @param {string} subAlias + * @return {Promise} + */ + static async getAllWithAlias(modules, subAlias) { + const promises = modules.map(m => Module.getWithAlias(m, subAlias)) + + return Promise.all(promises) + } + + /** + * Same as get method, but import the path directly. + * + * @param {string} path + * @return {Promise} + */ + static async getFrom(path) { + const module = await Module.import(path) + + return Module.get(module) + } + + /** + * Same as getWithAlias method, but import the path directly. + * + * @param {string} path + * @param {string} subAlias + * @return {Promise<{ alias: string, module: any }>} + */ + static async getFromWithAlias(path, subAlias) { + const module = await Module.import(path) + + return Module.getWithAlias(module, subAlias) + } + + /** + * Same as getAll method but import everything in the path directly. + * + * @param {string} path + * @return {Promise} + */ + static async getAllFrom(path) { + const files = await Module.getAllJSFilesFrom(path) + + const promises = files.map(file => Module.getFrom(file.path)) + + return Promise.all(promises) + } + + /** + * Same as getAllWithAlias method but import everything in the path directly. + * + * @param {string} path + * @param {string} subAlias + * @return {Promise<{ alias: string, module: any }[]>} + */ + static async getAllFromWithAlias(path, subAlias) { + const files = await Module.getAllJSFilesFrom(path) + + const promises = files.map(f => Module.getFromWithAlias(f.path, subAlias)) + + return Promise.all(promises) + } + + /** + * Verify if folder exists and get all .js files inside. + * + * @param {string} path + * @return {Promise} + */ + static async getAllJSFilesFrom(path) { + if (!(await Folder.exists(path))) { + return [] + } + + if (!(await Folder.isFolder(path))) { + return [] + } + + const folder = await new Folder(path).load() + + // FIXME Why glob pattern *.js is retrieving .d.ts and .js.map files? + return folder + .getFilesByPattern('*/**/*.js', true) + .filter(file => file.extension.endsWith('.js')) + } + + /** + * Import a full path using the path href to ensure compatibility + * between OS's. + * + * @param {string} path + * @return {Promise} + */ + static async import(path) { + return import(pathToFileURL(path).href) + } + + /** + * Create the __dirname property. Set in global if necessary. + * + * @param {boolean} setInGlobal + * @return {string} + */ + static createDirname(setInGlobal = false) { + const __dirname = dirname(Module.createFilename(false)) + + if (setInGlobal) { + global.__dirname = __dirname + } + + return __dirname + } + + /** + * Create the __filename property. Set in global if necessary. + * + * @param {boolean} setInGlobal + * @return {string} + */ + static createFilename(setInGlobal = false) { + const __filename = fileURLToPath(import.meta.url) + + if (setInGlobal) { + global.__filename = __filename + } + + return __filename + } +} diff --git a/src/index.d.ts b/src/index.d.ts index 7851f39..1ab3468 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -150,8 +150,6 @@ export declare class Exec { total: number, pagination: PaginationContract, ): PaginatedResponse - - static getModule(module: any | Promise): Promise } export declare class File { @@ -170,7 +168,7 @@ export declare class File { public base: string public path: string - + public href: string public isCopy: boolean @@ -416,6 +414,33 @@ export declare class Json { static get(object: any, key: string, defaultValue?: any): any | undefined } +export class Module { + static get(module: any | Promise): Promise + + static getWithAlias(module: any | Promise, subAlias: string): Promise<{ alias: string, module: any }> + + static getAll(modules: any[] | Promise): Promise + + static getAllWithAlias(modules: any[] | Promise, subAlias: string): Promise<{ alias: string, module: any }[]> + + static getFrom(path: string): Promise + + static getFromWithAlias(path: string, subAlias: string): Promise<{ alias: string, module: any }> + + static getAllFrom(path: string): Promise + + static getAllFromWithAlias(path: string, subAlias: string): Promise<{ alias: string, module: any }[]> + + static getAllJSFilesFrom(path: string): Promise + + static import(path: string): Promise + + static createDirname(setInGlobal?: boolean): string + + static createFilename(setInGlobal?: boolean): string +} + + export declare class Number { static getHigher(numbers: number[]): number diff --git a/src/index.js b/src/index.js index ea9a20d..e203407 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ export * from './Helpers/File.js' export * from './Helpers/Folder.js' export * from './Helpers/Is.js' export * from './Helpers/Json.js' +export * from './Helpers/Module.js' export * from './Helpers/Number.js' export * from './Helpers/Options.js' export * from './Helpers/Parser.js' diff --git a/tests/Unit/ExecTest.js b/tests/Unit/ExecTest.js index 2806f55..7af0b70 100644 --- a/tests/Unit/ExecTest.js +++ b/tests/Unit/ExecTest.js @@ -8,7 +8,7 @@ */ import { test } from '@japa/runner' -import { Exec, File, Path, Folder } from '#src/index' +import { Exec, File, Folder, Path } from '#src/index' import { NodeCommandException } from '#src/Exceptions/NodeCommandException' test.group('ExecTest', group => { @@ -79,14 +79,4 @@ test.group('ExecTest', group => { last: 'https://my-api.com/products?page=2&limit=10', }) }) - - test('should be able to get the module first export match or default', async ({ assert }) => { - const moduleDefault = await Exec.getModule(import('../Stubs/config/app.js')) - - assert.equal(moduleDefault.name, 'SecJS') - - const moduleFirstExport = await Exec.getModule(import('#src/Helpers/Options')) - - assert.equal(moduleFirstExport.name, 'Options') - }) }) diff --git a/tests/Unit/ModuleTest.js b/tests/Unit/ModuleTest.js new file mode 100644 index 0000000..570d7c1 --- /dev/null +++ b/tests/Unit/ModuleTest.js @@ -0,0 +1,83 @@ +/** + * @secjs/utils + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { Module, Path } from '#src/index' + +test.group('ExecTest', group => { + test('should be able to get the module first export match or default', async ({ assert }) => { + const moduleDefault = await Module.get(import('../Stubs/config/app.js')) + + assert.equal(moduleDefault.name, 'SecJS') + + const moduleFirstExport = await Module.get(import('#src/Helpers/Options')) + + assert.equal(moduleFirstExport.name, 'Options') + }) + + test('should be able to get all modules first export match or default', async ({ assert }) => { + const modules = [import('../Stubs/config/app.js'), import('#src/Helpers/Options')] + + const modulesResolved = await Module.getAll(modules) + + assert.equal(modulesResolved[0].name, 'SecJS') + assert.equal(modulesResolved[1].name, 'Options') + }) + + test('should be able to get all modules first export match or default with alias', async ({ assert }) => { + const modules = [import('#src/Helpers/Is'), import('#src/Helpers/Options')] + + const modulesResolved = await Module.getAllWithAlias(modules, 'App/Helpers/') + + assert.equal(modulesResolved[0].module.name, 'Is') + assert.equal(modulesResolved[0].alias, 'App/Helpers/Is') + + assert.equal(modulesResolved[1].module.name, 'Options') + assert.equal(modulesResolved[1].alias, 'App/Helpers/Options') + }) + + test('should be able to get the module first export match or default from any path', async ({ assert }) => { + const moduleDefault = await Module.getFrom(Path.stubs('config/app.js')) + + assert.equal(moduleDefault.name, 'SecJS') + + const moduleFirstExport = await Module.getFrom(Path.pwd('src/Helpers/Options.js')) + + assert.equal(moduleFirstExport.name, 'Options') + }) + + test('should be able to get all modules first export match or default from any path', async ({ assert }) => { + const modules = await Module.getAllFrom(Path.pwd('src/Helpers')) + + assert.lengthOf(modules, 17) + assert.equal(modules[0].name, 'Clean') + }) + + test('should be able to get all modules first export match or default from any path with alias', async ({ + assert, + }) => { + const modules = await Module.getAllFromWithAlias(Path.pwd('src/Helpers'), 'App/Helpers') + + assert.lengthOf(modules, 17) + assert.equal(modules[0].module.name, 'Clean') + assert.equal(modules[0].alias, 'App/Helpers/Clean') + }) + + test('should be able to create __filename property inside node global', async ({ assert }) => { + Module.createFilename(true) + + assert.isTrue(__filename.includes('Module.js')) + }) + + test('should be able to create __dirname property inside node global', async ({ assert }) => { + Module.createDirname(true) + + assert.isTrue(__dirname.includes('Helpers')) + }) +})