@fastify/static 6.3.0 → 6.3.1

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/index.d.ts CHANGED
@@ -8,98 +8,108 @@ import { Stats } from 'fs';
8
8
  declare module "fastify" {
9
9
  interface FastifyReply {
10
10
  sendFile(filename: string, rootPath?: string): FastifyReply;
11
- sendFile(filename: string, options?: SendOptions): FastifyReply;
12
- sendFile(filename: string, rootPath?: string, options?: SendOptions): FastifyReply;
13
- download(filepath: string, options?: SendOptions): FastifyReply;
11
+ sendFile(filename: string, options?: fastifyStatic.SendOptions): FastifyReply;
12
+ sendFile(filename: string, rootPath?: string, options?: fastifyStatic.SendOptions): FastifyReply;
13
+ download(filepath: string, options?: fastifyStatic.SendOptions): FastifyReply;
14
14
  download(filepath: string, filename?: string): FastifyReply;
15
- download(filepath: string, filename?: string, options?: SendOptions): FastifyReply;
15
+ download(filepath: string, filename?: string, options?: fastifyStatic.SendOptions): FastifyReply;
16
16
  }
17
17
  }
18
18
 
19
- interface ExtendedInformation {
20
- fileCount: number;
21
- totalFileCount: number;
22
- folderCount: number;
23
- totalFolderCount: number;
24
- totalSize: number;
25
- lastModified: number;
26
- }
27
-
28
- interface ListDir {
29
- href: string;
30
- name: string;
31
- stats: Stats;
32
- extendedInfo?: ExtendedInformation;
33
- }
19
+ type FastifyStaticPlugin = FastifyPluginCallback<NonNullable<fastifyStatic.FastifyStaticOptions>>;
34
20
 
