From b520b24a88b441632a9d2d90e83258d7aa5f3e7b Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 11:30:54 +0200 Subject: [PATCH 01/18] refactor: reduce tree traversal complexity --- index.js | 299 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 163 insertions(+), 136 deletions(-) diff --git a/index.js b/index.js index 4bf9347..6d964e5 100644 --- a/index.js +++ b/index.js @@ -19,80 +19,88 @@ const defaults = { const fastifyAutoload = async function autoload (fastify, options) { const packageType = await getPackageType(options.dir) const opts = { ...defaults, packageType, ...options } - const pluginTree = await findPlugins(opts.dir, opts) - const pluginsMeta = {} - const hooksMeta = {} - - const pluginArray = [].concat.apply([], Object.values(pluginTree).map(o => o.plugins)) - const hookArray = [].concat.apply([], Object.values(pluginTree).map(o => o.hooks)) - - await Promise.all(pluginArray.map(({ file, type, prefix }) => { - return loadPlugin({ file, type, directoryPrefix: prefix, options: opts, log: fastify.log }) - .then((plugin) => { - if (plugin) { - // create route parameters from prefixed folders - if (options.routeParams) { - plugin.options.prefix = plugin.options.prefix - ? replaceRouteParamPattern(plugin.options.prefix) - : plugin.options.prefix - } - pluginsMeta[plugin.name] = plugin - } - }) - .catch((err) => { - throw enrichError(err) - }) - })) + const pluginTree = await findPlugins(opts.dir, { opts }) - function replaceRouteParamPattern (pattern) { - const isRegularRouteParam = pattern.match(routeParamPattern) - const isMixedRouteParam = pattern.match(routeMixedParamPattern) + await loadPlugins() - if (isMixedRouteParam) { - return pattern.replace(routeMixedParamPattern, ':') - } else if (isRegularRouteParam) { - return pattern.replace(routeParamPattern, '/:') - } else { - return pattern + async function loadPlugins () { + for (const key in pluginTree) { + const node = { + ...pluginTree[key], + pluginsMeta: {}, + hooksMeta: {} + } + + await Promise.all(node.plugins.map(({ file, type, prefix }) => { + return loadPlugin({ file, type, directoryPrefix: prefix, options: opts, log: fastify.log }) + .then((plugin) => { + if (plugin) { + // create route parameters from prefixed folders + if (options.routeParams) { + plugin.options.prefix = plugin.options.prefix + ? replaceRouteParamPattern(plugin.options.prefix) + : plugin.options.prefix + } + node.pluginsMeta[plugin.name] = plugin + } + }) + .catch((err) => { + throw enrichError(err) + }) + })) + + await Promise.all(node.hooks.map((h) => { + return loadHook(h, opts) + .then((hookPlugin) => { + node.hooksMeta[h.file] = hookPlugin + }) + .catch((err) => { + throw enrichError(err) + }) + })) + + registerNode(node) } } - await Promise.all(hookArray.map((h) => { - return loadHook(h, opts) - .then((hookPlugin) => { - hooksMeta[h.file] = hookPlugin - }) - .catch((err) => { - throw enrichError(err) - }) - })) - - const metas = Object.values(pluginsMeta) - for (const prefix in pluginTree) { - const hookFiles = pluginTree[prefix].hooks - const pluginFiles = pluginTree[prefix].plugins - if (hookFiles.length === 0) { - registerAllPlugins(fastify, pluginFiles) + function registerNode (node) { + if (node.hooks.length === 0) { + registerAllPlugins(fastify, node) } else { const composedPlugin = async function (app) { // find hook functions for this prefix - for (const hookFile of hookFiles) { - const hookPlugin = hooksMeta[hookFile.file] + for (const hookFile of node.hooks) { + const hookPlugin = node.hooksMeta[hookFile.file] // encapsulate hooks at plugin level app.register(hookPlugin) } - registerAllPlugins(app, pluginFiles) + + registerAllPlugins(app, node) } fastify.register(composedPlugin) } } - function registerAllPlugins (app, pluginFiles) { - for (const pluginFile of pluginFiles) { + function replaceRouteParamPattern (pattern) { + const isRegularRouteParam = pattern.match(routeParamPattern) + const isMixedRouteParam = pattern.match(routeMixedParamPattern) + + if (isMixedRouteParam) { + return pattern.replace(routeMixedParamPattern, ':') + } else if (isRegularRouteParam) { + return pattern.replace(routeParamPattern, '/:') + } else { + return pattern + } + } + + function registerAllPlugins (app, node) { + const metas = Object.values(node.pluginsMeta) + for (const pluginFile of node.plugins) { // find plugins for this prefix, based on filename stored in registerPlugins() const plugin = metas.find((i) => i.filename === pluginFile.file) // register plugins at fastify level - if (plugin) registerPlugin(app, plugin, pluginsMeta) + if (plugin) registerPlugin(app, plugin, node.pluginsMeta) } } } @@ -119,56 +127,22 @@ async function getPackageType (cwd) { } } -const typescriptPattern = /\.(ts|mts|cts)$/iu -const modulePattern = /\.(mjs|mts)$/iu -const commonjsPattern = /\.(cjs|cts)$/iu -function getScriptType (fname, packageType) { - return { - language: typescriptPattern.test(fname) ? 'typescript' : 'javascript', - type: (modulePattern.test(fname) ? 'module' : commonjsPattern.test(fname) ? 'commonjs' : packageType) || 'commonjs' - } -} - -// eslint-disable-next-line default-param-last -async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth = 0, hooks = []) { - const { indexPattern, ignorePattern, ignoreFilter, matchFilter, scriptPattern, dirNameRoutePrefix, maxDepth, autoHooksPattern } = options +async function findPlugins (dir, options) { + const { opts, hookedAccumulator = {}, prefix, depth = 0, hooks = [] } = options const list = await readdir(dir, { withFileTypes: true }) - let currentHooks = [] // check to see if hooks or plugins have been added to this prefix, initialize if not - if (!hookedAccumulator[prefix || '/']) hookedAccumulator[prefix || '/'] = { hooks: [], plugins: [] } - - if (options.autoHooks) { - // Hooks were passed in, create new array specific to this plugin item - if (hooks && hooks.length > 0) { - for (const hook of hooks) { - currentHooks.push(hook) - } - } - - // Contains autohooks file? - const autoHooks = list.find((dirent) => autoHooksPattern.test(dirent.name)) - if (autoHooks) { - const autoHooksFile = join(dir, autoHooks.name) - const { type: autoHooksType } = getScriptType(autoHooksFile, options.packageType) - - // Overwrite current hooks? - if (options.overwriteHooks && currentHooks.length > 0) { - currentHooks = [] - } - - // Add hook to current chain - currentHooks.push({ file: autoHooksFile, type: autoHooksType }) - } - - hookedAccumulator[prefix || '/'].hooks = currentHooks + if (!hookedAccumulator[prefix || '/']) { + hookedAccumulator[prefix || '/'] = { hooks: [], plugins: [] } } + const currentHooks = getCurrentHooks() + // Contains index file? - const indexDirent = list.find((dirent) => indexPattern.test(dirent.name)) + const indexDirent = list.find((dirent) => opts.indexPattern.test(dirent.name)) if (indexDirent) { const file = join(dir, indexDirent.name) - const { language, type } = getScriptType(file, options.packageType) + const { language, type } = getScriptType(file, opts.packageType) if (language === 'typescript' && !runtime.supportTypeScript) { throw new Error(`@fastify/autoload cannot import hooks plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) } @@ -190,28 +164,28 @@ async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth // Otherwise treat each script file as a plugin const directoryPromises = [] for (const dirent of list) { - if (ignorePattern && dirent.name.match(ignorePattern)) { + if (opts.ignorePattern && dirent.name.match(opts.ignorePattern)) { continue } - const atMaxDepth = Number.isFinite(maxDepth) && maxDepth <= depth + const atMaxDepth = Number.isFinite(opts.maxDepth) && opts.maxDepth <= depth const file = join(dir, dirent.name) if (dirent.isDirectory() && !atMaxDepth) { let prefixBreadCrumb = (prefix ? `${prefix}/` : '/') - if (dirNameRoutePrefix === true) { + if (opts.dirNameRoutePrefix === true) { prefixBreadCrumb += dirent.name - } else if (typeof dirNameRoutePrefix === 'function') { - const prefixReplacer = dirNameRoutePrefix(dir, dirent.name) + } else if (typeof opts.dirNameRoutePrefix === 'function') { + const prefixReplacer = opts.dirNameRoutePrefix(dir, dirent.name) if (prefixReplacer) { prefixBreadCrumb += prefixReplacer } } // Pass hooks forward to next level - if (options.autoHooks && options.cascadeHooks) { - directoryPromises.push(findPlugins(file, options, hookedAccumulator, prefixBreadCrumb, depth + 1, currentHooks)) + if (opts.autoHooks && opts.cascadeHooks) { + directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1, hooks: currentHooks })) } else { - directoryPromises.push(findPlugins(file, options, hookedAccumulator, prefixBreadCrumb, depth + 1)) + directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1 })) } continue @@ -220,31 +194,64 @@ async function findPlugins (dir, options, hookedAccumulator = {}, prefix, depth continue } - if (dirent.isFile() && scriptPattern.test(dirent.name)) { - const { language, type } = getScriptType(file, options.packageType) + if (dirent.isFile() && opts.scriptPattern.test(dirent.name)) { + const { language, type } = getScriptType(file, opts.packageType) if (language === 'typescript' && !runtime.supportTypeScript) { throw new Error(`@fastify/autoload cannot import plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) } // Don't place hook in plugin queue - if (!autoHooksPattern.test(dirent.name)) { + if (!opts.autoHooksPattern.test(dirent.name)) { accumulatePlugin({ file, type }) } } } + await Promise.all(directoryPromises) return hookedAccumulator + function getCurrentHooks () { + let currentHooks = [] + + if (opts.autoHooks) { + // Hooks were passed in, create new array specific to this plugin item + if (hooks && hooks.length > 0) { + for (const hook of hooks) { + currentHooks.push(hook) + } + } + + // Contains autohooks file? + const autoHooks = list.find((dirent) => opts.autoHooksPattern.test(dirent.name)) + if (autoHooks) { + const autoHooksFile = join(dir, autoHooks.name) + const { type: autoHooksType } = getScriptType(autoHooksFile, opts.packageType) + + // Overwrite current hooks? + if (opts.overwriteHooks && currentHooks.length > 0) { + currentHooks = [] + } + + // Add hook to current chain + currentHooks.push({ file: autoHooksFile, type: autoHooksType }) + } + + hookedAccumulator[prefix || '/'].hooks = currentHooks + } + + return currentHooks + } + function accumulatePlugin ({ file, type }) { // Replace backward slash to forward slash for consistent behavior between windows and posix. - const filePath = '/' + relative(options.dir, file).replace(/\\/gu, '/') + const filePath = '/' + relative(opts.dir, file).replace(/\\/gu, '/') - if (matchFilter && !filterPath(filePath, matchFilter)) { + if (opts.matchFilter && !filterPath(filePath, opts.matchFilter)) { return } - if (ignoreFilter && filterPath(filePath, ignoreFilter)) { + if (opts.ignoreFilter && filterPath(filePath, opts.ignoreFilter)) { return } @@ -317,6 +324,26 @@ async function loadPlugin ({ file, type, directoryPrefix, options, log }) { } } +async function loadHook (hook, options) { + let hookContent + if (options.forceESM || hook.type === 'module' || runtime.forceESM) { + hookContent = await import(pathToFileURL(hook.file).href) + } else { + hookContent = require(hook.file) + } + + hookContent = hookContent.default || hookContent + + if ( + Object.prototype.toString.call(hookContent) === '[object AsyncFunction]' || + Object.prototype.toString.call(hookContent) === '[object Function]' + ) { + hookContent[Symbol.for('skip-override')] = true + } + + return hookContent +} + function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { const { plugin, name, options, dependencies = [] } = meta @@ -342,18 +369,6 @@ function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { meta.registered = true } -function filterPath (path, filter) { - if (typeof filter === 'string') { - return path.includes(filter) - } - - if (filter instanceof RegExp) { - return filter.test(path) - } - - return filter(path) -} - /** * Used to determine if the contents of a required autoloaded file matches * the shape of a Fastify route configuration object. @@ -407,24 +422,36 @@ function wrapRoutes (content) { return content } -async function loadHook (hook, options) { - let hookContent - if (options.forceESM || hook.type === 'module' || runtime.forceESM) { - hookContent = await import(pathToFileURL(hook.file).href) - } else { - hookContent = require(hook.file) +function filterPath (path, filter) { + if (typeof filter === 'string') { + return path.includes(filter) } - hookContent = hookContent.default || hookContent + if (filter instanceof RegExp) { + return filter.test(path) + } - if ( - Object.prototype.toString.call(hookContent) === '[object AsyncFunction]' || - Object.prototype.toString.call(hookContent) === '[object Function]' - ) { - hookContent[Symbol.for('skip-override')] = true + return filter(path) +} + +const typescriptPattern = /\.(ts|mts|cts)$/iu +const modulePattern = /\.(mjs|mts)$/iu +const commonjsPattern = /\.(cjs|cts)$/iu +function getScriptType (fname, packageType) { + return { + language: typescriptPattern.test(fname) ? 'typescript' : 'javascript', + type: determineModuleType(fname, packageType) } +} - return hookContent +function determineModuleType (fname, defaultType) { + if (modulePattern.test(fname)) { + return 'module' + } else if (commonjsPattern.test(fname)) { + return 'commonjs' + } + + return defaultType || 'commonjs' } function enrichError (err) { From 72f70e5c1b4cf3176d7ed96ff825769bc9f47f6c Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 13:20:39 +0200 Subject: [PATCH 02/18] refactor: -reduce cognitive complexity - extract find-plugins in its own module --- find-plugins.js | 184 ++++++++++++++++++++++++++++++++++++++++ index.js | 218 ++++++++---------------------------------------- 2 files changed, 218 insertions(+), 184 deletions(-) create mode 100644 find-plugins.js diff --git a/find-plugins.js b/find-plugins.js new file mode 100644 index 0000000..d1bd399 --- /dev/null +++ b/find-plugins.js @@ -0,0 +1,184 @@ +const { readdir } = require('node:fs/promises') +const { relative, join } = require('path') +const runtime = require('./runtime') + +async function findPlugins (dir, options) { + const { opts, hookedAccumulator = {}, prefix, depth = 0, hooks = [] } = options + const list = await readdir(dir, { withFileTypes: true }) + + // check to see if hooks or plugins have been added to this prefix, initialize if not + if (!hookedAccumulator[prefix || '/']) { + hookedAccumulator[prefix || '/'] = { hooks: [], plugins: [] } + } + + const currentHooks = findCurrentHooks({ dir, list, hooks, opts, hookedAccumulator, prefix }) + + const indexDirent = processIndexDirentIfExists({ list, opts, dir, hookedAccumulator, prefix }) + + // Contains package.json but no index.js file? + const packageDirent = list.find((dirent) => dirent.name === 'package.json') + if (packageDirent && !indexDirent) { + throw new Error(`@fastify/autoload cannot import plugin at '${dir}'. To fix this error rename the main entry file to 'index.js' (or .cjs, .mjs, .ts).`) + } + + // Otherwise treat each script file as a plugin + await processList({ list, opts, indexDirent, prefix, dir, depth, currentHooks, hookedAccumulator }) + + return hookedAccumulator +} + +function findCurrentHooks ({ dir, list, hooks, opts, hookedAccumulator, prefix }) { + let currentHooks = [] + + if (opts.autoHooks) { + // Hooks were passed in, create new array specific to this plugin item + if (hooks && hooks.length > 0) { + for (const hook of hooks) { + currentHooks.push(hook) + } + } + + // Contains autohooks file? + const autoHooks = list.find((dirent) => opts.autoHooksPattern.test(dirent.name)) + if (autoHooks) { + const autoHooksFile = join(dir, autoHooks.name) + const { type: autoHooksType } = getScriptType(autoHooksFile, opts.packageType) + + // Overwrite current hooks? + if (opts.overwriteHooks && currentHooks.length > 0) { + currentHooks = [] + } + + // Add hook to current chain + currentHooks.push({ file: autoHooksFile, type: autoHooksType }) + } + + hookedAccumulator[prefix || '/'].hooks = currentHooks + } + + return currentHooks +} + +function processIndexDirentIfExists ({ opts, list, dir, hookedAccumulator, prefix }) { + // Contains index file? + const indexDirent = list.find((dirent) => opts.indexPattern.test(dirent.name)) + if (!indexDirent) return null + + const file = join(dir, indexDirent.name) + const { language, type } = getScriptType(file, opts.packageType) + handleTypeScriptSupport(file, language, true) + + accumulatePlugin({ file, type, opts, hookedAccumulator, prefix }) + const hasDirectory = list.find((dirent) => dirent.isDirectory()) + + if (!hasDirectory) { + return hookedAccumulator + } + + return indexDirent +} + +async function processList ({ list, opts, indexDirent, prefix, dir, depth, currentHooks, hookedAccumulator }) { + const directoryPromises = [] + for (const dirent of list) { + if (opts.ignorePattern && RegExp(opts.ignorePattern).exec(dirent.name)) { + continue + } + + const atMaxDepth = Number.isFinite(opts.maxDepth) && opts.maxDepth <= depth + const file = join(dir, dirent.name) + if (dirent.isDirectory() && !atMaxDepth) { + processDir({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) + } else if (indexDirent) { + // An index.js file is present in the directory so we ignore the others modules (but not the subdirectories) + } else if (dirent.isFile() && opts.scriptPattern.test(dirent.name)) { + processFile({ file, opts, dirent, hookedAccumulator, prefix }) + } + } + + await Promise.all(directoryPromises) +} + +function processDir ({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) { + let prefixBreadCrumb = (prefix ? `${prefix}/` : '/') + if (opts.dirNameRoutePrefix === true) { + prefixBreadCrumb += dirent.name + } else if (typeof opts.dirNameRoutePrefix === 'function') { + const prefixReplacer = opts.dirNameRoutePrefix(dir, dirent.name) + if (prefixReplacer) { + prefixBreadCrumb += prefixReplacer + } + } + + // Pass hooks forward to next level + if (opts.autoHooks && opts.cascadeHooks) { + directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1, hooks: currentHooks })) + } else { + directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1 })) + } +} + +function processFile ({ file, opts, dirent, hookedAccumulator, prefix }) { + const { language, type } = getScriptType(file, opts.packageType) + handleTypeScriptSupport(file, language) + + // Don't place hook in plugin queue + if (!opts.autoHooksPattern.test(dirent.name)) { + accumulatePlugin({ file, type, opts, hookedAccumulator, prefix }) + } +} + +function accumulatePlugin ({ file, type, opts, hookedAccumulator, prefix }) { + // Replace backward slash to forward slash for consistent behavior between windows and posix. + const filePath = '/' + relative(opts.dir, file).replace(/\\/gu, '/') + + if (opts.matchFilter && !filterPath(filePath, opts.matchFilter)) { + return + } + + if (opts.ignoreFilter && filterPath(filePath, opts.ignoreFilter)) { + return + } + + hookedAccumulator[prefix || '/'].plugins.push({ file, type, prefix }) +} + +function handleTypeScriptSupport (file, language, isHook = false) { + if (language === 'typescript' && !runtime.supportTypeScript) { + throw new Error(`@fastify/autoload cannot import ${isHook ? 'hooks ' : ''}plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) + } +} + +function filterPath (path, filter) { + if (typeof filter === 'string') { + return path.includes(filter) + } + + if (filter instanceof RegExp) { + return filter.test(path) + } + + return filter(path) +} + +const typescriptPattern = /\.(ts|mts|cts)$/iu +const modulePattern = /\.(mjs|mts)$/iu +const commonjsPattern = /\.(cjs|cts)$/iu +function getScriptType (fname, packageType) { + return { + language: typescriptPattern.test(fname) ? 'typescript' : 'javascript', + type: determineModuleType(fname, packageType) + } +} + +function determineModuleType (fname, defaultType) { + if (modulePattern.test(fname)) { + return 'module' + } else if (commonjsPattern.test(fname)) { + return 'commonjs' + } + + return defaultType || 'commonjs' +} + +module.exports = findPlugins diff --git a/index.js b/index.js index 6d964e5..239d621 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,10 @@ 'use strict' -const { promises: { readdir, readFile } } = require('node:fs') -const { join, relative, sep } = require('node:path') +const { readFile } = require('node:fs/promises') +const { join, sep } = require('node:path') const { pathToFileURL } = require('node:url') const runtime = require('./runtime') +const findPlugins = require('./find-plugins') const routeParamPattern = /\/_/gu const routeMixedParamPattern = /__/gu @@ -127,138 +128,6 @@ async function getPackageType (cwd) { } } -async function findPlugins (dir, options) { - const { opts, hookedAccumulator = {}, prefix, depth = 0, hooks = [] } = options - const list = await readdir(dir, { withFileTypes: true }) - - // check to see if hooks or plugins have been added to this prefix, initialize if not - if (!hookedAccumulator[prefix || '/']) { - hookedAccumulator[prefix || '/'] = { hooks: [], plugins: [] } - } - - const currentHooks = getCurrentHooks() - - // Contains index file? - const indexDirent = list.find((dirent) => opts.indexPattern.test(dirent.name)) - if (indexDirent) { - const file = join(dir, indexDirent.name) - const { language, type } = getScriptType(file, opts.packageType) - if (language === 'typescript' && !runtime.supportTypeScript) { - throw new Error(`@fastify/autoload cannot import hooks plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) - } - - accumulatePlugin({ file, type }) - const hasDirectory = list.find((dirent) => dirent.isDirectory()) - - if (!hasDirectory) { - return hookedAccumulator - } - } - - // Contains package.json but no index.js file? - const packageDirent = list.find((dirent) => dirent.name === 'package.json') - if (packageDirent && !indexDirent) { - throw new Error(`@fastify/autoload cannot import plugin at '${dir}'. To fix this error rename the main entry file to 'index.js' (or .cjs, .mjs, .ts).`) - } - - // Otherwise treat each script file as a plugin - const directoryPromises = [] - for (const dirent of list) { - if (opts.ignorePattern && dirent.name.match(opts.ignorePattern)) { - continue - } - - const atMaxDepth = Number.isFinite(opts.maxDepth) && opts.maxDepth <= depth - const file = join(dir, dirent.name) - if (dirent.isDirectory() && !atMaxDepth) { - let prefixBreadCrumb = (prefix ? `${prefix}/` : '/') - if (opts.dirNameRoutePrefix === true) { - prefixBreadCrumb += dirent.name - } else if (typeof opts.dirNameRoutePrefix === 'function') { - const prefixReplacer = opts.dirNameRoutePrefix(dir, dirent.name) - if (prefixReplacer) { - prefixBreadCrumb += prefixReplacer - } - } - - // Pass hooks forward to next level - if (opts.autoHooks && opts.cascadeHooks) { - directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1, hooks: currentHooks })) - } else { - directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1 })) - } - - continue - } else if (indexDirent) { - // An index.js file is present in the directory so we ignore the others modules (but not the subdirectories) - continue - } - - if (dirent.isFile() && opts.scriptPattern.test(dirent.name)) { - const { language, type } = getScriptType(file, opts.packageType) - if (language === 'typescript' && !runtime.supportTypeScript) { - throw new Error(`@fastify/autoload cannot import plugin at '${file}'. To fix this error compile TypeScript to JavaScript or use 'ts-node' to run your app.`) - } - - // Don't place hook in plugin queue - if (!opts.autoHooksPattern.test(dirent.name)) { - accumulatePlugin({ file, type }) - } - } - } - - await Promise.all(directoryPromises) - - return hookedAccumulator - - function getCurrentHooks () { - let currentHooks = [] - - if (opts.autoHooks) { - // Hooks were passed in, create new array specific to this plugin item - if (hooks && hooks.length > 0) { - for (const hook of hooks) { - currentHooks.push(hook) - } - } - - // Contains autohooks file? - const autoHooks = list.find((dirent) => opts.autoHooksPattern.test(dirent.name)) - if (autoHooks) { - const autoHooksFile = join(dir, autoHooks.name) - const { type: autoHooksType } = getScriptType(autoHooksFile, opts.packageType) - - // Overwrite current hooks? - if (opts.overwriteHooks && currentHooks.length > 0) { - currentHooks = [] - } - - // Add hook to current chain - currentHooks.push({ file: autoHooksFile, type: autoHooksType }) - } - - hookedAccumulator[prefix || '/'].hooks = currentHooks - } - - return currentHooks - } - - function accumulatePlugin ({ file, type }) { - // Replace backward slash to forward slash for consistent behavior between windows and posix. - const filePath = '/' + relative(opts.dir, file).replace(/\\/gu, '/') - - if (opts.matchFilter && !filterPath(filePath, opts.matchFilter)) { - return - } - - if (opts.ignoreFilter && filterPath(filePath, opts.ignoreFilter)) { - return - } - - hookedAccumulator[prefix || '/'].plugins.push({ file, type, prefix }) - } -} - async function loadPlugin ({ file, type, directoryPrefix, options, log }) { const { options: overrideConfig, forceESM, encapsulate } = options let content @@ -277,7 +146,7 @@ async function loadPlugin ({ file, type, directoryPrefix, options, log }) { } const plugin = wrapRoutes(content.default || content) - const pluginConfig = (content.default && content.default.autoConfig) || content.autoConfig || {} + const pluginConfig = (content.default?.autoConfig) || content.autoConfig || {} let pluginOptions if (typeof pluginConfig === 'function') { pluginOptions = function (fastify) { @@ -305,15 +174,33 @@ async function loadPlugin ({ file, type, directoryPrefix, options, log }) { plugin.autoConfig = undefined } - pluginOptions.prefix = (pluginOptions.prefix && pluginOptions.prefix.endsWith('/')) ? pluginOptions.prefix.slice(0, -1) : pluginOptions.prefix - const prefixOverride = plugin.prefixOverride !== undefined ? plugin.prefixOverride : content.prefixOverride !== undefined ? content.prefixOverride : undefined - const prefix = (plugin.autoPrefix !== undefined ? plugin.autoPrefix : content.autoPrefix !== undefined ? content.autoPrefix : undefined) || directoryPrefix - if (prefixOverride !== undefined) { - pluginOptions.prefix = prefixOverride - } else if (prefix) { - pluginOptions.prefix = (pluginOptions.prefix || '') + prefix.replace(/\/+/gu, '/') + function handlePrefix () { + pluginOptions.prefix = pluginOptions.prefix?.endsWith('/') + ? pluginOptions.prefix.slice(0, -1) + : pluginOptions.prefix + + const prefixOverride = plugin.prefixOverride !== undefined + ? plugin.prefixOverride + : content.prefixOverride + + let prefix + if (plugin.autoPrefix !== undefined) { + prefix = plugin.autoPrefix + } else if (content.autoPrefix !== undefined) { + prefix = content.autoPrefix + } else { + prefix = directoryPrefix + } + + if (prefixOverride !== undefined) { + pluginOptions.prefix = prefixOverride + } else if (prefix) { + pluginOptions.prefix = (pluginOptions.prefix || '') + prefix.replace(/\/+/gu, '/') + } } + handlePrefix() + return { plugin, filename: file, @@ -334,10 +221,8 @@ async function loadHook (hook, options) { hookContent = hookContent.default || hookContent - if ( - Object.prototype.toString.call(hookContent) === '[object AsyncFunction]' || - Object.prototype.toString.call(hookContent) === '[object Function]' - ) { + const type = Object.prototype.toString.call(hookContent) + if (type === '[object AsyncFunction]' || type === '[object Function]') { hookContent[Symbol.for('skip-override')] = true } @@ -379,12 +264,9 @@ function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { * False otherwise. */ function isRouteObject (input) { - if (input && + return !!(input && Object.prototype.toString.call(input) === '[object Object]' && - Object.prototype.hasOwnProperty.call(input, 'method')) { - return true - } - return false + Object.hasOwn(input, 'method')) } const pluginOrModulePattern = /\[object (?:AsyncFunction|Function|Module)\]/u @@ -404,7 +286,7 @@ function isPluginOrModule (input) { const inputType = Object.prototype.toString.call(input) if (pluginOrModulePattern.test(inputType) === true) { result = true - } else if (Object.prototype.hasOwnProperty.call(input, 'default')) { + } else if (Object.hasOwn(input, 'default')) { result = isPluginOrModule(input.default) } else { result = isRouteObject(input) @@ -422,38 +304,6 @@ function wrapRoutes (content) { return content } -function filterPath (path, filter) { - if (typeof filter === 'string') { - return path.includes(filter) - } - - if (filter instanceof RegExp) { - return filter.test(path) - } - - return filter(path) -} - -const typescriptPattern = /\.(ts|mts|cts)$/iu -const modulePattern = /\.(mjs|mts)$/iu -const commonjsPattern = /\.(cjs|cts)$/iu -function getScriptType (fname, packageType) { - return { - language: typescriptPattern.test(fname) ? 'typescript' : 'javascript', - type: determineModuleType(fname, packageType) - } -} - -function determineModuleType (fname, defaultType) { - if (modulePattern.test(fname)) { - return 'module' - } else if (commonjsPattern.test(fname)) { - return 'commonjs' - } - - return defaultType || 'commonjs' -} - function enrichError (err) { // Hack SyntaxError message so that we provide // the line number to the user, otherwise they From 1330db476fd07a47721a81f6f5113b0d3499d786 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 14:06:54 +0200 Subject: [PATCH 03/18] refactor: reduce cognitive complexity of plugin loading algorithm --- index.js | 277 +++++++++++++++++++++++++++---------------------------- 1 file changed, 138 insertions(+), 139 deletions(-) diff --git a/index.js b/index.js index 239d621..51df7ac 100644 --- a/index.js +++ b/index.js @@ -2,12 +2,9 @@ const { readFile } = require('node:fs/promises') const { join, sep } = require('node:path') -const { pathToFileURL } = require('node:url') -const runtime = require('./runtime') const findPlugins = require('./find-plugins') - -const routeParamPattern = /\/_/gu -const routeMixedParamPattern = /__/gu +const runtime = require('./runtime') +const { pathToFileURL } = require('node:url') const defaults = { scriptPattern: /(?:(?:^.?|\.[^d]|[^.]d|[^.][^d])\.ts|\.js|\.cjs|\.mjs|\.cts|\.mts)$/iu, @@ -22,109 +19,61 @@ const fastifyAutoload = async function autoload (fastify, options) { const opts = { ...defaults, packageType, ...options } const pluginTree = await findPlugins(opts.dir, { opts }) - await loadPlugins() - - async function loadPlugins () { - for (const key in pluginTree) { - const node = { - ...pluginTree[key], - pluginsMeta: {}, - hooksMeta: {} - } - - await Promise.all(node.plugins.map(({ file, type, prefix }) => { - return loadPlugin({ file, type, directoryPrefix: prefix, options: opts, log: fastify.log }) - .then((plugin) => { - if (plugin) { - // create route parameters from prefixed folders - if (options.routeParams) { - plugin.options.prefix = plugin.options.prefix - ? replaceRouteParamPattern(plugin.options.prefix) - : plugin.options.prefix - } - node.pluginsMeta[plugin.name] = plugin - } - }) - .catch((err) => { - throw enrichError(err) - }) - })) - - await Promise.all(node.hooks.map((h) => { - return loadHook(h, opts) - .then((hookPlugin) => { - node.hooksMeta[h.file] = hookPlugin - }) - .catch((err) => { - throw enrichError(err) - }) - })) - - registerNode(node) - } - } - - function registerNode (node) { - if (node.hooks.length === 0) { - registerAllPlugins(fastify, node) - } else { - const composedPlugin = async function (app) { - // find hook functions for this prefix - for (const hookFile of node.hooks) { - const hookPlugin = node.hooksMeta[hookFile.file] - // encapsulate hooks at plugin level - app.register(hookPlugin) - } - - registerAllPlugins(app, node) - } - fastify.register(composedPlugin) - } - } - - function replaceRouteParamPattern (pattern) { - const isRegularRouteParam = pattern.match(routeParamPattern) - const isMixedRouteParam = pattern.match(routeMixedParamPattern) + await loadPlugins({ pluginTree, options, opts, fastify }) +} - if (isMixedRouteParam) { - return pattern.replace(routeMixedParamPattern, ':') - } else if (isRegularRouteParam) { - return pattern.replace(routeParamPattern, '/:') - } else { - return pattern +async function loadPlugins ({ pluginTree, options, opts, fastify }) { + for (const key in pluginTree) { + const node = { + ...pluginTree[key], + pluginsMeta: {}, + hooksMeta: {} } - } - function registerAllPlugins (app, node) { - const metas = Object.values(node.pluginsMeta) - for (const pluginFile of node.plugins) { - // find plugins for this prefix, based on filename stored in registerPlugins() - const plugin = metas.find((i) => i.filename === pluginFile.file) - // register plugins at fastify level - if (plugin) registerPlugin(app, plugin, node.pluginsMeta) - } + await Promise.all(node.plugins.map(({ file, type, prefix }) => { + return loadPlugin({ file, type, directoryPrefix: prefix, options: opts, log: fastify.log }) + .then((plugin) => { + if (plugin) { + // create route parameters from prefixed folders + if (options.routeParams) { + plugin.options.prefix = plugin.options.prefix + ? replaceRouteParamPattern(plugin.options.prefix) + : plugin.options.prefix + } + node.pluginsMeta[plugin.name] = plugin + } + }) + .catch((err) => { + throw enrichError(err) + }) + })) + + await Promise.all(node.hooks.map((h) => { + return loadHook(h, opts) + .then((hookPlugin) => { + node.hooksMeta[h.file] = hookPlugin + }) + .catch((err) => { + throw enrichError(err) + }) + })) + + registerNode(node, fastify) } } -async function getPackageType (cwd) { - const directories = cwd.split(sep) - - /* c8 ignore start */ - // required for paths that begin with the sep, such as linux root - // ignore because OS specific evaluation - directories[0] = directories[0] !== '' ? directories[0] : sep - /* c8 ignore stop */ - while (directories.length > 0) { - const filePath = join(...directories, 'package.json') - - const fileContents = await readFile(filePath, 'utf-8') - .catch(() => null) - - if (fileContents) { - return JSON.parse(fileContents).type - } - - directories.pop() +const routeParamPattern = /\/_/gu +const routeMixedParamPattern = /__/gu +function replaceRouteParamPattern (pattern) { + const isRegularRouteParam = pattern.match(routeParamPattern) + const isMixedRouteParam = pattern.match(routeMixedParamPattern) + + if (isMixedRouteParam) { + return pattern.replace(routeMixedParamPattern, ':') + } else if (isRegularRouteParam) { + return pattern.replace(routeParamPattern, '/:') + } else { + return pattern } } @@ -174,32 +123,7 @@ async function loadPlugin ({ file, type, directoryPrefix, options, log }) { plugin.autoConfig = undefined } - function handlePrefix () { - pluginOptions.prefix = pluginOptions.prefix?.endsWith('/') - ? pluginOptions.prefix.slice(0, -1) - : pluginOptions.prefix - - const prefixOverride = plugin.prefixOverride !== undefined - ? plugin.prefixOverride - : content.prefixOverride - - let prefix - if (plugin.autoPrefix !== undefined) { - prefix = plugin.autoPrefix - } else if (content.autoPrefix !== undefined) { - prefix = content.autoPrefix - } else { - prefix = directoryPrefix - } - - if (prefixOverride !== undefined) { - pluginOptions.prefix = prefixOverride - } else if (prefix) { - pluginOptions.prefix = (pluginOptions.prefix || '') + prefix.replace(/\/+/gu, '/') - } - } - - handlePrefix() + handlePrefix({ plugin, pluginOptions, content, directoryPrefix }) return { plugin, @@ -229,6 +153,59 @@ async function loadHook (hook, options) { return hookContent } +function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { + pluginOptions.prefix = pluginOptions.prefix?.endsWith('/') + ? pluginOptions.prefix.slice(0, -1) + : pluginOptions.prefix + + const prefixOverride = plugin.prefixOverride !== undefined + ? plugin.prefixOverride + : content.prefixOverride + + let prefix + if (plugin.autoPrefix !== undefined) { + prefix = plugin.autoPrefix + } else if (content.autoPrefix !== undefined) { + prefix = content.autoPrefix + } else { + prefix = directoryPrefix + } + + if (prefixOverride !== undefined) { + pluginOptions.prefix = prefixOverride + } else if (prefix) { + pluginOptions.prefix = (pluginOptions.prefix || '') + prefix.replace(/\/+/gu, '/') + } +} + +function registerNode (node, fastify) { + if (node.hooks.length === 0) { + registerAllPlugins(fastify, node) + } else { + const composedPlugin = async function (app) { + // find hook functions for this prefix + for (const hookFile of node.hooks) { + const hookPlugin = node.hooksMeta[hookFile.file] + // encapsulate hooks at plugin level + app.register(hookPlugin) + } + + registerAllPlugins(app, node) + } + fastify.register(composedPlugin) + } +} + +function registerAllPlugins (app, node) { + const metas = Object.values(node.pluginsMeta) + for (const pluginFile of node.plugins) { + // find plugins for this prefix, based on filename stored in registerPlugins() + const plugin = metas.find((i) => i.filename === pluginFile.file) + // register plugins at fastify level + if (plugin) registerPlugin(app, plugin, node.pluginsMeta) + } +} + function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { const { plugin, name, options, dependencies = [] } = meta @@ -265,21 +242,21 @@ function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { */ function isRouteObject (input) { return !!(input && - Object.prototype.toString.call(input) === '[object Object]' && - Object.hasOwn(input, 'method')) + Object.prototype.toString.call(input) === '[object Object]' && + Object.hasOwn(input, 'method')) } const pluginOrModulePattern = /\[object (?:AsyncFunction|Function|Module)\]/u /** - * Used to determine if the contents of a required autoloaded file is a valid - * plugin or route configuration object. In the case of a route configuration - * object, it will later be wrapped into a plugin. - * - * @param {*} input The data to check. - * - * @returns {boolean} True if the object can be used by the autoload system by - * eventually passing it into `avvio`. False otherwise. - */ + * Used to determine if the contents of a required autoloaded file is a valid + * plugin or route configuration object. In the case of a route configuration + * object, it will later be wrapped into a plugin. + * + * @param {*} input The data to check. + * + * @returns {boolean} True if the object can be used by the autoload system by + * eventually passing it into `avvio`. False otherwise. + */ function isPluginOrModule (input) { let result = false @@ -315,6 +292,28 @@ function enrichError (err) { return err } +async function getPackageType (cwd) { + const directories = cwd.split(sep) + + /* c8 ignore start */ + // required for paths that begin with the sep, such as linux root + // ignore because OS specific evaluation + directories[0] = directories[0] !== '' ? directories[0] : sep + /* c8 ignore stop */ + while (directories.length > 0) { + const filePath = join(...directories, 'package.json') + + const fileContents = await readFile(filePath, 'utf-8') + .catch(() => null) + + if (fileContents) { + return JSON.parse(fileContents).type + } + + directories.pop() + } +} + // do not create a new context, do not encapsulate // same as fastify-plugin fastifyAutoload[Symbol.for('skip-override')] = true From 6afce6ce1abb94b2020e887746d45ffa4d37d756 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 14:25:38 +0200 Subject: [PATCH 04/18] fix: annotations idnentation --- index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 51df7ac..6640877 100644 --- a/index.js +++ b/index.js @@ -248,15 +248,15 @@ function isRouteObject (input) { const pluginOrModulePattern = /\[object (?:AsyncFunction|Function|Module)\]/u /** - * Used to determine if the contents of a required autoloaded file is a valid - * plugin or route configuration object. In the case of a route configuration - * object, it will later be wrapped into a plugin. - * - * @param {*} input The data to check. - * - * @returns {boolean} True if the object can be used by the autoload system by - * eventually passing it into `avvio`. False otherwise. - */ + * Used to determine if the contents of a required autoloaded file is a valid + * plugin or route configuration object. In the case of a route configuration + * object, it will later be wrapped into a plugin. + * + * @param {*} input The data to check. + * + * @returns {boolean} True if the object can be used by the autoload system by + * eventually passing it into `avvio`. False otherwise. + */ function isPluginOrModule (input) { let result = false From ef1456cf73088bb896cd096a65360c591316d1d6 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 14:34:16 +0200 Subject: [PATCH 05/18] refactor, place function in relevant order --- index.js | 80 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 6640877..63244a5 100644 --- a/index.js +++ b/index.js @@ -62,21 +62,6 @@ async function loadPlugins ({ pluginTree, options, opts, fastify }) { } } -const routeParamPattern = /\/_/gu -const routeMixedParamPattern = /__/gu -function replaceRouteParamPattern (pattern) { - const isRegularRouteParam = pattern.match(routeParamPattern) - const isMixedRouteParam = pattern.match(routeMixedParamPattern) - - if (isMixedRouteParam) { - return pattern.replace(routeMixedParamPattern, ':') - } else if (isRegularRouteParam) { - return pattern.replace(routeParamPattern, '/:') - } else { - return pattern - } -} - async function loadPlugin ({ file, type, directoryPrefix, options, log }) { const { options: overrideConfig, forceESM, encapsulate } = options let content @@ -153,31 +138,6 @@ async function loadHook (hook, options) { return hookContent } -function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { - pluginOptions.prefix = pluginOptions.prefix?.endsWith('/') - ? pluginOptions.prefix.slice(0, -1) - : pluginOptions.prefix - - const prefixOverride = plugin.prefixOverride !== undefined - ? plugin.prefixOverride - : content.prefixOverride - - let prefix - if (plugin.autoPrefix !== undefined) { - prefix = plugin.autoPrefix - } else if (content.autoPrefix !== undefined) { - prefix = content.autoPrefix - } else { - prefix = directoryPrefix - } - - if (prefixOverride !== undefined) { - pluginOptions.prefix = prefixOverride - } else if (prefix) { - pluginOptions.prefix = (pluginOptions.prefix || '') + prefix.replace(/\/+/gu, '/') - } -} - function registerNode (node, fastify) { if (node.hooks.length === 0) { registerAllPlugins(fastify, node) @@ -231,6 +191,46 @@ function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { meta.registered = true } +function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { + pluginOptions.prefix = pluginOptions.prefix?.endsWith('/') + ? pluginOptions.prefix.slice(0, -1) + : pluginOptions.prefix + + const prefixOverride = plugin.prefixOverride !== undefined + ? plugin.prefixOverride + : content.prefixOverride + + let prefix + if (plugin.autoPrefix !== undefined) { + prefix = plugin.autoPrefix + } else if (content.autoPrefix !== undefined) { + prefix = content.autoPrefix + } else { + prefix = directoryPrefix + } + + if (prefixOverride !== undefined) { + pluginOptions.prefix = prefixOverride + } else if (prefix) { + pluginOptions.prefix = (pluginOptions.prefix || '') + prefix.replace(/\/+/gu, '/') + } +} + +const routeParamPattern = /\/_/gu +const routeMixedParamPattern = /__/gu +function replaceRouteParamPattern (pattern) { + const isRegularRouteParam = pattern.match(routeParamPattern) + const isMixedRouteParam = pattern.match(routeMixedParamPattern) + + if (isMixedRouteParam) { + return pattern.replace(routeMixedParamPattern, ':') + } else if (isRegularRouteParam) { + return pattern.replace(routeParamPattern, '/:') + } else { + return pattern + } +} + /** * Used to determine if the contents of a required autoloaded file matches * the shape of a Fastify route configuration object. From e3281cd6f99ffcfa6fc0d668126e4c6f763a9fca Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:13:22 +0200 Subject: [PATCH 06/18] refactor: handlePrefix --- index.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 63244a5..eaf31c4 100644 --- a/index.js +++ b/index.js @@ -192,13 +192,9 @@ function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { } function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { - pluginOptions.prefix = pluginOptions.prefix?.endsWith('/') - ? pluginOptions.prefix.slice(0, -1) - : pluginOptions.prefix - - const prefixOverride = plugin.prefixOverride !== undefined - ? plugin.prefixOverride - : content.prefixOverride + if (pluginOptions.prefix?.endsWith('/')) { + pluginOptions.prefix = pluginOptions.prefix.slice(0, -1) + } let prefix if (plugin.autoPrefix !== undefined) { @@ -209,6 +205,7 @@ function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { prefix = directoryPrefix } + const prefixOverride = plugin.prefixOverride ?? content.prefixOverride if (prefixOverride !== undefined) { pluginOptions.prefix = prefixOverride } else if (prefix) { From 1ba498ba8bd00bd5d7ffc83b4970bed426df0b43 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:14:18 +0200 Subject: [PATCH 07/18] refactor: replaceRouteParamPattern --- index.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index eaf31c4..e1ff5a1 100644 --- a/index.js +++ b/index.js @@ -216,16 +216,13 @@ function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { const routeParamPattern = /\/_/gu const routeMixedParamPattern = /__/gu function replaceRouteParamPattern (pattern) { - const isRegularRouteParam = pattern.match(routeParamPattern) - const isMixedRouteParam = pattern.match(routeMixedParamPattern) - - if (isMixedRouteParam) { + if (pattern.match(routeMixedParamPattern)) { return pattern.replace(routeMixedParamPattern, ':') - } else if (isRegularRouteParam) { + } else if (pattern.match(routeParamPattern)) { return pattern.replace(routeParamPattern, '/:') - } else { - return pattern } + + return pattern } /** From 10a4bc398673b0fce8e086fae658ecb72883b3a0 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:17:24 +0200 Subject: [PATCH 08/18] refactor: loadPlugins --- index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index e1ff5a1..e99c68e 100644 --- a/index.js +++ b/index.js @@ -35,10 +35,8 @@ async function loadPlugins ({ pluginTree, options, opts, fastify }) { .then((plugin) => { if (plugin) { // create route parameters from prefixed folders - if (options.routeParams) { - plugin.options.prefix = plugin.options.prefix - ? replaceRouteParamPattern(plugin.options.prefix) - : plugin.options.prefix + if (options.routeParams && plugin.options.prefix) { + plugin.options.prefix = replaceRouteParamPattern(plugin.options.prefix) } node.pluginsMeta[plugin.name] = plugin } @@ -272,6 +270,7 @@ function wrapRoutes (content) { fastify.route(content) } } + return content } From 5c393cac19468fd2accd34a05455590fabbb775d Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:31:21 +0200 Subject: [PATCH 09/18] refactor: create function to load plugin options --- index.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index e99c68e..e491235 100644 --- a/index.js +++ b/index.js @@ -78,18 +78,7 @@ async function loadPlugin ({ file, type, directoryPrefix, options, log }) { } const plugin = wrapRoutes(content.default || content) - const pluginConfig = (content.default?.autoConfig) || content.autoConfig || {} - let pluginOptions - if (typeof pluginConfig === 'function') { - pluginOptions = function (fastify) { - return { ...pluginConfig(fastify), ...overrideConfig } - } - - pluginOptions.prefix = overrideConfig.prefix ?? pluginConfig.prefix - } else { - pluginOptions = { ...pluginConfig, ...overrideConfig } - } - + const pluginOptions = loadPluginOptions(content, overrideConfig) const pluginMeta = plugin[Symbol.for('plugin-meta')] || {} if (!encapsulate) { @@ -189,6 +178,18 @@ function registerPlugin (fastify, meta, allPlugins, parentPlugins = {}) { meta.registered = true } +function loadPluginOptions (content, overrideConfig) { + const pluginConfig = (content.default?.autoConfig) || content.autoConfig || {} + if (typeof pluginConfig === 'function') { + const pluginOptions = (fastify) => ({ ...pluginConfig(fastify), ...overrideConfig }) + pluginOptions.prefix = overrideConfig.prefix ?? pluginConfig.prefix + + return pluginOptions + } + + return { ...pluginConfig, ...overrideConfig } +} + function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { if (pluginOptions.prefix?.endsWith('/')) { pluginOptions.prefix = pluginOptions.prefix.slice(0, -1) From 213f1df97448cd481e7f7a29656979775b7188d2 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:35:26 +0200 Subject: [PATCH 10/18] refactor: rename handlePrefix to handlePrefixConfig --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index e491235..f8b8fd3 100644 --- a/index.js +++ b/index.js @@ -95,7 +95,7 @@ async function loadPlugin ({ file, type, directoryPrefix, options, log }) { plugin.autoConfig = undefined } - handlePrefix({ plugin, pluginOptions, content, directoryPrefix }) + handlePrefixConfig({ plugin, pluginOptions, content, directoryPrefix }) return { plugin, @@ -190,7 +190,7 @@ function loadPluginOptions (content, overrideConfig) { return { ...pluginConfig, ...overrideConfig } } -function handlePrefix ({ plugin, pluginOptions, content, directoryPrefix }) { +function handlePrefixConfig ({ plugin, pluginOptions, content, directoryPrefix }) { if (pluginOptions.prefix?.endsWith('/')) { pluginOptions.prefix = pluginOptions.prefix.slice(0, -1) } From dbd61ae70f9c38c3d6924649366d2663fd24fcf2 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:39:39 +0200 Subject: [PATCH 11/18] refactor: findCurrentHooks --- find-plugins.js | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/find-plugins.js b/find-plugins.js index d1bd399..1f3ded9 100644 --- a/find-plugins.js +++ b/find-plugins.js @@ -28,34 +28,31 @@ async function findPlugins (dir, options) { } function findCurrentHooks ({ dir, list, hooks, opts, hookedAccumulator, prefix }) { - let currentHooks = [] - - if (opts.autoHooks) { - // Hooks were passed in, create new array specific to this plugin item - if (hooks && hooks.length > 0) { - for (const hook of hooks) { - currentHooks.push(hook) - } - } + if (!opts.autoHooks) return [] - // Contains autohooks file? - const autoHooks = list.find((dirent) => opts.autoHooksPattern.test(dirent.name)) - if (autoHooks) { - const autoHooksFile = join(dir, autoHooks.name) - const { type: autoHooksType } = getScriptType(autoHooksFile, opts.packageType) + let currentHooks = [] + // Hooks were passed in, create new array specific to this plugin item + for (const hook of hooks) { + currentHooks.push(hook) + } - // Overwrite current hooks? - if (opts.overwriteHooks && currentHooks.length > 0) { - currentHooks = [] - } + // Contains autohooks file? + const autoHooks = list.find((dirent) => opts.autoHooksPattern.test(dirent.name)) + if (autoHooks) { + const autoHooksFile = join(dir, autoHooks.name) + const { type: autoHooksType } = getScriptType(autoHooksFile, opts.packageType) - // Add hook to current chain - currentHooks.push({ file: autoHooksFile, type: autoHooksType }) + // Overwrite current hooks? + if (opts.overwriteHooks && currentHooks.length > 0) { + currentHooks = [] } - hookedAccumulator[prefix || '/'].hooks = currentHooks + // Add hook to current chain + currentHooks.push({ file: autoHooksFile, type: autoHooksType }) } + hookedAccumulator[prefix || '/'].hooks = currentHooks + return currentHooks } From eb856552934427f76933fefa57b795a533aa3a39 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:41:50 +0200 Subject: [PATCH 12/18] refactor: rename processDir to processDirectory --- find-plugins.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/find-plugins.js b/find-plugins.js index 1f3ded9..d7ea520 100644 --- a/find-plugins.js +++ b/find-plugins.js @@ -64,10 +64,9 @@ function processIndexDirentIfExists ({ opts, list, dir, hookedAccumulator, prefi const file = join(dir, indexDirent.name) const { language, type } = getScriptType(file, opts.packageType) handleTypeScriptSupport(file, language, true) - accumulatePlugin({ file, type, opts, hookedAccumulator, prefix }) - const hasDirectory = list.find((dirent) => dirent.isDirectory()) + const hasDirectory = list.find((dirent) => dirent.isDirectory()) if (!hasDirectory) { return hookedAccumulator } @@ -85,7 +84,7 @@ async function processList ({ list, opts, indexDirent, prefix, dir, depth, curre const atMaxDepth = Number.isFinite(opts.maxDepth) && opts.maxDepth <= depth const file = join(dir, dirent.name) if (dirent.isDirectory() && !atMaxDepth) { - processDir({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) + processDirectory({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) } else if (indexDirent) { // An index.js file is present in the directory so we ignore the others modules (but not the subdirectories) } else if (dirent.isFile() && opts.scriptPattern.test(dirent.name)) { @@ -96,7 +95,7 @@ async function processList ({ list, opts, indexDirent, prefix, dir, depth, curre await Promise.all(directoryPromises) } -function processDir ({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) { +function processDirectory ({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) { let prefixBreadCrumb = (prefix ? `${prefix}/` : '/') if (opts.dirNameRoutePrefix === true) { prefixBreadCrumb += dirent.name From a404ab318b6570c2c84bc8f50239dd0f272b5276 Mon Sep 17 00:00:00 2001 From: jean Date: Fri, 26 Jul 2024 16:44:49 +0200 Subject: [PATCH 13/18] refactor: nit --- find-plugins.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/find-plugins.js b/find-plugins.js index d7ea520..59ca8c9 100644 --- a/find-plugins.js +++ b/find-plugins.js @@ -127,7 +127,6 @@ function processFile ({ file, opts, dirent, hookedAccumulator, prefix }) { function accumulatePlugin ({ file, type, opts, hookedAccumulator, prefix }) { // Replace backward slash to forward slash for consistent behavior between windows and posix. const filePath = '/' + relative(opts.dir, file).replace(/\\/gu, '/') - if (opts.matchFilter && !filterPath(filePath, opts.matchFilter)) { return } @@ -158,8 +157,6 @@ function filterPath (path, filter) { } const typescriptPattern = /\.(ts|mts|cts)$/iu -const modulePattern = /\.(mjs|mts)$/iu -const commonjsPattern = /\.(cjs|cts)$/iu function getScriptType (fname, packageType) { return { language: typescriptPattern.test(fname) ? 'typescript' : 'javascript', @@ -167,10 +164,14 @@ function getScriptType (fname, packageType) { } } +const modulePattern = /\.(mjs|mts)$/iu +const commonjsPattern = /\.(cjs|cts)$/iu function determineModuleType (fname, defaultType) { if (modulePattern.test(fname)) { return 'module' - } else if (commonjsPattern.test(fname)) { + } + + if (commonjsPattern.test(fname)) { return 'commonjs' } From 40e220a887024aee45f6abd3e8818796916b0f1d Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 29 Jul 2024 13:34:27 +0200 Subject: [PATCH 14/18] chore: use strict --- find-plugins.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/find-plugins.js b/find-plugins.js index 59ca8c9..9992726 100644 --- a/find-plugins.js +++ b/find-plugins.js @@ -1,3 +1,5 @@ +'use strict' + const { readdir } = require('node:fs/promises') const { relative, join } = require('path') const runtime = require('./runtime') From bea84a1682fbf151920fd8175153148d6a88d607 Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 29 Jul 2024 13:36:01 +0200 Subject: [PATCH 15/18] chore: move find-plugin & runtime in lib --- index.js | 4 ++-- find-plugins.js => lib/find-plugins.js | 0 runtime.js => lib/runtime.js | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename find-plugins.js => lib/find-plugins.js (100%) rename runtime.js => lib/runtime.js (100%) diff --git a/index.js b/index.js index f8b8fd3..3285f5a 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,8 @@ const { readFile } = require('node:fs/promises') const { join, sep } = require('node:path') -const findPlugins = require('./find-plugins') -const runtime = require('./runtime') +const findPlugins = require('./lib/find-plugins') +const runtime = require('./lib/runtime') const { pathToFileURL } = require('node:url') const defaults = { diff --git a/find-plugins.js b/lib/find-plugins.js similarity index 100% rename from find-plugins.js rename to lib/find-plugins.js diff --git a/runtime.js b/lib/runtime.js similarity index 100% rename from runtime.js rename to lib/runtime.js From 6c32532f9831de4aa05f02dc9f63bb85ff14da14 Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 29 Jul 2024 13:42:22 +0200 Subject: [PATCH 16/18] -nrefactor: remove directoryPromises --- lib/find-plugins.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/find-plugins.js b/lib/find-plugins.js index 9992726..6fdce22 100644 --- a/lib/find-plugins.js +++ b/lib/find-plugins.js @@ -77,7 +77,6 @@ function processIndexDirentIfExists ({ opts, list, dir, hookedAccumulator, prefi } async function processList ({ list, opts, indexDirent, prefix, dir, depth, currentHooks, hookedAccumulator }) { - const directoryPromises = [] for (const dirent of list) { if (opts.ignorePattern && RegExp(opts.ignorePattern).exec(dirent.name)) { continue @@ -86,18 +85,16 @@ async function processList ({ list, opts, indexDirent, prefix, dir, depth, curre const atMaxDepth = Number.isFinite(opts.maxDepth) && opts.maxDepth <= depth const file = join(dir, dirent.name) if (dirent.isDirectory() && !atMaxDepth) { - processDirectory({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) + await processDirectory({ prefix, opts, dirent, dir, file, hookedAccumulator, depth, currentHooks }) } else if (indexDirent) { // An index.js file is present in the directory so we ignore the others modules (but not the subdirectories) } else if (dirent.isFile() && opts.scriptPattern.test(dirent.name)) { processFile({ file, opts, dirent, hookedAccumulator, prefix }) } } - - await Promise.all(directoryPromises) } -function processDirectory ({ prefix, opts, dirent, dir, file, directoryPromises, hookedAccumulator, depth, currentHooks }) { +async function processDirectory ({ prefix, opts, dirent, dir, file, hookedAccumulator, depth, currentHooks }) { let prefixBreadCrumb = (prefix ? `${prefix}/` : '/') if (opts.dirNameRoutePrefix === true) { prefixBreadCrumb += dirent.name @@ -109,11 +106,8 @@ function processDirectory ({ prefix, opts, dirent, dir, file, directoryPromises, } // Pass hooks forward to next level - if (opts.autoHooks && opts.cascadeHooks) { - directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1, hooks: currentHooks })) - } else { - directoryPromises.push(findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1 })) - } + const hooks = opts.autoHooks && opts.cascadeHooks ? currentHooks : [] + await findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1, hooks }) } function processFile ({ file, opts, dirent, hookedAccumulator, prefix }) { From f980571945b87a26b5936f214844a9ac293d32ba Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 29 Jul 2024 14:49:32 +0200 Subject: [PATCH 17/18] refactor: add a buildTree step and improve misleading var identifiers --- lib/find-plugins.js | 107 ++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/lib/find-plugins.js b/lib/find-plugins.js index 6fdce22..2ef922e 100644 --- a/lib/find-plugins.js +++ b/lib/find-plugins.js @@ -5,122 +5,131 @@ const { relative, join } = require('path') const runtime = require('./runtime') async function findPlugins (dir, options) { - const { opts, hookedAccumulator = {}, prefix, depth = 0, hooks = [] } = options - const list = await readdir(dir, { withFileTypes: true }) + const { opts, prefix, depth = 0, hooks = [] } = options + const pluginTree = { + [prefix || '/']: { hooks: [], plugins: [] } + } + + await buildTree(pluginTree, dir, { prefix, opts, depth, hooks }) + + return pluginTree +} + +async function buildTree (pluginTree, dir, { prefix, opts, depth, hooks }) { // check to see if hooks or plugins have been added to this prefix, initialize if not - if (!hookedAccumulator[prefix || '/']) { - hookedAccumulator[prefix || '/'] = { hooks: [], plugins: [] } + if (!pluginTree[prefix]) { + pluginTree[prefix] = { hooks: [], plugins: [] } } - const currentHooks = findCurrentHooks({ dir, list, hooks, opts, hookedAccumulator, prefix }) + const dirEntries = await readdir(dir, { withFileTypes: true }) + + const currentDirHooks = findCurrentDirHooks(pluginTree, { dir, dirEntries, hooks, opts, prefix }) - const indexDirent = processIndexDirentIfExists({ list, opts, dir, hookedAccumulator, prefix }) + const indexDirEntry = processIndexDirEntryIfExists(pluginTree, { dirEntries, opts, dir, prefix }) // Contains package.json but no index.js file? - const packageDirent = list.find((dirent) => dirent.name === 'package.json') - if (packageDirent && !indexDirent) { + const packageDirEntry = dirEntries.find((dirEntry) => dirEntry.name === 'package.json') + if (packageDirEntry && !indexDirEntry) { throw new Error(`@fastify/autoload cannot import plugin at '${dir}'. To fix this error rename the main entry file to 'index.js' (or .cjs, .mjs, .ts).`) } // Otherwise treat each script file as a plugin - await processList({ list, opts, indexDirent, prefix, dir, depth, currentHooks, hookedAccumulator }) - - return hookedAccumulator + await processDirContents(pluginTree, { dirEntries, opts, indexDirEntry, prefix, dir, depth, currentDirHooks }) } -function findCurrentHooks ({ dir, list, hooks, opts, hookedAccumulator, prefix }) { +function findCurrentDirHooks (pluginTree, { dir, dirEntries, hooks, opts, prefix }) { if (!opts.autoHooks) return [] - let currentHooks = [] + let currentDirHooks = [] // Hooks were passed in, create new array specific to this plugin item for (const hook of hooks) { - currentHooks.push(hook) + currentDirHooks.push(hook) } // Contains autohooks file? - const autoHooks = list.find((dirent) => opts.autoHooksPattern.test(dirent.name)) + const autoHooks = dirEntries.find((dirEntry) => opts.autoHooksPattern.test(dirEntry.name)) if (autoHooks) { - const autoHooksFile = join(dir, autoHooks.name) - const { type: autoHooksType } = getScriptType(autoHooksFile, opts.packageType) + const file = join(dir, autoHooks.name) + const { type } = getScriptType(file, opts.packageType) // Overwrite current hooks? - if (opts.overwriteHooks && currentHooks.length > 0) { - currentHooks = [] + if (opts.overwriteHooks && currentDirHooks.length > 0) { + currentDirHooks = [] } // Add hook to current chain - currentHooks.push({ file: autoHooksFile, type: autoHooksType }) + currentDirHooks.push({ file, type }) } - hookedAccumulator[prefix || '/'].hooks = currentHooks + pluginTree[prefix || '/'].hooks = currentDirHooks - return currentHooks + return currentDirHooks } -function processIndexDirentIfExists ({ opts, list, dir, hookedAccumulator, prefix }) { +function processIndexDirEntryIfExists (pluginTree, { opts, dirEntries, dir, prefix }) { // Contains index file? - const indexDirent = list.find((dirent) => opts.indexPattern.test(dirent.name)) - if (!indexDirent) return null + const indexDirEntry = dirEntries.find((dirEntry) => opts.indexPattern.test(dirEntry.name)) + if (!indexDirEntry) return null - const file = join(dir, indexDirent.name) + const file = join(dir, indexDirEntry.name) const { language, type } = getScriptType(file, opts.packageType) handleTypeScriptSupport(file, language, true) - accumulatePlugin({ file, type, opts, hookedAccumulator, prefix }) + accumulatePlugin({ file, type, opts, pluginTree, prefix }) - const hasDirectory = list.find((dirent) => dirent.isDirectory()) + const hasDirectory = dirEntries.find((dirEntry) => dirEntry.isDirectory()) if (!hasDirectory) { - return hookedAccumulator + return pluginTree } - return indexDirent + return indexDirEntry } -async function processList ({ list, opts, indexDirent, prefix, dir, depth, currentHooks, hookedAccumulator }) { - for (const dirent of list) { - if (opts.ignorePattern && RegExp(opts.ignorePattern).exec(dirent.name)) { +async function processDirContents (pluginTree, { dirEntries, opts, indexDirEntry, prefix, dir, depth, currentDirHooks }) { + for (const dirEntry of dirEntries) { + if (opts.ignorePattern && RegExp(opts.ignorePattern).exec(dirEntry.name)) { continue } const atMaxDepth = Number.isFinite(opts.maxDepth) && opts.maxDepth <= depth - const file = join(dir, dirent.name) - if (dirent.isDirectory() && !atMaxDepth) { - await processDirectory({ prefix, opts, dirent, dir, file, hookedAccumulator, depth, currentHooks }) - } else if (indexDirent) { + const file = join(dir, dirEntry.name) + if (dirEntry.isDirectory() && !atMaxDepth) { + await processDirectory(pluginTree, { prefix, opts, dirEntry, dir, file, depth, currentDirHooks }) + } else if (indexDirEntry) { // An index.js file is present in the directory so we ignore the others modules (but not the subdirectories) - } else if (dirent.isFile() && opts.scriptPattern.test(dirent.name)) { - processFile({ file, opts, dirent, hookedAccumulator, prefix }) + } else if (dirEntry.isFile() && opts.scriptPattern.test(dirEntry.name)) { + processFile(pluginTree, { file, opts, dirEntry, pluginTree, prefix }) } } } -async function processDirectory ({ prefix, opts, dirent, dir, file, hookedAccumulator, depth, currentHooks }) { +async function processDirectory (pluginTree, { prefix, opts, dirEntry, dir, file, depth, currentDirHooks }) { let prefixBreadCrumb = (prefix ? `${prefix}/` : '/') if (opts.dirNameRoutePrefix === true) { - prefixBreadCrumb += dirent.name + prefixBreadCrumb += dirEntry.name } else if (typeof opts.dirNameRoutePrefix === 'function') { - const prefixReplacer = opts.dirNameRoutePrefix(dir, dirent.name) + const prefixReplacer = opts.dirNameRoutePrefix(dir, dirEntry.name) if (prefixReplacer) { prefixBreadCrumb += prefixReplacer } } // Pass hooks forward to next level - const hooks = opts.autoHooks && opts.cascadeHooks ? currentHooks : [] - await findPlugins(file, { opts, hookedAccumulator, prefix: prefixBreadCrumb, depth: depth + 1, hooks }) + const hooks = opts.autoHooks && opts.cascadeHooks ? currentDirHooks : [] + await buildTree(pluginTree, file, { opts, prefix: prefixBreadCrumb, depth: depth + 1, hooks }) } -function processFile ({ file, opts, dirent, hookedAccumulator, prefix }) { +function processFile (pluginTree, { file, opts, dirEntry, prefix }) { const { language, type } = getScriptType(file, opts.packageType) handleTypeScriptSupport(file, language) // Don't place hook in plugin queue - if (!opts.autoHooksPattern.test(dirent.name)) { - accumulatePlugin({ file, type, opts, hookedAccumulator, prefix }) + if (!opts.autoHooksPattern.test(dirEntry.name)) { + accumulatePlugin({ file, type, opts, pluginTree, prefix }) } } -function accumulatePlugin ({ file, type, opts, hookedAccumulator, prefix }) { +function accumulatePlugin ({ file, type, opts, pluginTree, prefix }) { // Replace backward slash to forward slash for consistent behavior between windows and posix. const filePath = '/' + relative(opts.dir, file).replace(/\\/gu, '/') if (opts.matchFilter && !filterPath(filePath, opts.matchFilter)) { @@ -131,7 +140,7 @@ function accumulatePlugin ({ file, type, opts, hookedAccumulator, prefix }) { return } - hookedAccumulator[prefix || '/'].plugins.push({ file, type, prefix }) + pluginTree[prefix || '/'].plugins.push({ file, type, prefix }) } function handleTypeScriptSupport (file, language, isHook = false) { From 082dd2ea846134e1a3fea48f2fe3ff084e453a95 Mon Sep 17 00:00:00 2001 From: jean Date: Mon, 29 Jul 2024 15:26:57 +0200 Subject: [PATCH 18/18] refactor: if has no directory, stop the tree construction --- lib/find-plugins.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/find-plugins.js b/lib/find-plugins.js index 2ef922e..631d870 100644 --- a/lib/find-plugins.js +++ b/lib/find-plugins.js @@ -5,13 +5,13 @@ const { relative, join } = require('path') const runtime = require('./runtime') async function findPlugins (dir, options) { - const { opts, prefix, depth = 0, hooks = [] } = options + const { opts, prefix } = options const pluginTree = { [prefix || '/']: { hooks: [], plugins: [] } } - await buildTree(pluginTree, dir, { prefix, opts, depth, hooks }) + await buildTree(pluginTree, dir, { prefix, opts, depth: 0, hooks: [] }) return pluginTree } @@ -26,7 +26,10 @@ async function buildTree (pluginTree, dir, { prefix, opts, depth, hooks }) { const currentDirHooks = findCurrentDirHooks(pluginTree, { dir, dirEntries, hooks, opts, prefix }) - const indexDirEntry = processIndexDirEntryIfExists(pluginTree, { dirEntries, opts, dir, prefix }) + const { indexDirEntry, hasNoDirectory } = processIndexDirEntryIfExists(pluginTree, { dirEntries, opts, dir, prefix }) + if (hasNoDirectory) { + return + } // Contains package.json but no index.js file? const packageDirEntry = dirEntries.find((dirEntry) => dirEntry.name === 'package.json') @@ -70,19 +73,16 @@ function findCurrentDirHooks (pluginTree, { dir, dirEntries, hooks, opts, prefix function processIndexDirEntryIfExists (pluginTree, { opts, dirEntries, dir, prefix }) { // Contains index file? const indexDirEntry = dirEntries.find((dirEntry) => opts.indexPattern.test(dirEntry.name)) - if (!indexDirEntry) return null + if (!indexDirEntry) return { indexDirEntry } const file = join(dir, indexDirEntry.name) const { language, type } = getScriptType(file, opts.packageType) handleTypeScriptSupport(file, language, true) accumulatePlugin({ file, type, opts, pluginTree, prefix }) - const hasDirectory = dirEntries.find((dirEntry) => dirEntry.isDirectory()) - if (!hasDirectory) { - return pluginTree - } + const hasNoDirectory = dirEntries.every((dirEntry) => !dirEntry.isDirectory()) - return indexDirEntry + return { indexDirEntry, hasNoDirectory } } async function processDirContents (pluginTree, { dirEntries, opts, indexDirEntry, prefix, dir, depth, currentDirHooks }) {