Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const path = require('node:path')
const { fileURLToPath } = require('node:url')
const { statSync } = require('node:fs')
const { glob } = require('glob')
const { glob } = require('./lib/glob')
const fp = require('fastify-plugin')
const send = require('@fastify/send')
const encodingNegotiator = require('@fastify/accept-negotiator')
Expand Down
85 changes: 85 additions & 0 deletions lib/glob.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict'

const fs = require('node:fs/promises')
const path = require('node:path')

function globToRegex (pattern) {
let p = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&')
p = p.replace(/\*\*\//g, '___GLOBSTAR___')
p = p.replace(/\*\*/g, '.*')
p = p.replace(/\*/g, '[^/]*')
p = p.replace(/___GLOBSTAR___/g, '(?:.*/)?')
return new RegExp('^' + p + '$')
}

function isIgnored(relativePath, ignorePatterns) {
const patterns = [].concat(ignorePatterns)
return patterns.some(pattern => globToRegex(pattern).test(relativePath))
}

async function findFiles (dir, opts, baseDir = dir) {
const files = []
let list
try {
list = await fs.readdir(dir, { withFileTypes: true })
} catch (err) {
return files
}

for (const entry of list) {
const fullPath = path.join(dir, entry.name)
const relativePath = path.relative(baseDir, fullPath).split(path.win32.sep).join(path.posix.sep)

const isDotFile = entry.name.startsWith('.')
if (isDotFile && !opts.serveDotFiles) {
continue
}

if (opts.globIgnore && isIgnored(relativePath, opts.globIgnore)) {
continue
}

let isDirectory = entry.isDirectory()
let isFile = entry.isFile()

if (entry.isSymbolicLink()) {
try {
const stat = await fs.stat(fullPath)
isDirectory = stat.isDirectory()
isFile = stat.isFile()
} catch (err) {
continue
}
}

if (isDirectory) {
files.push(...(await findFiles(fullPath, opts, baseDir)))
} else if (isFile) {
files.push(relativePath)
}
}
return files
}

async function glob (pattern, options, cb) {
if (typeof options === 'function') {
cb = options
options = {}
}
const opts = {
serveDotFiles: options.dot,
globIgnore: options.ignore
}

if (typeof cb === 'function') {
findFiles(options.cwd, opts).then(
files => cb(null, files),
err => cb(err)
)
return
}

return findFiles(options.cwd, opts)
}

module.exports = { glob }
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@
"@fastify/send": "^4.0.0",
"content-disposition": "^1.0.1",
"fastify-plugin": "^5.0.0",
"fastq": "^1.17.1",
"glob": "^13.0.0"
"fastq": "^1.17.1"
},
"devDependencies": {
"@fastify/compress": "^8.0.0",
Expand Down
43 changes: 39 additions & 4 deletions test/static.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2517,10 +2517,16 @@ test('if dotfiles are properly served according to plugin options', async (t) =>

test('register with failing glob handler', async (t) => {
const fastifyStatic = proxyquire.noCallThru()('../', {
glob: function globStub (_pattern, _options, cb) {
process.nextTick(function () {
return cb(new Error('mock glob error'))
})
'./lib/glob': {
glob: function globStub (_pattern, _options, cb) {
if (typeof cb === 'function') {
process.nextTick(function () {
cb(new Error('mock glob error'))
})
return
}
return Promise.reject(new Error('mock glob error'))
}
}
})

Expand Down Expand Up @@ -4150,3 +4156,32 @@ test('register with wildcard false and globIgnore', async t => {
await response.text()
})
})

test('custom glob helper coverage', async (t) => {
const { glob } = require('../lib/glob')

await t.test('glob with non-existent directory returns empty array', async (t) => {
const files = await glob('**/**', { cwd: './non-existent-directory' })
t.assert.deepStrictEqual(files, [])
})

await t.test('glob with callback and options', async (t) => {
await new Promise((resolve, reject) => {
glob('**/**', { cwd: './non-existent-directory' }, (err, files) => {
if (err) return reject(err)
t.assert.deepStrictEqual(files, [])
resolve()
})
})
})

await t.test('glob with callback and no options', async (t) => {
await new Promise((resolve, reject) => {
glob('**/**', (err, files) => {
if (err) return reject(err)
t.assert.deepStrictEqual(files, [])
resolve()
})
})
})
})