@fastify/static 9.1.3 → 9.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.
package/.gitattributes CHANGED
@@ -1,5 +1,2 @@
1
- # Set the default behavior, in case people don't have core.autocrlf set
2
- * text=auto
3
-
4
- # Require Unix line endings
5
- * text eol=lf
1
+ # Set default behavior to automatically convert line endings
2
+ * text=auto eol=lf
@@ -2,12 +2,52 @@ version: 2
2
2
  updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
+ commit-message:
6
+ # Prefix all commit messages with "chore: "
7
+ prefix: "chore"
5
8
  schedule:
6
- interval: "monthly"
9
+ interval: "weekly"
10
+ cooldown:
11
+ default-days: 7
12
+ allow:
13
+ - dependency-name: "*"
14
+ update-types:
15
+ - "version-update:semver-major"
7
16
  open-pull-requests-limit: 10
8
17
 
9
18
  - package-ecosystem: "npm"
10
19
  directory: "/"
20
+ commit-message:
21
+ # Prefix all commit messages with "chore: "
22
+ prefix: "chore"
11
23
  schedule:
12
- interval: "monthly"
24
+ interval: "weekly"
25
+ cooldown:
26
+ default-days: 7
27
+ versioning-strategy: "increase-if-necessary"
28
+ allow:
29
+ - dependency-name: "*"
30
+ update-types:
31
+ - "version-update:semver-major"
32
+ ignore:
33
+ # TODO: remove ignore until neostandard support ESLint 10
34
+ - dependency-name: "eslint"
35
+ - dependency-name: "neostandard"
36
+ - dependency-name: "@stylistic/*"
13
37
  open-pull-requests-limit: 10