35
- interface ListFile {
36
- href: string;
37
- name: string;
38
- stats: Stats;
39
- }
21
+ declare namespace fastifyStatic {
22
+ export interface ExtendedInformation {
23
+ fileCount: number;
24
+ totalFileCount: number;
25
+ folderCount: number;
26
+ totalFolderCount: number;
27
+ totalSize: number;
28
+ lastModified: number;
29
+ }
40
30
 
41
- interface ListRender {
42
- (dirs: ListDir[], files: ListFile[]): string;
43
- }
31
+ export interface ListDir {
32
+ href: string;
33
+ name: string;
34
+ stats: Stats;
35
+ extendedInfo?: ExtendedInformation;
36
+ }
44
37
 
45
- interface ListOptions {
46
- names?: string[];
47
- extendedFolderInfo?: boolean;
48
- }
38
+ export interface ListFile {
39
+ href: string;
40
+ name: string;
41
+ stats: Stats;
42
+ }
49
43
 
50
- interface ListOptionsJsonFormat extends ListOptions {
51
- format: 'json';
52
- jsonFormat?: 'names' | 'extended';
53
- }
44
+ export interface ListRender {
45
+ (dirs: ListDir[], files: ListFile[]): string;
46
+ }
54
47
 
55
- interface ListOptionsHtmlFormat extends ListOptions {
56
- format: 'html';
57
- render: ListRender;
58
- }
48
+ export interface ListOptions {
49
+ names?: string[];
50
+ extendedFolderInfo?: boolean;
51
+ jsonFormat?: 'names' | 'extended';
52
+ }
59
53
 
60
- // Passed on to `send`
61
- interface SendOptions {
62
- acceptRanges?: boolean;
63
- cacheControl?: boolean;
64
- dotfiles?: 'allow' | 'deny' | 'ignore';
65
- etag?: boolean;
66
- extensions?: string[];
67
- immutable?: boolean;
68
- index?: string[] | string | false;
69
- lastModified?: boolean;
70
- maxAge?: string | number;
71
- }
54
+ export interface ListOptionsJsonFormat extends ListOptions {
55
+ format: 'json';
56
+ // Required when the URL parameter `format=html` exists
57
+ render?: ListRender;
58
+ }
72
59
 
73
- export interface FastifyStaticOptions extends SendOptions {
74
- root: string | string[];
75
- prefix?: string;
76
- prefixAvoidTrailingSlash?: boolean;
77
- serve?: boolean;
78
- decorateReply?: boolean;
79
- schemaHide?: boolean;
80
- setHeaders?: (...args: any[]) => void;
81
- redirect?: boolean;
82
- wildcard?: boolean;
83
- list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
84
- allowedPath?: (pathName: string, root?: string) => boolean;
85
- /**
86
- * @description
87
- * Opt-in to looking for pre-compressed files
88
- */
89
- preCompressed?: boolean;
60
+ export interface ListOptionsHtmlFormat extends ListOptions {
61
+ format: 'html';
62
+ render: ListRender;
63
+ }
90
64
 
91
65
  // Passed on to `send`
92
- acceptRanges?: boolean;
93
- cacheControl?: boolean;
94
- dotfiles?: 'allow' | 'deny' | 'ignore';
95
- etag?: boolean;
96
- extensions?: string[];
97
- immutable?: boolean;
98
- index?: string[] | string | false;
99
- lastModified?: boolean;
100
- maxAge?: string | number;
66
+ export interface SendOptions {
67
+ acceptRanges?: boolean;
68
+ cacheControl?: boolean;
69
+ dotfiles?: 'allow' | 'deny' | 'ignore';
70
+ etag?: boolean;
71
+ extensions?: string[];
72
+ immutable?: boolean;
73
+ index?: string[] | string | false;
74
+ lastModified?: boolean;
75
+ maxAge?: string | number;
76
+ }
77
+
78
+ export interface FastifyStaticOptions extends SendOptions {
79
+ root: string | string[];
80
+ prefix?: string;
81
+ prefixAvoidTrailingSlash?: boolean;
82
+ serve?: boolean;
83
+ decorateReply?: boolean;
84
+ schemaHide?: boolean;
85
+ setHeaders?: (...args: any[]) => void;
86
+ redirect?: boolean;
87
+ wildcard?: boolean;
88
+ list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
89
+ allowedPath?: (pathName: string, root?: string) => boolean;
90
+ /**
91
+ * @description
92
+ * Opt-in to looking for pre-compressed files
93
+ */
94
+ preCompressed?: boolean;
95
+
96
+ // Passed on to `send`
97
+ acceptRanges?: boolean;
98
+ cacheControl?: boolean;
99
+ dotfiles?: 'allow' | 'deny' | 'ignore';
100
+ etag?: boolean;
101
+ extensions?: string[];
102
+ immutable?: boolean;
103
+ index?: string[] | string | false;
104
+ lastModified?: boolean;
105
+ maxAge?: string | number;
106
+ }
107
+
108
+ export const fastifyStatic: FastifyStaticPlugin;
109
+
110
+ export { fastifyStatic as default };
101
111
  }
102
112
 
103
- export declare const fastifyStatic: FastifyPluginCallback<FastifyStaticOptions>
113
+ declare function fastifyStatic(...params: Parameters<FastifyStaticPlugin>): ReturnType<FastifyStaticPlugin>;
104
114
 
105
- export default fastifyStatic;
115
+ export = fastifyStatic;
package/index.js CHANGED
@@ -9,7 +9,7 @@ const contentDisposition = require('content-disposition')
9
9
  const fp = require('fastify-plugin')
10
10
  const util = require('util')
11
11
  const globPromise = util.promisify(glob)
12
- const encodingNegotiator = require('encoding-negotiator')
12
+ const encodingNegotiator = require('@fastify/accept-negotiator')
13
13
 
14
14
  const dirList = require('./lib/dirList')
15
15
 
@@ -195,7 +195,7 @@ async function fastifyStatic (fastify, opts) {
195
195
 
196
196
  stream.on('error', function (err) {
197
197
  if (err.code === 'ENOENT') {
198
- // when preCompress is enabled and the path is a directoy without a trailing shash
198
+ // when preCompress is enabled and the path is a directory without a trailing slash
199
199
  if (opts.preCompressed && encoding) {
200
200
  const indexPathname = findIndexFile(pathname, options.root, options.index)
201
201
  if (indexPathname) {
@@ -494,7 +494,7 @@ function getEncodingExtension (encoding) {
494
494
 
495
495
  function getRedirectUrl (url) {
496
496
  let i = 0
497
- // we detech how many slash before a valid path
497
+ // we detect how many slash before a valid path
498
498
  for (i; i < url.length; i++) {
499
499
  if (url[i] !== '/' && url[i] !== '\\') break
500
500
  }
package/lib/dirList.js CHANGED
@@ -104,6 +104,10 @@ const dirList = {
104
104
  * @param {string} dotfiles
105
105
  */
106
106
  send: async function ({ reply, dir, options, route, prefix, dotfiles }) {
107
+ if (reply.request.query.format === 'html' && typeof options.render !== 'function') {
108
+ throw new Error('The `list.render` option must be a function and is required with the URL parameter `format=html`')
109
+ }
110
+
107
111
  let entries
108
112
  try {
109
113
  entries = await dirList.list(dir, options, dotfiles)
@@ -200,9 +204,6 @@ const dirList = {
200
204
  if (options.list.format === 'html' && typeof options.list.render !== 'function') {
201
205
  return new TypeError('The `list.render` option must be a function and is required with html format')
202
206
  }
203
- if (options.list.format === 'html' && options.list.jsonFormat != null) {
204
- return new TypeError('The `list.jsonFormat` option must be with json format')
205
- }
206
207
  }
207
208
 
208
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/static",
3
- "version": "6.3.0",
3
+ "version": "6.3.1",
4
4
  "description": "Plugin for serving static files as fast as possible.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -30,16 +30,16 @@
30
30
  "homepage": "https://github.com/fastify/fastify-static",
31
31
  "dependencies": {
32
32
  "content-disposition": "^0.5.3",
33
- "encoding-negotiator": "^2.0.1",
34
- "fastify-plugin": "^3.0.0",
33
+ "@fastify/accept-negotiator": "^1.0.0",
34
+ "fastify-plugin": "^4.0.0",
35
35
  "glob": "^8.0.1",
36
36
  "p-limit": "^3.1.0",
37
- "readable-stream": "^3.4.0",
37
+ "readable-stream": "^4.0.0",
38
38
  "send": "^0.18.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@fastify/compress": "^6.0.0",
42
- "@types/node": "^17.0.0",
42
+ "@types/node": "^18.0.0",
43
43
  "@typescript-eslint/eslint-plugin": "^2.29.0",
44
44
  "@typescript-eslint/parser": "^2.29.0",
45
45
  "concat-stream": "^2.0.0",
@@ -53,7 +53,7 @@
53
53
  "snazzy": "^9.0.0",
54
54
  "standard": "^17.0.0",
55
55
  "tap": "^16.0.0",
56
- "tsd": "^0.20.0",
56
+ "tsd": "^0.22.0",
57
57
  "typescript": "^4.0.2"
58
58
  },
59
59
  "tsd": {
@@ -72,14 +72,6 @@ t.test('throws when `list.format` is html and `list render` is not a function',
72
72
  t.equal(err.message, 'The `list.render` option must be a function and is required with html format')
73
73
  })
74
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
-
83
75
  t.test('dir list wrong options', t => {
84
76
  t.plan(3)
85
77
 
@@ -485,7 +477,103 @@ t.test('dir list json format - extended info', t => {
485
477
  })
486
478
  })
487
479
 
488
- t.test('dir list - url parameter format', t => {
480
+ t.test('json format with url parameter format', t => {
481
+ t.plan(13)
482
+
483
+ const options = {
484
+ root: path.join(__dirname, '/static'),
485
+ prefix: '/public',
486
+ index: false,
487
+ list: {
488
+ format: 'json',
489
+ render (dirs, files) {
490
+ return 'html'
491
+ }
492
+ }
493
+ }
494
+ const route = '/public/'
495
+ const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
496
+
497
+ helper.arrange(t, options, (url) => {
498
+ simple.concat({
499
+ method: 'GET',
500
+ url: url + route
501
+ }, (err, response, body) => {
502
+ t.error(err)
503
+ t.equal(response.statusCode, 200)
504
+ t.equal(body.toString(), JSON.stringify(jsonContent))
505
+ t.ok(response.headers['content-type'].includes('application/json'))
506
+ })
507
+
508
+ simple.concat({
509
+ method: 'GET',
510
+ url: url + route + '?format=html'
511
+ }, (err, response, body) => {
512
+ t.error(err)
513
+ t.equal(response.statusCode, 200)
514
+ t.equal(body.toString(), 'html')
515
+ t.ok(response.headers['content-type'].includes('text/html'))
516
+ })
517
+
518
+ simple.concat({
519
+ method: 'GET',
520
+ url: url + route + '?format=json'
521
+ }, (err, response, body) => {
522
+ t.error(err)
523
+ t.equal(response.statusCode, 200)
524
+ t.equal(body.toString(), JSON.stringify(jsonContent))
525
+ t.ok(response.headers['content-type'].includes('application/json'))
526
+ })
527
+ })
528
+ })
529
+
530
+ t.test('json format with url parameter format and without render option', t => {
531
+ t.plan(12)
532
+
533
+ const options = {
534
+ root: path.join(__dirname, '/static'),
535
+ prefix: '/public',
536
+ index: false,
537
+ list: {
538
+ format: 'json'
539
+ }
540
+ }
541
+ const route = '/public/'
542
+ const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
543
+
544
+ helper.arrange(t, options, (url) => {
545
+ simple.concat({
546
+ method: 'GET',
547
+ url: url + route
548
+ }, (err, response, body) => {
549
+ t.error(err)
550
+ t.equal(response.statusCode, 200)
551
+ t.equal(body.toString(), JSON.stringify(jsonContent))
552
+ t.ok(response.headers['content-type'].includes('application/json'))
553
+ })
554
+
555
+ simple.concat({
556
+ method: 'GET',
557
+ url: url + route + '?format=html'
558
+ }, (err, response, body) => {
559
+ t.error(err)
560
+ t.equal(response.statusCode, 500)
561
+ t.equal(JSON.parse(body.toString()).message, 'The `list.render` option must be a function and is required with the URL parameter `format=html`')
562
+ })
563
+
564
+ simple.concat({
565
+ method: 'GET',
566
+ url: url + route + '?format=json'
567
+ }, (err, response, body) => {
568
+ t.error(err)
569
+ t.equal(response.statusCode, 200)
570
+ t.equal(body.toString(), JSON.stringify(jsonContent))
571
+ t.ok(response.headers['content-type'].includes('application/json'))
572
+ })
573
+ })
574
+ })
575
+
576
+ t.test('html format with url parameter format', t => {
489
577
  t.plan(13)
490
578
 
491
579
  const options = {
@@ -500,6 +588,7 @@ t.test('dir list - url parameter format', t => {
500
588
  }
501
589
  }
502
590
  const route = '/public/'
591
+ const jsonContent = { dirs: ['deep', 'shallow'], files: ['.example', 'a .md', 'foo.html', 'foobar.html', 'index.css', 'index.html'] }
503
592
 
504
593
  helper.arrange(t, options, (url) => {
505
594
  simple.concat({
@@ -528,7 +617,7 @@ t.test('dir list - url parameter format', t => {
528
617
  }, (err, response, body) => {
529
618
  t.error(err)
530
619
  t.equal(response.statusCode, 200)
531
- t.ok(body.toString())
620
+ t.equal(body.toString(), JSON.stringify(jsonContent))
532
621
  t.ok(response.headers['content-type'].includes('application/json'))
533
622
  })
534
623
  })
@@ -1642,7 +1642,7 @@ t.test('with fastify-compress', t => {
1642
1642
  })
1643
1643
  })
1644
1644
  t.test('register /static/ with schemaHide true', t => {
1645
- t.plan(5)
1645
+ t.plan(4)
1646
1646
 
1647
1647
  const pluginOptions = {
1648
1648
  root: path.join(__dirname, '/static'),
@@ -1682,7 +1682,7 @@ t.test('register /static/ with schemaHide true', t => {
1682
1682
  })
1683
1683
 
1684
1684
  t.test('register /static/ with schemaHide false', t => {
1685
- t.plan(5)
1685
+ t.plan(4)
1686
1686
 
1687
1687
  const pluginOptions = {
1688
1688
  root: path.join(__dirname, '/static'),
@@ -1722,7 +1722,7 @@ t.test('register /static/ with schemaHide false', t => {
1722
1722
  })
1723
1723
 
1724
1724
  t.test('register /static/ without schemaHide', t => {
1725
- t.plan(5)
1725
+ t.plan(4)
1726
1726
 
1727
1727
  const pluginOptions = {
1728
1728
  root: path.join(__dirname, '/static'),
@@ -2769,7 +2769,7 @@ t.test('inject support', async (t) => {
2769
2769
  })
2770
2770
 
2771
2771
  t.test('routes should use custom errorHandler premature stream close', t => {
2772
- t.plan(5)
2772
+ t.plan(4)
2773
2773
 
2774
2774
  const pluginOptions = {
2775
2775
  root: path.join(__dirname, '/static'),
@@ -2804,7 +2804,7 @@ t.test('routes should use custom errorHandler premature stream close', t => {
2804
2804
  })
2805
2805
 
2806
2806
  t.test('routes should fallback to default errorHandler', t => {
2807
- t.plan(5)
2807
+ t.plan(4)
2808
2808
 
2809
2809
  const pluginOptions = {
2810
2810
  root: path.join(__dirname, '/static'),
@@ -2840,7 +2840,7 @@ t.test('routes should fallback to default errorHandler', t => {
2840
2840
  })
2841
2841
  })
2842
2842
 
2843
- t.test('precent encoded URLs in glob mode', (t) => {
2843
+ t.test('percent encoded URLs in glob mode', (t) => {
2844
2844
  t.plan(4)
2845
2845
 
2846
2846
  const fastify = Fastify({})
@@ -1,6 +1,34 @@
1
- import fastify from 'fastify'
2
- import { expectAssignable, expectError } from 'tsd'
3
- import fastifyStatic, { FastifyStaticOptions } from '../..'
1
+ import fastify, { FastifyInstance, FastifyPluginCallback } from 'fastify'
2
+ import { Server } from 'http';
3
+ import { expectAssignable, expectError, expectType } from 'tsd'
4
+ import * as fastifyStaticStar from '../..';
5
+ import fastifyStatic, {
6
+ FastifyStaticOptions,
7
+ fastifyStatic as fastifyStaticNamed,
8
+ } from '../..'
9
+
10
+ import fastifyStaticCjsImport = require('../..');
11
+ const fastifyStaticCjs = require('../..');
12
+
13
+ const app: FastifyInstance = fastify();
14
+
15
+ app.register(fastifyStatic);
16
+ app.register(fastifyStaticNamed);
17
+ app.register(fastifyStaticCjs);
18
+ app.register(fastifyStaticCjsImport.default);
19
+ app.register(fastifyStaticCjsImport.fastifyStatic);
20
+ app.register(fastifyStaticStar.default);
21
+ app.register(fastifyStaticStar.fastifyStatic);
22
+
23
+ expectType<FastifyPluginCallback<FastifyStaticOptions, Server>>(fastifyStatic);
24
+ expectType<FastifyPluginCallback<FastifyStaticOptions, Server>>(fastifyStaticNamed);
25
+ expectType<FastifyPluginCallback<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.default);
26
+ expectType<FastifyPluginCallback<FastifyStaticOptions, Server>>(fastifyStaticCjsImport.fastifyStatic);
27
+ expectType<FastifyPluginCallback<FastifyStaticOptions, Server>>(fastifyStaticStar.default);
28
+ expectType<FastifyPluginCallback<FastifyStaticOptions, Server>>(
29
+ fastifyStaticStar.fastifyStatic
30
+ );
31
+ expectType<any>(fastifyStaticCjs);
4
32
 
5
33
  const appWithImplicitHttp = fastify()
6
34
  const options: FastifyStaticOptions = {
@@ -39,7 +67,7 @@ expectAssignable<FastifyStaticOptions>({
39
67
  }
40
68
  })
41
69
 
42
- expectError<FastifyStaticOptions>({
70
+ expectAssignable<FastifyStaticOptions>({
43
71
  root: '',
44
72
  list: {
45
73
  format: 'json',