|
2 | 2 |
|
3 | 3 | const path = require('node:path') |
4 | 4 | const { fileURLToPath } = require('node:url') |
5 | | -const { statSync } = require('node:fs') |
6 | | -const { glob } = require('glob') |
| 5 | +const { statSync, lstatSync, realpathSync } = require('node:fs') |
| 6 | +const { glob } = require('node:fs/promises') |
7 | 7 | const fp = require('fastify-plugin') |
8 | 8 | const send = require('@fastify/send') |
9 | 9 | const encodingNegotiator = require('@fastify/accept-negotiator') |
@@ -63,7 +63,6 @@ async function fastifyStatic (fastify, opts) { |
63 | 63 | : prefix + '/' |
64 | 64 | } |
65 | 65 |
|
66 | | - // Set the schema hide property if defined in opts or true by default |
67 | 66 | const routeOpts = { |
68 | 67 | constraints: opts.constraints, |
69 | 68 | schema: { |
@@ -139,25 +138,55 @@ async function fastifyStatic (fastify, opts) { |
139 | 138 | for (let rootPath of roots) { |
140 | 139 | rootPath = rootPath.split(path.win32.sep).join(path.posix.sep) |
141 | 140 | !rootPath.endsWith('/') && (rootPath += '/') |
142 | | - const files = await glob('**/**', { |
143 | | - cwd: rootPath, absolute: false, follow: true, nodir: true, dot: opts.serveDotFiles, ignore: opts.globIgnore |
144 | | - }) |
145 | | - |
146 | | - for (let file of files) { |
147 | | - file = file.split(path.win32.sep).join(path.posix.sep) |
148 | | - const route = prefix + file |
149 | | - |
150 | | - if (routes.has(route)) { |
151 | | - continue |
152 | | - } |
153 | 141 |
|
154 | | - routes.add(route) |
| 142 | + const globPattern = opts.serveDotFiles ? '{**/**,.**/**}' : '**/**' |
| 143 | + const globExclude = opts.globIgnore?.length |
| 144 | + ? (f) => opts.globIgnore.some(p => path.matchesGlob(f, p)) |
| 145 | + : undefined |
| 146 | + |
| 147 | + const scanQueue = [{ cwd: rootPath.slice(0, -1), relPrefix: '' }] |
| 148 | + const visitedDirs = new Set([realpathSync(rootPath.slice(0, -1))]) |
| 149 | + |
| 150 | + const toUnixPath = (p) => p.split(path.win32.sep).join(path.posix.sep) |
| 151 | + const tryFsSync = (fn, p) => { try { return fn(p) } catch { return null } } |
| 152 | + |
| 153 | + while (scanQueue.length > 0) { |
| 154 | + const { cwd: scanCwd, relPrefix } = scanQueue.shift() |
| 155 | + for await (const f of glob(globPattern, { cwd: scanCwd, exclude: globExclude })) { |
| 156 | + if (f === '.') continue |
| 157 | + |
| 158 | + const file = toUnixPath(f) |
| 159 | + const fullPath = path.join(scanCwd, file) |
| 160 | + const lstat = tryFsSync(lstatSync, fullPath) |
| 161 | + if (!lstat || lstat.isDirectory()) continue |
| 162 | + |
| 163 | + const relFile = relPrefix ? `${relPrefix}/${file}` : file |
| 164 | + |
| 165 | + if (lstat.isSymbolicLink()) { |
| 166 | + const stat = tryFsSync(statSync, fullPath) |
| 167 | + if (!stat) continue |
| 168 | + |
| 169 | + if (stat.isDirectory()) { |
| 170 | + const realPath = tryFsSync(realpathSync, fullPath) |
| 171 | + /* c8 ignore next */ |
| 172 | + if (!realPath) continue |
| 173 | + if (!visitedDirs.has(realPath)) { |
| 174 | + visitedDirs.add(realPath) |
| 175 | + scanQueue.push({ cwd: fullPath, relPrefix: relFile }) |
| 176 | + } |
| 177 | + continue |
| 178 | + } |
| 179 | + } |
155 | 180 |
|
156 | | - setUpHeadAndGet(routeOpts, route, `/${file}`, rootPath) |
| 181 | + const route = prefix + relFile |
| 182 | + if (routes.has(route)) continue |
| 183 | + routes.add(route) |
| 184 | + setUpHeadAndGet(routeOpts, route, `/${relFile}`, rootPath) |
157 | 185 |
|
158 | | - const key = path.posix.basename(route) |
159 | | - if (indexes.has(key) && !indexDirs.has(key)) { |
160 | | - indexDirs.set(path.posix.dirname(route), rootPath) |
| 186 | + const key = path.posix.basename(route) |
| 187 | + if (indexes.has(key) && !indexDirs.has(key)) { |
| 188 | + indexDirs.set(path.posix.dirname(route), rootPath) |
| 189 | + } |
161 | 190 | } |
162 | 191 | } |
163 | 192 | } |
|
0 commit comments