Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@fastify/autoload

Package Overview
Dependencies
Maintainers
20
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fastify/autoload - npm Package Compare versions

Comparing version 6.0.0-pre.fv5.1 to 6.0.0

lib/find-plugins.js

434

index.js
'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 findPlugins = require('./lib/find-plugins')
const runtime = require('./lib/runtime')
const { pathToFileURL } = require('node:url')
const runtime = require('./runtime')
const routeParamPattern = /\/_/gu
const routeMixedParamPattern = /__/gu
const defaults = {

@@ -22,233 +20,43 @@ scriptPattern: /(?:(?:^.?|\.[^d]|[^.]d|[^.][^d])\.ts|\.js|\.cjs|\.mjs|\.cts|\.mts)$/iu,

const opts = { ...defaults, packageType, ...options }
const pluginTree = await findPlugins(opts.dir, opts)
const pluginsMeta = {}
const hooksMeta = {}
const pluginTree = await findPlugins(opts.dir, { opts })
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)
})
}))
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
}
}
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)
} else {
const composedPlugin = async function (app) {
// find hook functions for this prefix
for (const hookFile of hookFiles) {
const hookPlugin = hooksMeta[hookFile.file]
// encapsulate hooks at plugin level
app.register(hookPlugin)
}
registerAllPlugins(app, pluginFiles)
}
fastify.register(composedPlugin)
}
}
function registerAllPlugins (app, pluginFiles) {
for (const pluginFile of pluginFiles) {
// 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)
}
}
await loadPlugins({ pluginTree, options, opts, 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
async function loadPlugins ({ pluginTree, options, opts, fastify }) {
for (const key in pluginTree) {
const node = {
...pluginTree[key],
pluginsMeta: {},
hooksMeta: {}
}
directories.pop()
}
}
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)
}
node.pluginsMeta[plugin.name] = plugin
}
})
.catch((err) => {
throw enrichError(err)
})
}))
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'
}
}
await Promise.all(node.hooks.map((h) => {
return loadHook(h, opts)
.then((hookPlugin) => {
node.hooksMeta[h.file] = hookPlugin
})
.catch((err) => {
throw enrichError(err)
})
}))
// 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
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
registerNode(node, fastify)
}
// Contains index file?
const indexDirent = list.find((dirent) => indexPattern.test(dirent.name))
if (indexDirent) {
const file = join(dir, indexDirent.name)
const { language, type } = getScriptType(file, options.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 (ignorePattern && dirent.name.match(ignorePattern)) {
continue
}
const atMaxDepth = Number.isFinite(maxDepth) && maxDepth <= depth
const file = join(dir, dirent.name)
if (dirent.isDirectory() && !atMaxDepth) {
let prefixBreadCrumb = (prefix ? `${prefix}/` : '/')
if (dirNameRoutePrefix === true) {
prefixBreadCrumb += dirent.name
} else if (typeof dirNameRoutePrefix === 'function') {
const prefixReplacer = 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))
} else {
directoryPromises.push(findPlugins(file, options, hookedAccumulator, prefixBreadCrumb, 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() && scriptPattern.test(dirent.name)) {
const { language, type } = getScriptType(file, options.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)) {
accumulatePlugin({ file, type })
}
}
}
await Promise.all(directoryPromises)
return hookedAccumulator
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, '/')
if (matchFilter && !filterPath(filePath, matchFilter)) {
return
}
if (ignoreFilter && filterPath(filePath, ignoreFilter)) {
return
}
hookedAccumulator[prefix || '/'].plugins.push({ file, type, prefix })
}
}

@@ -274,14 +82,3 @@

const plugin = wrapRoutes(content.default || content)
const pluginConfig = (content.default && 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')] || {}

@@ -303,10 +100,3 @@

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, '/')
}
handlePrefixConfig({ plugin, pluginOptions, content, directoryPrefix })

@@ -323,2 +113,48 @@ return {

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
const type = Object.prototype.toString.call(hookContent)
if (type === '[object AsyncFunction]' || type === '[object Function]') {
hookContent[Symbol.for('skip-override')] = true
}
return hookContent
}
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 = {}) {

@@ -349,14 +185,48 @@ const { plugin, name, options, dependencies = [] } = meta

function filterPath (path, filter) {
if (typeof filter === 'string') {
return path.includes(filter)
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
}
if (filter instanceof RegExp) {
return filter.test(path)
return { ...pluginConfig, ...overrideConfig }
}
function handlePrefixConfig ({ plugin, pluginOptions, content, directoryPrefix }) {
if (pluginOptions.prefix?.endsWith('/')) {
pluginOptions.prefix = pluginOptions.prefix.slice(0, -1)
}
return filter(path)
let prefix
if (plugin.autoPrefix !== undefined) {
prefix = plugin.autoPrefix
} else if (content.autoPrefix !== undefined) {
prefix = content.autoPrefix
} else {
prefix = directoryPrefix
}
const prefixOverride = plugin.prefixOverride ?? content.prefixOverride
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) {
if (pattern.match(routeMixedParamPattern)) {
return pattern.replace(routeMixedParamPattern, ':')
} else if (pattern.match(routeParamPattern)) {
return pattern.replace(routeParamPattern, '/:')
}
return pattern
}
/**

@@ -372,8 +242,5 @@ * Used to determine if the contents of a required autoloaded file matches

function isRouteObject (input) {
if (input &&
Object.prototype.toString.call(input) === '[object Object]' &&
Object.prototype.hasOwnProperty.call(input, 'method')) {
return true
}
return false
return !!(input &&
Object.prototype.toString.call(input) === '[object Object]' &&
Object.hasOwn(input, 'method'))
}

@@ -398,3 +265,3 @@

result = true
} else if (Object.prototype.hasOwnProperty.call(input, 'default')) {
} else if (Object.hasOwn(input, 'default')) {
result = isPluginOrModule(input.default)

@@ -414,25 +281,6 @@ } else {

}
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)
}
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 enrichError (err) {

@@ -449,2 +297,24 @@ // Hack SyntaxError message so that we provide

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

@@ -451,0 +321,0 @@ // same as fastify-plugin

{
"name": "@fastify/autoload",
"version": "6.0.0-pre.fv5.1",
"version": "6.0.0",
"description": "Require all plugins in a directory",

@@ -47,12 +47,12 @@ "main": "index.js",

"@fastify/pre-commit": "^2.1.0",
"@fastify/url-data": "^6.0.0-pre.fv5.1",
"@fastify/url-data": "^6.0.0",
"@swc-node/register": "^1.9.1",
"@swc/core": "^1.5.25",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.2",
"@types/node": "^22.0.0",
"@types/tap": "^15.0.11",
"esbuild": "^0.21.4",
"esbuild": "^0.23.0",
"esbuild-register": "^3.5.0",
"fastify": "^5.0.0-alpha.3",
"fastify-plugin": "^5.0.0-pre.fv5.1",
"fastify": "^5.0.0-alpha.4",
"fastify-plugin": "^5.0.0",
"jest": "^29.7.0",

@@ -70,3 +70,3 @@ "snazzy": "^9.0.0",

"vite": "^5.2.12",
"vitest": "^1.6.0"
"vitest": "^2.0.3"
},

@@ -73,0 +73,0 @@ "standard": {

@@ -152,3 +152,3 @@ # @fastify/autoload

})
```
```

@@ -155,0 +155,0 @@ - `indexPattern` (optional) - Regex to override the `index.js` naming convention

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc