@fastify/static 6.0.0 → 6.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/README.md +3 -2
- package/index.d.ts +11 -4
- package/index.js +7 -3
- package/lib/dirList.js +18 -7
- package/package.json +2 -2
- package/test/dir-list.test.js +101 -1
- package/test/static-dotfiles/.aaa +0 -0
- package/test/static-dotfiles/dir/index.html +0 -0
- package/test/static-dotfiles/test.txt +0 -0
- package/test/types/index.ts +31 -1
package/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
[](https://www.npmjs.com/package/@fastify/static)
|
|
5
|
-
[](https://snyk.io/test/github/fastify/fastify-static)
|
|
6
5
|
[](https://standardjs.com/)
|
|
7
6
|
|
|
8
7
|
Plugin for serving static files as fast as possible. Supports Fastify version `3.x`.
|
|
@@ -191,7 +190,9 @@ If set, it provide the directory list calling the directory path.
|
|
|
191
190
|
|
|
192
191
|
Default response is json.
|
|
193
192
|
|
|
194
|
-
Note:
|
|
193
|
+
Note:
|
|
194
|
+
- Multi-root is not supported within the `list` option.
|
|
195
|
+
- If `dotfiles` option value is `deny` or `ignore`, dotfiles will be excluded.
|
|
195
196
|
|
|
196
197
|
**Example:**
|
|
197
198
|
|
package/index.d.ts
CHANGED
|
@@ -43,13 +43,20 @@ interface ListRender {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
interface ListOptions {
|
|
46
|
-
|
|
47
|
-
names: string[];
|
|
48
|
-
render: ListRender;
|
|
46
|
+
names?: string[];
|
|
49
47
|
extendedFolderInfo?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ListOptionsJsonFormat extends ListOptions {
|
|
51
|
+
format: 'json';
|
|
50
52
|
jsonFormat?: 'names' | 'extended';
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
interface ListOptionsHtmlFormat extends ListOptions {
|
|
56
|
+
format: 'html';
|
|
57
|
+
render: ListRender;
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
// Passed on to `send`
|
|
54
61
|
interface SendOptions {
|
|
55
62
|
acceptRanges?: boolean;
|
|
@@ -73,7 +80,7 @@ export interface FastifyStaticOptions extends SendOptions {
|
|
|
73
80
|
setHeaders?: (...args: any[]) => void;
|
|
74
81
|
redirect?: boolean;
|
|
75
82
|
wildcard?: boolean;
|
|
76
|
-
list?: boolean |
|
|
83
|
+
list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
|
|
77
84
|
allowedPath?: (pathName: string, root?: string) => boolean;
|
|
78
85
|
/**
|
|
79
86
|
* @description
|
package/index.js
CHANGED
|
@@ -161,7 +161,8 @@ async function fastifyStatic (fastify, opts) {
|
|
|
161
161
|
dir: path,
|
|
162
162
|
options: opts.list,
|
|
163
163
|
route: pathname,
|
|
164
|
-
prefix
|
|
164
|
+
prefix,
|
|
165
|
+
dotfiles: opts.dotfiles
|
|
165
166
|
}).catch((err) => reply.send(err))
|
|
166
167
|
return
|
|
167
168
|
}
|
|
@@ -217,7 +218,8 @@ async function fastifyStatic (fastify, opts) {
|
|
|
217
218
|
dir: dirList.path(opts.root, pathname),
|
|
218
219
|
options: opts.list,
|
|
219
220
|
route: pathname,
|
|
220
|
-
prefix
|
|
221
|
+
prefix,
|
|
222
|
+
dotfiles: opts.dotfiles
|
|
221
223
|
}).catch((err) => reply.send(err))
|
|
222
224
|
return
|
|
223
225
|
}
|
|
@@ -331,8 +333,10 @@ async function fastifyStatic (fastify, opts) {
|
|
|
331
333
|
const indexDirs = new Map()
|
|
332
334
|
const routes = new Set()
|
|
333
335
|
|
|
336
|
+
const winSeparatorRegex = new RegExp(`\\${path.win32.sep}`, 'g')
|
|
337
|
+
|
|
334
338
|
for (const rootPath of Array.isArray(sendOptions.root) ? sendOptions.root : [sendOptions.root]) {
|
|
335
|
-
const files = await globPromise(path.join(rootPath, globPattern), { nodir: true })
|
|
339
|
+
const files = await globPromise(path.join(rootPath, globPattern).replace(winSeparatorRegex, path.posix.sep), { nodir: true })
|
|
336
340
|
const indexes = typeof opts.index === 'undefined' ? ['index.html'] : [].concat(opts.index)
|
|
337
341
|
|
|
338
342
|
for (let file of files) {
|
package/lib/dirList.js
CHANGED
|
@@ -8,12 +8,16 @@ const dirList = {
|
|
|
8
8
|
/**
|
|
9
9
|
* get files and dirs from dir, or error
|
|
10
10
|
* @param {string} dir full path fs dir
|
|
11
|
-
* @param {
|
|
11
|
+
* @param {(boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat)} options
|
|
12
|
+
* @param {string} dotfiles
|
|
12
13
|
* note: can't use glob because don't get error on non existing dir
|
|
13
14
|
*/
|
|
14
|
-
list: async function (dir, options) {
|
|
15
|
+
list: async function (dir, options, dotfiles) {
|
|
15
16
|
const entries = { dirs: [], files: [] }
|
|
16
|
-
|
|
17
|
+
let files = await fs.readdir(dir)
|
|
18
|
+
if (dotfiles === 'deny' || dotfiles === 'ignore') {
|
|
19
|
+
files = files.filter(f => f.charAt(0) !== '.')
|
|
20
|
+
}
|
|
17
21
|
if (files.length < 1) {
|
|
18
22
|
return entries
|
|
19
23
|
}
|
|
@@ -95,13 +99,14 @@ const dirList = {
|
|
|
95
99
|
* send dir list content, or 404 on error
|
|
96
100
|
* @param {Fastify.Reply} reply
|
|
97
101
|
* @param {string} dir full path fs dir
|
|
98
|
-
* @param {
|
|
102
|
+
* @param {(boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat)} options
|
|
99
103
|
* @param {string} route request route
|
|
104
|
+
* @param {string} dotfiles
|
|
100
105
|
*/
|
|
101
|
-
send: async function ({ reply, dir, options, route, prefix }) {
|
|
106
|
+
send: async function ({ reply, dir, options, route, prefix, dotfiles }) {
|
|
102
107
|
let entries
|
|
103
108
|
try {
|
|
104
|
-
entries = await dirList.list(dir, options)
|
|
109
|
+
entries = await dirList.list(dir, options, dotfiles)
|
|
105
110
|
} catch (error) {
|
|
106
111
|
return reply.callNotFound()
|
|
107
112
|
}
|
|
@@ -146,7 +151,7 @@ const dirList = {
|
|
|
146
151
|
/**
|
|
147
152
|
* say if the route can be handled by dir list or not
|
|
148
153
|
* @param {string} route request route
|
|
149
|
-
* @param {
|
|
154
|
+
* @param {(boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat)} options
|
|
150
155
|
* @return {boolean}
|
|
151
156
|
*/
|
|
152
157
|
handle: function (route, options) {
|
|
@@ -189,9 +194,15 @@ const dirList = {
|
|
|
189
194
|
if (options.list.names && !Array.isArray(options.list.names)) {
|
|
190
195
|
return new TypeError('The `list.names` option must be an array')
|
|
191
196
|
}
|
|
197
|
+
if (options.list.jsonFormat != null && options.list.jsonFormat !== 'names' && options.list.jsonFormat !== 'extended') {
|
|
198
|
+
return new TypeError('The `list.jsonFormat` option must be name or extended')
|
|
199
|
+
}
|
|
192
200
|
if (options.list.format === 'html' && typeof options.list.render !== 'function') {
|
|
193
201
|
return new TypeError('The `list.render` option must be a function and is required with html format')
|
|
194
202
|
}
|
|
203
|
+
if (options.list.format === 'html' && options.list.jsonFormat != null) {
|
|
204
|
+
return new TypeError('The `list.jsonFormat` option must be with json format')
|
|
205
|
+
}
|
|
195
206
|
}
|
|
196
207
|
|
|
197
208
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/static",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Plugin for serving static files as fast as possible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"content-disposition": "^0.5.3",
|
|
33
33
|
"encoding-negotiator": "^2.0.1",
|
|
34
34
|
"fastify-plugin": "^3.0.0",
|
|
35
|
-
"glob": "^
|
|
35
|
+
"glob": "^8.0.1",
|
|
36
36
|
"p-limit": "^3.1.0",
|
|
37
37
|
"readable-stream": "^3.4.0",
|
|
38
38
|
"send": "^0.18.0"
|
package/test/dir-list.test.js
CHANGED
|
@@ -40,7 +40,7 @@ t.test('throws when `root` is an array', t => {
|
|
|
40
40
|
t.equal(err.message, 'multi-root with list option is not supported')
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
t.test('throws when `list.format option
|
|
43
|
+
t.test('throws when `list.format` option is invalid', t => {
|
|
44
44
|
t.plan(2)
|
|
45
45
|
|
|
46
46
|
const err = dirList.validateOptions({ list: { format: 'hello' } })
|
|
@@ -56,6 +56,14 @@ t.test('throws when `list.names option` is not an array', t => {
|
|
|
56
56
|
t.equal(err.message, 'The `list.names` option must be an array')
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
+
t.test('throws when `list.jsonFormat` option is invalid', t => {
|
|
60
|
+
t.plan(2)
|
|
61
|
+
|
|
62
|
+
const err = dirList.validateOptions({ list: { jsonFormat: 'hello' } })
|
|
63
|
+
t.type(err, TypeError)
|
|
64
|
+
t.equal(err.message, 'The `list.jsonFormat` option must be name or extended')
|
|
65
|
+
})
|
|
66
|
+
|
|
59
67
|
t.test('throws when `list.format` is html and `list render` is not a function', t => {
|
|
60
68
|
t.plan(2)
|
|
61
69
|
|
|
@@ -64,6 +72,14 @@ t.test('throws when `list.format` is html and `list render` is not a function',
|
|
|
64
72
|
t.equal(err.message, 'The `list.render` option must be a function and is required with html format')
|
|
65
73
|
})
|
|
66
74
|
|
|
75
|
+
t.test('throws when `list.format` is html and `list.jsonFormat` is given', t => {
|
|
76
|
+
t.plan(2)
|
|
77
|
+
|
|
78
|
+
const err = dirList.validateOptions({ list: { format: 'html', render: () => '', jsonFormat: 'extended' } })
|
|
79
|
+
t.type(err, TypeError)
|
|
80
|
+
t.equal(err.message, 'The `list.jsonFormat` option must be with json format')
|
|
81
|
+
})
|
|
82
|
+
|
|
67
83
|
t.test('dir list wrong options', t => {
|
|
68
84
|
t.plan(3)
|
|
69
85
|
|
|
@@ -636,6 +652,90 @@ t.test('serve a non existent dir and get error', t => {
|
|
|
636
652
|
})
|
|
637
653
|
})
|
|
638
654
|
|
|
655
|
+
t.test('dir list with dotfiles allow option', t => {
|
|
656
|
+
t.plan(2)
|
|
657
|
+
|
|
658
|
+
const options = {
|
|
659
|
+
root: path.join(__dirname, '/static-dotfiles'),
|
|
660
|
+
prefix: '/public',
|
|
661
|
+
dotfiles: 'allow',
|
|
662
|
+
index: false,
|
|
663
|
+
list: true
|
|
664
|
+
}
|
|
665
|
+
const route = '/public/'
|
|
666
|
+
const content = { dirs: ['dir'], files: ['.aaa', 'test.txt'] }
|
|
667
|
+
|
|
668
|
+
helper.arrange(t, options, (url) => {
|
|
669
|
+
t.test(route, t => {
|
|
670
|
+
t.plan(3)
|
|
671
|
+
simple.concat({
|
|
672
|
+
method: 'GET',
|
|
673
|
+
url: url + route
|
|
674
|
+
}, (err, response, body) => {
|
|
675
|
+
t.error(err)
|
|
676
|
+
t.equal(response.statusCode, 200)
|
|
677
|
+
t.equal(body.toString(), JSON.stringify(content))
|
|
678
|
+
})
|
|
679
|
+
})
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
|
|
683
|
+
t.test('dir list with dotfiles deny option', t => {
|
|
684
|
+
t.plan(2)
|
|
685
|
+
|
|
686
|
+
const options = {
|
|
687
|
+
root: path.join(__dirname, '/static-dotfiles'),
|
|
688
|
+
prefix: '/public',
|
|
689
|
+
dotfiles: 'deny',
|
|
690
|
+
index: false,
|
|
691
|
+
list: true
|
|
692
|
+
}
|
|
693
|
+
const route = '/public/'
|
|
694
|
+
const content = { dirs: ['dir'], files: ['test.txt'] }
|
|
695
|
+
|
|
696
|
+
helper.arrange(t, options, (url) => {
|
|
697
|
+
t.test(route, t => {
|
|
698
|
+
t.plan(3)
|
|
699
|
+
simple.concat({
|
|
700
|
+
method: 'GET',
|
|
701
|
+
url: url + route
|
|
702
|
+
}, (err, response, body) => {
|
|
703
|
+
t.error(err)
|
|
704
|
+
t.equal(response.statusCode, 200)
|
|
705
|
+
t.equal(body.toString(), JSON.stringify(content))
|
|
706
|
+
})
|
|
707
|
+
})
|
|
708
|
+
})
|
|
709
|
+
})
|
|
710
|
+
|
|
711
|
+
t.test('dir list with dotfiles ignore option', t => {
|
|
712
|
+
t.plan(2)
|
|
713
|
+
|
|
714
|
+
const options = {
|
|
715
|
+
root: path.join(__dirname, '/static-dotfiles'),
|
|
716
|
+
prefix: '/public',
|
|
717
|
+
dotfiles: 'ignore',
|
|
718
|
+
index: false,
|
|
719
|
+
list: true
|
|
720
|
+
}
|
|
721
|
+
const route = '/public/'
|
|
722
|
+
const content = { dirs: ['dir'], files: ['test.txt'] }
|
|
723
|
+
|
|
724
|
+
helper.arrange(t, options, (url) => {
|
|
725
|
+
t.test(route, t => {
|
|
726
|
+
t.plan(3)
|
|
727
|
+
simple.concat({
|
|
728
|
+
method: 'GET',
|
|
729
|
+
url: url + route
|
|
730
|
+
}, (err, response, body) => {
|
|
731
|
+
t.error(err)
|
|
732
|
+
t.equal(response.statusCode, 200)
|
|
733
|
+
t.equal(body.toString(), JSON.stringify(content))
|
|
734
|
+
})
|
|
735
|
+
})
|
|
736
|
+
})
|
|
737
|
+
})
|
|
738
|
+
|
|
639
739
|
t.test('dir list error', t => {
|
|
640
740
|
t.plan(7)
|
|
641
741
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/types/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fastify from 'fastify'
|
|
2
|
-
import { expectError } from 'tsd'
|
|
2
|
+
import { expectAssignable, expectError } from 'tsd'
|
|
3
3
|
import fastifyStatic, { FastifyStaticOptions } from '../..'
|
|
4
4
|
|
|
5
5
|
const appWithImplicitHttp = fastify()
|
|
@@ -32,6 +32,36 @@ expectError<FastifyStaticOptions>({
|
|
|
32
32
|
wildcard: '**/**'
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
+
expectAssignable<FastifyStaticOptions>({
|
|
36
|
+
root: '',
|
|
37
|
+
list: {
|
|
38
|
+
format: 'json',
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expectError<FastifyStaticOptions>({
|
|
43
|
+
root: '',
|
|
44
|
+
list: {
|
|
45
|
+
format: 'json',
|
|
46
|
+
render: () => ''
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expectAssignable<FastifyStaticOptions>({
|
|
51
|
+
root: '',
|
|
52
|
+
list: {
|
|
53
|
+
format: 'html',
|
|
54
|
+
render: () => ''
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
expectError<FastifyStaticOptions>({
|
|
59
|
+
root: '',
|
|
60
|
+
list: {
|
|
61
|
+
format: 'html',
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
35
65
|
appWithImplicitHttp
|
|
36
66
|
.register(fastifyStatic, options)
|
|
37
67
|
.after(() => {
|