@fastify/static 9.1.3 → 9.2.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 +2 -5
- package/.github/dependabot.yml +42 -2
- package/README.md +5 -1
- package/index.js +1 -1
- package/package.json +5 -8
- package/test/static.test.js +37 -0
- package/types/index.d.ts +18 -13
- package/types/{index.test-d.ts → index.tst.ts} +59 -72
package/.gitattributes
CHANGED
package/.github/dependabot.yml
CHANGED
|
@@ -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: "
|
|
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: "
|
|
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
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/static",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.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": "
|
|
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"
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"glob": "^13.0.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@fastify/compress": "^
|
|
71
|
-
"@types/node": "^
|
|
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
|
-
"
|
|
82
|
-
},
|
|
83
|
-
"tsd": {
|
|
84
|
-
"directory": "test/types"
|
|
81
|
+
"tstyche": "^7.0.0"
|
|
85
82
|
},
|
|
86
83
|
"publishConfig": {
|
|
87
84
|
"access": "public"
|
package/test/static.test.js
CHANGED
|
@@ -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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
&
|
|
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?: (
|
|
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 {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
59
|
+
expect(path).type.toBe<string>()
|
|
62
60
|
|
|
63
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
82
|
+
expect<FastifyStaticOptions>()
|
|
83
|
+
.type.toBeAssignableFrom({
|
|
84
|
+
root: '',
|
|
85
|
+
list: {
|
|
86
|
+
format: 'json' as const,
|
|
87
|
+
render: () => ''
|
|
88
|
+
}
|
|
89
|
+
})
|
|
110
90
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
91
|
+
expect<FastifyStaticOptions>()
|
|
92
|
+
.type.toBeAssignableFrom({
|
|
93
|
+
root: '',
|
|
94
|
+
list: {
|
|
95
|
+
format: 'html' as const,
|
|
96
|
+
render: () => ''
|
|
97
|
+
}
|
|
98
|
+
})
|
|
114
99
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
expect<FastifyStaticOptions>()
|
|
101
|
+
.type.toBeAssignableFrom({
|
|
102
|
+
root: ['']
|
|
103
|
+
})
|
|
118
104
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
expect<FastifyStaticOptions>()
|
|
106
|
+
.type.toBeAssignableFrom({
|
|
107
|
+
root: new URL('file://')
|
|
108
|
+
})
|
|
122
109
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
})
|
|
110
|
+
expect<FastifyStaticOptions>()
|
|
111
|
+
.type.toBeAssignableFrom({ root: [new URL('file://')] })
|
|
126
112
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
expect<FastifyStaticOptions>()
|
|
114
|
+
.type.toBeAssignableFrom({
|
|
115
|
+
serve: true as const,
|
|
116
|
+
root: ''
|
|
117
|
+
})
|
|
131
118
|
|
|
132
|
-
|
|
133
|
-
serve:
|
|
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)
|