@fastify/static 8.2.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,11 @@ on:
14
14
  - 'docs/**'
15
15
  - '*.md'
16
16
 
17
+ # This allows a subsequently queued workflow run to interrupt previous runs
18
+ concurrency:
19
+ group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
20
+ cancel-in-progress: true
21
+
17
22
  permissions:
18
23
  contents: read
19
24
 
package/LICENSE CHANGED
@@ -1,6 +1,8 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2023 Fastify
3
+ Copyright (c) 2017-present The Fastify team
4
+
5
+ The Fastify team members are listed at https://github.com/fastify/fastify#team.
4
6
 
5
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
8
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -16,7 +16,7 @@ npm i @fastify/static
16
16
  | Plugin version | Fastify version |
17
17
  | ---------------|-----------------|
18
18
  | `>=8.x` | `^5.x` |
19
- | `^7.x` | `^4.x` |
19
+ | `>=7.x <8.x` | `^4.x` |
20
20
  | `>=5.x <7.x` | `^3.x` |
21
21
  | `>=2.x <5.x` | `^2.x` |
22
22
  | `^1.x` | `^1.x` |
@@ -138,7 +138,12 @@ fastify.get('/favicon.ico', function (req, reply) {
138
138
 
139
139
  ### Options
140
140
 
141
- #### `root` (required)
141
+ #### `serve`
142
+ Default: `true`
143
+
144
+ If set to `false`, the plugin will not serve files from the `root` directory.
145
+
146
+ #### `root` (required if `serve` is not false)
142
147
 
143
148
  The absolute path of the directory containing the files to serve.
144
149
  The file to serve is determined by combining `req.url` with the
@@ -278,7 +283,7 @@ Example:
278
283
  fastify.register(require('@fastify/static'), {
279
284
  root: path.join(__dirname, 'public'),
280
285
  prefix: '/public/',
281
- index: false
286
+ index: false,
282
287
  list: true
283
288
  })
284
289
  ```
@@ -364,7 +369,7 @@ Default: `['']`
364
369
 
365
370
  Directory list can respond to different routes declared in `list.names`.
366
371
 
367
- > 🛈 Note: If a file with the same name exists, the actual file is sent.
372
+ > â„šī¸ Note: If a file with the same name exists, the actual file is sent.
368
373
 
369
374
  Example:
370
375
 
package/index.js CHANGED
@@ -16,10 +16,17 @@ const asteriskRegex = /\*/gu
16
16
 
17
17
  const supportedEncodings = ['br', 'gzip', 'deflate']
18
18
  send.mime.default_type = 'application/octet-stream'
19
+ const encodingExtensionMap = {
20
+ br: '.br',
21
+ gzip: '.gz'
22
+ }
19
23
 
24
+ /** @type {import("fastify").FastifyPluginAsync<import("./types").FastifyStaticOptions>} */
20
25
  async function fastifyStatic (fastify, opts) {
21
- opts.root = normalizeRoot(opts.root)
22
- checkRootPathForErrors(fastify, opts.root)
26
+ if (opts.serve !== false || opts.root !== undefined) {
27
+ opts.root = normalizeRoot(opts.root)
28
+ checkRootPathForErrors(fastify, opts.root)
29
+ }
23
30
 
24
31
  const setHeaders = opts.setHeaders
25
32
  if (setHeaders !== undefined && typeof setHeaders !== 'function') {
@@ -31,9 +38,7 @@ async function fastifyStatic (fastify, opts) {
31
38
  throw invalidDirListOpts
32
39
  }
33
40
 
34
- if (opts.dotfiles === undefined) {
35
- opts.dotfiles = 'allow'
36
- }
41
+ opts.dotfiles ??= 'allow'
37
42
 
38
43
  const sendOptions = {
39
44
  root: opts.root,
@@ -49,7 +54,7 @@ async function fastifyStatic (fastify, opts) {
49
54
  maxAge: opts.maxAge
50
55
  }
51
56
 
52
- let prefix = opts.prefix ?? (opts.prefix = '/')
57
+ let prefix = opts.prefix ??= '/'
53
58
 
54
59
  if (!opts.prefixAvoidTrailingSlash) {
55
60
  prefix =
@@ -62,7 +67,7 @@ async function fastifyStatic (fastify, opts) {
62
67
  const routeOpts = {
63
68
  constraints: opts.constraints,
64
69
  schema: {
65
- hide: opts.schemaHide !== undefined ? opts.schemaHide : true
70
+ hide: opts.schemaHide ?? true
66
71
  },
67
72
  logLevel: opts.logLevel,
68
73
  errorHandler (error, request, reply) {
@@ -126,7 +131,7 @@ async function fastifyStatic (fastify, opts) {
126
131
  })
127
132
  }
128
133
  } else {
129
- const indexes = opts.index === undefined ? ['index.html'] : [].concat(opts.index)
134
+ const indexes = new Set(opts.index === undefined ? ['index.html'] : [].concat(opts.index))
130
135
  const indexDirs = new Map()
131
136
  const routes = new Set()
132
137
 
@@ -151,7 +156,7 @@ async function fastifyStatic (fastify, opts) {
151
156
  setUpHeadAndGet(routeOpts, route, `/${file}`, rootPath)
152
157
 
153
158
  const key = path.posix.basename(route)
154
- if (indexes.includes(key) && !indexDirs.has(key)) {
159
+ if (indexes.has(key) && !indexDirs.has(key)) {
155
160
  indexDirs.set(path.posix.dirname(route), rootPath)
156
161
  }
157
162
  }
@@ -171,6 +176,15 @@ async function fastifyStatic (fastify, opts) {
171
176
 
172
177
  const allowedPath = opts.allowedPath
173
178
 
179
+ /**
180
+ * @param {import("fastify").FastifyRequest} request
181
+ * @param {import("fastify").FastifyReply} reply
182
+ * @param {string} pathname
183
+ * @param {import("./types").FastifyStaticOptions['root']} rootPath
184
+ * @param {number} [rootPathOffset]
185
+ * @param {import("@fastify/send").SendOptions} [pumpOptions]
186
+ * @param {Set<string>} [checkedEncodings]
187
+ */
174
188
  async function pumpSendToReply (
175
189
  request,
176
190
  reply,
@@ -189,6 +203,8 @@ async function fastifyStatic (fastify, opts) {
189
203
  } else {
190
204
  options.root = rootPath
191
205
  }
206
+ } else if (path.isAbsolute(pathname) === false) {
207
+ return reply.callNotFound()
192
208
  }
193
209
 
194
210
  if (allowedPath && !allowedPath(pathname, options.root, request)) {
@@ -203,9 +219,7 @@ async function fastifyStatic (fastify, opts) {
203
219
  * We conditionally create this structure to track our attempts
204
220
  * at sending pre-compressed assets
205
221
  */
206
- if (!checkedEncodings) {
207
- checkedEncodings = new Set()
208
- }
222
+ checkedEncodings ??= new Set()
209
223
 
210
224
  encoding = getEncodingHeader(request.headers, checkedEncodings)
211
225
 
@@ -215,9 +229,9 @@ async function fastifyStatic (fastify, opts) {
215
229
  if (!pathname) {
216
230
  return reply.callNotFound()
217
231
  }
218
- pathnameForSend = pathnameForSend + pathname + '.' + getEncodingExtension(encoding)
232
+ pathnameForSend = pathnameForSend + pathname + encodingExtensionMap[encoding]
219
233
  } else {
220
- pathnameForSend = pathname + '.' + getEncodingExtension(encoding)
234
+ pathnameForSend = pathname + encodingExtensionMap[encoding]
221
235
  }
222
236
  }
223
237
  }
@@ -360,9 +374,7 @@ async function fastifyStatic (fastify, opts) {
360
374
  // otherwise use send provided status code
361
375
  const newStatusCode = reply.statusCode !== 200 ? reply.statusCode : statusCode
362
376
  reply.code(newStatusCode)
363
- if (setHeaders !== undefined) {
364
- setHeaders(reply.raw, metadata.path, metadata.stat)
365
- }
377
+ setHeaders?.(reply.raw, metadata.path, metadata.stat)
366
378
  reply.headers(headers)
367
379
  if (encoding) {
368
380
  reply.header('content-type', getContentType(pathname))
@@ -380,20 +392,23 @@ async function fastifyStatic (fastify, opts) {
380
392
  url: route,
381
393
  handler: serveFileHandler
382
394
  })
383
- toSetUp.config = toSetUp.config || {}
395
+ toSetUp.config ??= {}
384
396
  toSetUp.config.file = file
385
397
  toSetUp.config.rootPath = rootPath
386
398
  fastify.route(toSetUp)
387
399
  }
388
400
 
401
+ /** @type {import("fastify").RouteHandlerMethod} */
389
402
  async function serveFileHandler (req, reply) {
390
- // TODO: remove the fallback branch when bump major
391
- /* c8 ignore next */
392
- const routeConfig = req.routeOptions?.config || req.routeConfig
403
+ const routeConfig = req.routeOptions?.config
393
404
  return pumpSendToReply(req, reply, routeConfig.file, routeConfig.rootPath)
394
405
  }
395
406
  }
396
407
 
408
+ /**
409
+ * @param {import("./types").FastifyStaticOptions['root']} root
410
+ * @returns {import("./types").FastifyStaticOptions['root']}
411
+ */
397
412
  function normalizeRoot (root) {
398
413
  if (root === undefined) {
399
414
  return root
@@ -417,6 +432,11 @@ function normalizeRoot (root) {
417
432
  return root
418
433
  }
419
434
 
435
+ /**
436
+ * @param {import("fastify").FastifyInstance} fastify
437
+ * @param {import("./types").FastifyStaticOptions['root']} rootPath
438
+ * @returns {void}
439
+ */
420
440
  function checkRootPathForErrors (fastify, rootPath) {
421
441
  if (rootPath === undefined) {
422
442
  throw new Error('"root" option is required')
@@ -445,6 +465,11 @@ function checkRootPathForErrors (fastify, rootPath) {
445
465
  throw new Error('"root" option must be a string or array of strings')
446
466
  }
447
467
 
468
+ /**
469
+ * @param {import("fastify").FastifyInstance} fastify
470
+ * @param {import("./types").FastifyStaticOptions['root']} rootPath
471
+ * @returns {void}
472
+ */
448
473
  function checkPath (fastify, rootPath) {
449
474
  if (typeof rootPath !== 'string') {
450
475
  throw new TypeError('"root" option must be a string')
@@ -471,6 +496,10 @@ function checkPath (fastify, rootPath) {
471
496
  }
472
497
  }
473
498
 
499
+ /**
500
+ * @param {string} path
501
+ * @return {string}
502
+ */
474
503
  function getContentType (path) {
475
504
  const type = send.mime.getType(path) || send.mime.default_type
476
505
 
@@ -480,6 +509,12 @@ function getContentType (path) {
480
509
  return `${type}; charset=utf-8`
481
510
  }
482
511
 
512
+ /**
513
+ * @param {string} pathname
514
+ * @param {*} root
515
+ * @param {import("./types").FastifyStaticOptions['index']} [indexFiles]
516
+ * @return {string|boolean}
517
+ */
483
518
  function findIndexFile (pathname, root, indexFiles = ['index.html']) {
484
519
  if (Array.isArray(indexFiles)) {
485
520
  return indexFiles.find(filename => {
@@ -496,7 +531,11 @@ function findIndexFile (pathname, root, indexFiles = ['index.html']) {
496
531
  return false
497
532
  }
498
533
 
499
- // Adapted from https://github.com/fastify/fastify-compress/blob/665e132fa63d3bf05ad37df3c20346660b71a857/index.js#L451
534
+ /**
535
+ * Adapted from https://github.com/fastify/fastify-compress/blob/665e132fa63d3bf05ad37df3c20346660b71a857/index.js#L451
536
+ * @param {import('fastify').FastifyRequest['headers']} headers
537
+ * @param {Set<string>} checked
538
+ */
500
539
  function getEncodingHeader (headers, checked) {
501
540
  if (!('accept-encoding' in headers)) return
502
541
 
@@ -509,24 +548,18 @@ function getEncodingHeader (headers, checked) {
509
548
  )
510
549
  }
511
550
 
512
- function getEncodingExtension (encoding) {
513
- switch (encoding) {
514
- case 'br':
515
- return 'br'
516
-
517
- case 'gzip':
518
- return 'gz'
519
- }
520
- }
521
-
551
+ /**
552
+ * @param {string} url
553
+ * @return {string}
554
+ */
522
555
  function getRedirectUrl (url) {
523
556
  let i = 0
524
557
  // we detect how many slash before a valid path
525
- for (; i < url.length; ++i) {
558
+ for (const ul = url.length; i < ul; ++i) {
526
559
  if (url[i] !== '/' && url[i] !== '\\') break
527
560
  }
528
561
  // turns all leading / or \ into a single /
529
- url = '/' + url.substr(i)
562
+ url = '/' + url.slice(i)
530
563
  try {
531
564
  const parsed = new URL(url, 'http://localhost.com/')
532
565
  const parsedPathname = parsed.pathname
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/static",
3
- "version": "8.2.0",
3
+ "version": "8.3.0",
4
4
  "description": "Plugin for serving static files as fast as possible.",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -67,8 +67,7 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@fastify/compress": "^8.0.0",
70
- "@fastify/pre-commit": "^2.1.0",
71
- "@types/node": "^22.0.0",
70
+ "@types/node": "^24.0.13",
72
71
  "borp": "^0.20.0",
73
72
  "c8": "^10.1.3",
74
73
  "concat-stream": "^2.0.0",
@@ -77,7 +76,7 @@
77
76
  "neostandard": "^0.12.0",
78
77
  "pino": "^9.1.0",
79
78
  "proxyquire": "^2.1.3",
80
- "tsd": "^0.32.0"
79
+ "tsd": "^0.33.0"
81
80
  },
82
81
  "tsd": {
83
82
  "directory": "test/types"
@@ -753,6 +753,51 @@ test('serving disabled', async (t) => {
753
753
  })
754
754
  })
755
755
 
756
+ test('serving disabled without root', async (t) => {
757
+ t.plan(2)
758
+
759
+ const pluginOptions = {
760
+ prefix: '/static/',
761
+ serve: false
762
+ }
763
+ const fastify = Fastify()
764
+ fastify.register(fastifyStatic, pluginOptions)
765
+
766
+ t.after(() => fastify.close())
767
+
768
+ fastify.get('/foo/bar/r', (_request, reply) => {
769
+ reply.sendFile('index.html')
770
+ })
771
+
772
+ fastify.get('/foo/bar/a', (_request, reply) => {
773
+ reply.sendFile(path.join(__dirname, pluginOptions.prefix, 'index.html'))
774
+ })
775
+
776
+ t.after(() => fastify.close())
777
+
778
+ await fastify.listen({ port: 0 })
779
+
780
+ fastify.server.unref()
781
+
782
+ await t.test('/static/index.html via sendFile not found', async (t) => {
783
+ t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT)
784
+
785
+ const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar/a')
786
+ t.assert.ok(response.ok)
787
+ t.assert.deepStrictEqual(response.status, 200)
788
+ t.assert.deepStrictEqual(await response.text(), indexContent)
789
+ genericResponseChecks(t, response)
790
+ })
791
+
792
+ await t.test('/static/index.html via sendFile with relative path not found', async (t) => {
793
+ t.plan(2)
794
+
795
+ const response = await fetch('http://localhost:' + fastify.server.address().port + '/foo/bar/r')
796
+ t.assert.ok(!response.ok)
797
+ t.assert.deepStrictEqual(response.status, 404)
798
+ })
799
+ })
800
+
756
801
  test('sendFile', async (t) => {
757
802
  t.plan(4)
758
803
 
@@ -1215,7 +1260,7 @@ test('maxAge option', async (t) => {
1215
1260
  })
1216
1261
 
1217
1262
  test('errors', async (t) => {
1218
- t.plan(11)
1263
+ t.plan(12)
1219
1264
 
1220
1265
  await t.test('no root', async (t) => {
1221
1266
  t.plan(1)
@@ -1280,6 +1325,16 @@ test('errors', async (t) => {
1280
1325
  await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
1281
1326
  })
1282
1327
 
1328
+ await t.test('no root and serve: false', async (t) => {
1329
+ t.plan(1)
1330
+ const pluginOptions = {
1331
+ serve: false,
1332
+ root: []
1333
+ }
1334
+ const fastify = Fastify({ logger: false })
1335
+ await t.assert.rejects(async () => await fastify.register(fastifyStatic, pluginOptions))
1336
+ })
1337
+
1283
1338
  await t.test('duplicate root paths are not allowed', async (t) => {
1284
1339
  t.plan(1)
1285
1340
  const pluginOptions = {
@@ -1936,8 +1991,6 @@ test('register /static with wildcard false and alternative index', async t => {
1936
1991
 
1937
1992
  const { promise, resolve } = Promise.withResolvers()
1938
1993
 
1939
- // simple-get doesn't tell us about redirects so use http.request directly
1940
- // to verify we do not get a redirect when not requested
1941
1994
  const testurl = 'http://localhost:' + fastify.server.address().port + '/static'
1942
1995
  const req = http.request(url.parse(testurl), res => {
1943
1996
  t.assert.deepStrictEqual(res.statusCode, 200)
@@ -2042,7 +2095,6 @@ test('register /static with redirect true', async t => {
2042
2095
 
2043
2096
  const { promise, resolve } = Promise.withResolvers()
2044
2097
 
2045
- // simple-get doesn't tell us about redirects so use http.request directly
2046
2098
  const testurl = 'http://localhost:' + fastify.server.address().port + '/static?a=b'
2047
2099
  const req = http.request(url.parse(testurl), res => {
2048
2100
  t.assert.deepStrictEqual(res.statusCode, 301)
@@ -2066,7 +2118,6 @@ test('register /static with redirect true', async t => {
2066
2118
 
2067
2119
  const { promise, resolve } = Promise.withResolvers()
2068
2120
 
2069
- // simple-get doesn't tell us about redirects so use http.request directly
2070
2121
  const testurl = 'http://localhost:' + fastify.server.address().port + '/static'
2071
2122
  const req = http.request(url.parse(testurl), res => {
2072
2123
  t.assert.deepStrictEqual(res.statusCode, 301)
@@ -2104,7 +2155,6 @@ test('register /static with redirect true', async t => {
2104
2155
 
2105
2156
  const { promise, resolve } = Promise.withResolvers()
2106
2157
 
2107
- // simple-get doesn't tell us about redirects so use http.request directly
2108
2158
  const testurl = 'http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test?a=b'
2109
2159
  const req = http.request(url.parse(testurl), res => {
2110
2160
  t.assert.deepStrictEqual(res.statusCode, 301)
@@ -2161,7 +2211,6 @@ test('register /static with redirect true and wildcard false', async t => {
2161
2211
 
2162
2212
  const { promise, resolve } = Promise.withResolvers()
2163
2213
 
2164
- // simple-get doesn't tell us about redirects so use http.request directly
2165
2214
  const testurl = 'http://localhost:' + fastify.server.address().port + '/static?a=b'
2166
2215
  const req = http.request(url.parse(testurl), res => {
2167
2216
  t.assert.deepStrictEqual(res.statusCode, 301)
@@ -2213,7 +2262,6 @@ test('register /static with redirect true and wildcard false', async t => {
2213
2262
 
2214
2263
  const { promise, resolve } = Promise.withResolvers()
2215
2264
 
2216
- // simple-get doesn't tell us about redirects so use http.request directly
2217
2265
  const testurl = 'http://localhost:' + fastify.server.address().port + '/static/deep/path/for/test?a=b'
2218
2266
  const req = http.request(url.parse(testurl), res => {
2219
2267
  t.assert.deepStrictEqual(res.statusCode, 301)
package/types/index.d.ts CHANGED
@@ -84,40 +84,52 @@ declare namespace fastifyStatic {
84
84
  serveDotFiles?: boolean;
85
85
  }
86
86
 
87
- export interface FastifyStaticOptions extends SendOptions {
88
- root: string | string[] | URL | URL[];
89
- prefix?: string;
90
- prefixAvoidTrailingSlash?: boolean;
91
- serve?: boolean;
92
- decorateReply?: boolean;
93
- schemaHide?: boolean;
94
- setHeaders?: (res: SetHeadersResponse, path: string, stat: Stats) => void;
95
- redirect?: boolean;
96
- wildcard?: boolean;
97
- globIgnore?: string[];
98
- list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
99
- allowedPath?: (pathName: string, root: string, request: FastifyRequest) => boolean;
100
- /**
101
- * @description
102
- * Opt-in to looking for pre-compressed files
103
- */
104
- preCompressed?: boolean;
105
-
106
- // Passed on to `send`
107
- acceptRanges?: boolean;
108
- contentType?: boolean;
109
- cacheControl?: boolean;
110
- dotfiles?: 'allow' | 'deny' | 'ignore';
111
- etag?: boolean;
112
- extensions?: string[];
113
- immutable?: boolean;
114
- index?: string[] | string | false;
115
- lastModified?: boolean;
116
- maxAge?: string | number;
117
- constraints?: RouteOptions['constraints'];
118
- logLevel?: RouteOptions['logLevel'];
87
+ type Root = string | string[] | URL | URL[]
88
+
89
+ type RootOptions = {
90
+ serve: true;
91
+ root: Root;
92
+ } | {
93
+ serve?: false;
94
+ root?: Root;
119
95
  }
120
96
 
97
+ export type FastifyStaticOptions =
98
+ SendOptions
99
+ & RootOptions
100
+ & {
101
+ // Added by this plugin
102
+ prefix?: string;
103
+ prefixAvoidTrailingSlash?: boolean;
104
+ decorateReply?: boolean;
105
+ schemaHide?: boolean;
106
+ setHeaders?: (res: SetHeadersResponse, path: string, stat: Stats) => void;
107
+ redirect?: boolean;
108
+ wildcard?: boolean;
109
+ globIgnore?: string[];
110
+ list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
111
+ allowedPath?: (pathName: string, root: string, request: FastifyRequest) => boolean;
112
+ /**
113
+ * @description
114
+ * Opt-in to looking for pre-compressed files
115
+ */
116
+ preCompressed?: boolean;
117
+
118
+ // Passed on to `send`
119
+ acceptRanges?: boolean;
120
+ contentType?: boolean;
121
+ cacheControl?: boolean;
122
+ dotfiles?: 'allow' | 'deny' | 'ignore';
123
+ etag?: boolean;
124
+ extensions?: string[];
125
+ immutable?: boolean;
126
+ index?: string[] | string | false;
127
+ lastModified?: boolean;
128
+ maxAge?: string | number;
129
+ constraints?: RouteOptions['constraints'];
130
+ logLevel?: RouteOptions['logLevel'];
131
+ }
132
+
121
133
  export const fastifyStatic: FastifyStaticPlugin
122
134
 
123
135
  export { fastifyStatic as default }
@@ -67,7 +67,7 @@ const options: FastifyStaticOptions = {
67
67
  return true
68
68
  },
69
69
  constraints: {
70
- host: /.*\.example\.com/,
70
+ host: /^.*\.example\.com$/,
71
71
  version: '1.0.2'
72
72
  },
73
73
  logLevel: 'warn'
@@ -120,6 +120,19 @@ expectAssignable<FastifyStaticOptions>({
120
120
  root: [new URL('')]
121
121
  })
122
122
 
123
+ expectError<FastifyStaticOptions>({
124
+ serve: true
125
+ })
126
+
127
+ expectAssignable<FastifyStaticOptions>({
128
+ serve: true,
129
+ root: ''
130
+ })
131
+
132
+ expectAssignable<FastifyStaticOptions>({
133
+ serve: false
134
+ })
135
+
123
136
  appWithImplicitHttp
124
137
  .register(fastifyStatic, options)
125
138
  .after(() => {