38
+ groups:
39
+ # Production dependencies with breaking changes
40
+ dependencies:
41
+ dependency-type: "production"
42
+ # ESLint related dependencies
43
+ dev-dependencies-eslint:
44
+ patterns:
45
+ - "eslint"
46
+ - "neostandard"
47
+ - "@stylistic/*"
48
+ # TypeScript related dependencies
49
+ dev-dependencies-typescript:
50
+ patterns:
51
+ - "@types/*"
52
+ - "tstyche"
53
+ - "typescript"
package/README.md CHANGED
@@ -114,7 +114,7 @@ fastify.get('/path/without/cache/control', function (req, reply) {
114
114
  ### Managing cache-control headers
115
115
 
116
116
  Production sites should use a reverse-proxy to manage caching headers.
117
- However, here is an example of using fastify-static to host a Single Page Application (for example a [vite.js](https://vite.dev/) build) with sane caching.
117
+ However, here is an example of using fastify-static to host a Single Page Application (for example a [vite.js](https://vite.dev) build) with sane caching.
118
118
 
119
119
  ```js
120
120
  fastify.register(require('@fastify/static'), {
@@ -253,6 +253,8 @@ Default: `(pathName, root, request) => true`
253
253
  This function filters served files. Using the request object, complex path authentication is possible.
254
254
  Returning `true` serves the file; returning `false` calls Fastify's 404 handler.
255
255
 
256
+ When using `preCompressed: true`, `allowedPath` receives the requested path before `.br` or `.gz` variants are selected; see the `preCompressed` note below before using extension-based access rules.
257
+
256
258
  #### `index`
257
259
 
258
260
  Default: `undefined`
@@ -470,6 +472,8 @@ Default: `false`
470
472
 
471
473
  First, try to send the brotli encoded asset (if supported by `Accept-Encoding` headers), then gzip, and finally the original `pathname`. Skip compression for smaller files that do not benefit from it.
472
474
 
475
+ > ⚠ Warning: `allowedPath` is evaluated against the requested path before pre-compressed variants are selected. A request for `/file.txt` can serve `/file.txt.gz` or `/file.txt.br` when the client accepts that encoding, even if direct requests to `.gz` or `.br` files are denied by `allowedPath`. Treat pre-compressed files as public alternate encodings of the same asset: keep restricted compressed files outside the served root, disable `preCompressed`, or deny the base path too.
476
+
473
477
  Assume this structure with the compressed asset as a sibling of the uncompressed counterpart:
474
478
 
475
479
  ```
package/index.js CHANGED
@@ -76,7 +76,7 @@ async function fastifyStatic (fastify, opts) {
76
76
  return
77
77
  }
78
78
 
79
- fastify.errorHandler(error, request, reply)
79
+ return fastify.errorHandler(error, request, reply)
80
80
  }
81
81
  }
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/static",
3
- "version": "9.1.3",
3
+ "version": "9.3.0",
4
4
  "description": "Plugin for serving static files as fast as possible.",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -10,7 +10,7 @@
10
10
  "lint": "eslint",
11
11
  "lint:fix": "eslint --fix",
12
12
  "test": "npm run test:unit && npm run test:typescript",
13
- "test:typescript": "tsd",
13
+ "test:typescript": "tstyche",
14
14
  "test:unit": "borp -C --check-coverage --lines 100",
15
15
  "example": "node example/server.js",
16
16
  "example:benchmark": "node example/server-benchmark.js"
@@ -62,13 +62,13 @@
62
62
  "@fastify/accept-negotiator": "^2.0.0",
63
63
  "@fastify/send": "^4.0.0",
64
64
  "content-disposition": "^1.0.1",
65
- "fastify-plugin": "^5.0.0",
65
+ "fastify-plugin": "^6.0.0",
66
66
  "fastq": "^1.17.1",
67
67
  "glob": "^13.0.0"
68
68
  },
69
69
  "devDependencies": {
70
- "@fastify/compress": "^8.0.0",
71
- "@types/node": "^25.0.3",
70
+ "@fastify/compress": "^9.0.0",
71
+ "@types/node": "^26.0.0",
72
72
  "autocannon": "^8.0.0",
73
73
  "borp": "^1.0.0",
74
74
  "c8": "^11.0.0",
@@ -78,10 +78,7 @@
78
78
  "neostandard": "^0.12.0",
79
79
  "pino": "^10.0.0",
80
80
  "proxyquire": "^2.1.3",
81
- "tsd": "^0.33.0"
82
- },
83
- "tsd": {
84
- "directory": "test/types"
81
+ "tstyche": "^7.0.0"
85
82
  },
86
83
  "publishConfig": {
87
84
  "access": "public"
@@ -2637,6 +2637,43 @@ test('routes should fallback to default errorHandler', async t => {
2637
2637
  })
2638
2638
  })
2639
2639
 
2640
+ test('routes should propagate return value from async custom errorHandler', async t => {
2641
+ t.plan(3)
2642
+
2643
+ const pluginOptions = {
2644
+ root: path.join(__dirname, '/static'),
2645
+ prefix: '/static/'
2646
+ }
2647
+
2648
+ const fastify = Fastify()
2649
+
2650
+ // An async error handler that returns its payload (without an explicit
2651
+ // reply.send) relies on Fastify awaiting the returned promise. If
2652
+ // @fastify/static does not propagate the return value of
2653
+ // fastify.errorHandler(), the promise is dropped and the reply never
2654
+ // resolves with the customized body.
2655
+ fastify.setErrorHandler(async function (_error, _request, reply) {
2656
+ reply.code(418).type('text/plain')
2657
+ return 'handled by return value'
2658
+ })
2659
+
2660
+ fastify.addHook('onRoute', function (routeOptions) {
2661
+ routeOptions.preHandler = (_request, _reply, done) => {
2662
+ const fakeError = new Error()
2663
+ fakeError.code = 'SOMETHING_ELSE'
2664
+ done(fakeError)
2665
+ }
2666
+ })
2667
+
2668
+ fastify.register(fastifyStatic, pluginOptions)
2669
+ t.after(() => fastify.close())
2670
+
2671
+ const response = await fastify.inject({ method: 'GET', url: '/static/index.html' })
2672
+ t.assert.deepStrictEqual(response.statusCode, 418)
2673
+ t.assert.ok(response.headers['content-type'].startsWith('text/plain'))
2674
+ t.assert.deepStrictEqual(response.body, 'handled by return value')
2675
+ })
2676
+
2640
2677
  test('percent encoded URLs in glob mode', async (t) => {
2641
2678
  t.plan(3)
2642
2679
 
package/types/index.d.ts CHANGED
@@ -82,18 +82,19 @@ declare namespace fastifyStatic {
82
82
 
83
83
  type Root = string | string[] | URL | URL[]
84
84
 
85
- type RootOptions = {
86
- serve: true;
87
- root: Root;
88
- } | {
89
- serve?: false;
90
- root?: Root;
91
- }
85
+ type RootOptions =
86
+ | {
87
+ serve: true;
88
+ root: Root;
89
+ }
90
+ | {
91
+ serve?: false;
92
+ root?: Root;
93
+ }
92
94
 
93
- export type FastifyStaticOptions =
94
- SendOptions
95
- & RootOptions
96
- & {
95
+ export type FastifyStaticOptions = SendOptions &
96
+ RootOptions &
97
+ Omit<import('fastify').RegisterOptions, 'logLevel'> & {
97
98
  // Added by this plugin
98
99
  prefix?: string;
99
100
  prefixAvoidTrailingSlash?: boolean;
@@ -104,7 +105,11 @@ declare namespace fastifyStatic {
104
105
  wildcard?: boolean;
105
106
  globIgnore?: string[];
106
107
  list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
107
- allowedPath?: (pathName: string, root: string, request: FastifyRequest) => boolean;
108
+ allowedPath?: (
109
+ pathName: string,
110
+ root: string,
111
+ request: FastifyRequest
112
+ ) => boolean;
108
113
  /**
109
114
  * @description
110
115
  * Opt-in to looking for pre-compressed files
@@ -123,7 +128,7 @@ declare namespace fastifyStatic {
123
128
  lastModified?: boolean;
124
129
  maxAge?: string | number;
125
130
  constraints?: RouteOptions['constraints'];
126
- logLevel?: RouteOptions['logLevel'];
131
+ logLevel?: NonNullable<RouteOptions['logLevel']>;
127
132
  }
128
133
 
129
134
  export const fastifyStatic: FastifyStaticPlugin
@@ -1,10 +1,10 @@
1
- import fastify, { FastifyInstance, FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify'
2
- import { Server } from 'node:http'
3
- import { Stats } from 'node:fs'
4
- import { expectAssignable, expectError, expectType } from 'tsd'
1
+ import fastify, { type FastifyInstance, type FastifyPluginAsync, type FastifyRequest, type FastifyReply } from 'fastify'
2
+ import { type Server } from 'node:http'
3
+ import { type Stats } from 'node:fs'
4
+ import { expect } from 'tstyche'
5
5
  import * as fastifyStaticStar from '..'
6
6
  import fastifyStatic, {
7
- FastifyStaticOptions,
7
+ type FastifyStaticOptions,
8
8
  fastifyStatic as fastifyStaticNamed
9
9
  } from '..'
10
10
 
@@ -21,15 +21,13 @@ app.register(fastifyStaticCjsImport.fastifyStatic, { root: __dirname })
21
21
  app.register(fastifyStaticStar.default, { root: __dirname })
22
22
  app.register(fastifyStaticStar.fastifyStatic, { root: __dirname })
23
23
 
24
- expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStatic)
25
- expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticNamed)
26
- expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.default)
27
- expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.fastifyStatic)
28
- expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(fastifyStaticStar.default)
29
- expectType<FastifyPluginAsync<FastifyStaticOptions, Server>>(
30
- fastifyStaticStar.fastifyStatic
31
- )
32
- expectType<any>(fastifyStaticCjs)
24
+ expect(fastifyStatic).type.toBe<FastifyPluginAsync<FastifyStaticOptions, Server>>()
25
+ expect(fastifyStaticNamed).type.toBe<FastifyPluginAsync<FastifyStaticOptions, Server>>()
26
+ expect(fastifyStaticCjsImport.default).type.toBe<FastifyPluginAsync<FastifyStaticOptions, Server>>()
27
+ expect(fastifyStaticCjsImport.fastifyStatic).type.toBe<FastifyPluginAsync<FastifyStaticOptions, Server>>()
28
+ expect(fastifyStaticStar.default).type.toBe<FastifyPluginAsync<FastifyStaticOptions, Server>>()
29
+ expect(fastifyStaticStar.fastifyStatic).type.toBe<FastifyPluginAsync<FastifyStaticOptions, Server>>()
30
+ expect(fastifyStaticCjs).type.toBe<any>()
33
31
 
34
32
  const appWithImplicitHttp = fastify()
35
33
  const options: FastifyStaticOptions = {
@@ -53,14 +51,14 @@ const options: FastifyStaticOptions = {
53
51
  globIgnore: ['**/*.private'],
54
52
  list: false,
55
53
  setHeaders: (res, path, stat) => {
56
- expectType<string>(res.filename)
57
- expectType<number>(res.statusCode)
58
- expectType<ReturnType<FastifyReply['getHeader']>>(res.getHeader('X-Test'))
54
+ expect(res.filename).type.toBe<string>()
55
+ expect(res.statusCode).type.toBe<number>()
56
+ expect(res.getHeader('X-Test')).type.toBe<ReturnType<FastifyReply['getHeader']>>()
59
57
  res.setHeader('X-Test', 'string')
60
58
 
61
- expectType<string>(path)
59
+ expect(path).type.toBe<string>()
62
60
 
63
- expectType<Stats>(stat)
61
+ expect(stat).type.toBe<Stats>()
64
62
  },
65
63
  preCompressed: false,
66
64
  allowedPath: (_pathName: string, _root: string, _request: FastifyRequest) => {
@@ -73,64 +71,53 @@ const options: FastifyStaticOptions = {
73
71
  logLevel: 'warn'
74
72
  }
75
73
 
76
- expectError<FastifyStaticOptions>({
77
- root: '',
78
- wildcard: '**/**'
79
- })
80
-
81
- expectAssignable<FastifyStaticOptions>({
82
- root: '',
83
- list: {
84
- format: 'json'
85
- }
86
- })
87
-
88
- expectAssignable<FastifyStaticOptions>({
89
- root: '',
90
- list: {
91
- format: 'json',
92
- render: () => ''
93
- }
94
- })
95
-
96
- expectAssignable<FastifyStaticOptions>({
97
- root: '',
98
- list: {
99
- format: 'html',
100
- render: () => ''
101
- }
102
- })
74
+ expect<FastifyStaticOptions>()
75
+ .type.toBeAssignableFrom({
76
+ root: '',
77
+ list: {
78
+ format: 'json' as const
79
+ }
80
+ })
103
81
 
104
- expectError<FastifyStaticOptions>({
105
- root: '',
106
- list: {
107
- format: 'html'
108
- }
109
- })
82
+ expect<FastifyStaticOptions>()
83
+ .type.toBeAssignableFrom({
84
+ root: '',
85
+ list: {
86
+ format: 'json' as const,
87
+ render: () => ''
88
+ }
89
+ })
110
90
 
111
- expectAssignable<FastifyStaticOptions>({
112
- root: ['']
113
- })
91
+ expect<FastifyStaticOptions>()
92
+ .type.toBeAssignableFrom({
93
+ root: '',
94
+ list: {
95
+ format: 'html' as const,
96
+ render: () => ''
97
+ }
98
+ })
114
99
 
115
- expectAssignable<FastifyStaticOptions>({
116
- root: new URL('')
117
- })
100
+ expect<FastifyStaticOptions>()
101
+ .type.toBeAssignableFrom({
102
+ root: ['']
103
+ })
118
104
 
119
- expectAssignable<FastifyStaticOptions>({
120
- root: [new URL('')]
121
- })
105
+ expect<FastifyStaticOptions>()
106
+ .type.toBeAssignableFrom({
107
+ root: new URL('file://')
108
+ })
122
109
 
123
- expectError<FastifyStaticOptions>({
124
- serve: true
125
- })
110
+ expect<FastifyStaticOptions>()
111
+ .type.toBeAssignableFrom({ root: [new URL('file://')] })
126
112
 
127
- expectAssignable<FastifyStaticOptions>({
128
- serve: true,
129
- root: ''
130
- })
113
+ expect<FastifyStaticOptions>()
114
+ .type.toBeAssignableFrom({
115
+ serve: true as const,
116
+ root: ''
117
+ })
131
118
 
132
- expectAssignable<FastifyStaticOptions>({
133
- serve: false
119
+ expect<FastifyStaticOptions>().type.not.toBeAssignableFrom({
120
+ serve: true as const,
134
121
  })
135
122
 
136
123
  appWithImplicitHttp
@@ -218,7 +205,7 @@ noIndexApp
218
205
  })
219
206
  })
220
207
 
221
- options.root = new URL('')
208
+ options.root = new URL('file://')
222
209
 
223
210
  const URLRootApp = fastify()
224
211
  URLRootApp.register(fastifyStatic, options)
@@ -229,7 +216,7 @@ URLRootApp.register(fastifyStatic, options)
229
216
  })
230
217
 
231
218
  const defaultIndexApp = fastify()
232
- options.index = 'index.html'
219
+ options.index = 'index.html' as const
233
220
 
234
221
  defaultIndexApp
235
222
  .register(fastifyStatic, options)