koa-classic-server 2.6.1 → 3.0.0-alpha.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.
Files changed (40) hide show
  1. package/README.md +68 -10
  2. package/__tests__/caching-headers.test.js +30 -30
  3. package/__tests__/compression-fixtures/data.json +1 -0
  4. package/__tests__/compression-fixtures/large.txt +1 -0
  5. package/__tests__/compression-fixtures/small.txt +1 -0
  6. package/__tests__/compression.test.js +270 -0
  7. package/__tests__/customTest/serversToLoad.util.js +1 -1
  8. package/__tests__/deprecation-warnings.test.js +71 -183
  9. package/__tests__/dt-unknown.test.js +20 -9
  10. package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
  11. package/__tests__/hidden-fixtures/.env +2 -0
  12. package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
  13. package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
  14. package/__tests__/hidden-fixtures/data.key +1 -0
  15. package/__tests__/hidden-fixtures/file.secret +1 -0
  16. package/__tests__/hidden-fixtures/index.html +1 -0
  17. package/__tests__/hidden-fixtures/normal.txt +1 -0
  18. package/__tests__/hidden-fixtures/subdir/.env +1 -0
  19. package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
  20. package/__tests__/hidden-option.test.js +422 -0
  21. package/__tests__/index-option.test.js +18 -16
  22. package/__tests__/index.test.js +8 -4
  23. package/__tests__/range-fixtures/sample.txt +1 -0
  24. package/__tests__/range.test.js +223 -0
  25. package/__tests__/security-headers.test.js +153 -0
  26. package/__tests__/security.test.js +145 -159
  27. package/__tests__/server-cache-fixtures/large.txt +1 -0
  28. package/__tests__/server-cache-fixtures/small.txt +1 -0
  29. package/__tests__/server-cache.test.js +423 -0
  30. package/__tests__/symlink.test.js +8 -5
  31. package/docs/ACTION_PLAN.md +293 -0
  32. package/docs/CHANGELOG.md +84 -0
  33. package/docs/EXAMPLES_INDEX_OPTION.md +2 -2
  34. package/docs/FLOW_DIAGRAM.md +13 -13
  35. package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
  36. package/docs/PERFORMANCE_COMPARISON.md +7 -7
  37. package/eslint.config.mjs +17 -0
  38. package/index.cjs +1096 -391
  39. package/index.mjs +1 -5
  40. package/package.json +4 -1
@@ -1,217 +1,105 @@
1
1
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2
2
  //
3
- // TEST FOR DEPRECATED OPTION NAMES (enableCaching, cacheMaxAge)
4
- // This test verifies backward compatibility and deprecation warnings
3
+ // TEST FOR REMOVED OPTION NAMES (enableCaching, cacheMaxAge)
4
+ // These options were removed in v3.0.0 passing them must throw an Error.
5
5
  //
6
6
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7
7
 
8
- const supertest = require('supertest');
9
8
  const koaClassicServer = require('../index.cjs');
10
9
  const Koa = require('koa');
11
10
  const path = require('path');
12
11
 
13
12
  const rootDir = path.join(__dirname, 'publicWwwTest');
14
13
 
15
- describe('Deprecated option names (backward compatibility)', () => {
14
+ describe('Removed option names (v3.0.0 breaking changes)', () => {
16
15
 
17
- describe('Using deprecated enableCaching option', () => {
18
- let app;
19
- let server;
20
- let consoleWarnSpy;
21
-
22
- beforeAll(() => {
23
- // Spy on console.warn to capture deprecation warnings
24
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
25
-
26
- app = new Koa();
27
-
28
- // Use deprecated option name
29
- app.use(koaClassicServer(rootDir, {
30
- enableCaching: true // DEPRECATED: should use browserCacheEnabled
31
- }));
32
-
33
- server = app.listen();
34
- });
35
-
36
- afterAll(() => {
37
- server.close();
38
- consoleWarnSpy.mockRestore();
39
- });
40
-
41
- test('should display deprecation warning for enableCaching', () => {
42
- expect(consoleWarnSpy).toHaveBeenCalled();
43
- const warningMessage = consoleWarnSpy.mock.calls[0][1];
44
- expect(warningMessage).toContain('DEPRECATION WARNING');
45
- expect(warningMessage).toContain('enableCaching');
46
- expect(warningMessage).toContain('browserCacheEnabled');
47
- });
48
-
49
- test('should still work with deprecated option', async () => {
50
- const response = await supertest(server).get('/test-page.html');
51
-
52
- // Should return the file
53
- expect(response.status).toBe(200);
54
-
55
- // Should have caching headers (because enableCaching: true was set)
56
- expect(response.headers['etag']).toBeDefined();
57
- expect(response.headers['last-modified']).toBeDefined();
58
- expect(response.headers['cache-control']).toContain('public');
59
- });
16
+ test('should throw when "enableCaching" is passed', () => {
17
+ expect(() => {
18
+ const app = new Koa();
19
+ app.use(koaClassicServer(rootDir, { enableCaching: true }));
20
+ }).toThrow('"enableCaching" option was removed in v3.0.0');
60
21
  });
61
22
 
62
- describe('Using deprecated cacheMaxAge option', () => {
63
- let app;
64
- let server;
65
- let consoleWarnSpy;
66
-
67
- beforeAll(() => {
68
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
69
-
70
- app = new Koa();
71
-
72
- // Use deprecated option name
73
- app.use(koaClassicServer(rootDir, {
74
- enableCaching: true, // Also deprecated
75
- cacheMaxAge: 7200 // DEPRECATED: should use browserCacheMaxAge
76
- }));
77
-
78
- server = app.listen();
79
- });
80
-
81
- afterAll(() => {
82
- server.close();
83
- consoleWarnSpy.mockRestore();
84
- });
85
-
86
- test('should display deprecation warning for cacheMaxAge', () => {
87
- const warningCalls = consoleWarnSpy.mock.calls;
88
- const cacheMaxAgeWarning = warningCalls.find(call =>
89
- call[1] && call[1].includes('cacheMaxAge')
90
- );
91
-
92
- expect(cacheMaxAgeWarning).toBeDefined();
93
- expect(cacheMaxAgeWarning[1]).toContain('DEPRECATION WARNING');
94
- expect(cacheMaxAgeWarning[1]).toContain('browserCacheMaxAge');
95
- });
96
-
97
- test('should use the deprecated cacheMaxAge value', async () => {
98
- const response = await supertest(server).get('/test-page.html');
99
-
100
- expect(response.status).toBe(200);
101
- expect(response.headers['cache-control']).toContain('max-age=7200');
102
- });
23
+ test('should throw when "cacheMaxAge" is passed', () => {
24
+ expect(() => {
25
+ const app = new Koa();
26
+ app.use(koaClassicServer(rootDir, { cacheMaxAge: 3600 }));
27
+ }).toThrow('"cacheMaxAge" option was removed in v3.0.0');
103
28
  });
104
29
 
105
- describe('Using new option names (no warnings)', () => {
106
- let app;
107
- let server;
108
- let consoleWarnSpy;
109
-
110
- beforeAll(() => {
111
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
112
-
113
- app = new Koa();
114
-
115
- // Use NEW option names
116
- app.use(koaClassicServer(rootDir, {
117
- browserCacheEnabled: true,
118
- browserCacheMaxAge: 3600
119
- }));
120
-
121
- server = app.listen();
122
- });
123
-
124
- afterAll(() => {
125
- server.close();
126
- consoleWarnSpy.mockRestore();
127
- });
128
-
129
- test('should NOT display deprecation warnings', () => {
130
- // Filter out warnings from other tests (like index option deprecation)
131
- const cachingWarnings = consoleWarnSpy.mock.calls.filter(call =>
132
- call[1] && (call[1].includes('enableCaching') || call[1].includes('cacheMaxAge'))
133
- );
134
-
135
- expect(cachingWarnings.length).toBe(0);
136
- });
137
-
138
- test('should work correctly with new option names', async () => {
139
- const response = await supertest(server).get('/test-page.html');
140
-
141
- expect(response.status).toBe(200);
142
- expect(response.headers['etag']).toBeDefined();
143
- expect(response.headers['cache-control']).toContain('max-age=3600');
144
- });
30
+ test('should throw even when new option is also provided alongside "cacheMaxAge"', () => {
31
+ expect(() => {
32
+ const app = new Koa();
33
+ app.use(koaClassicServer(rootDir, { cacheMaxAge: 3600, browserCacheMaxAge: 3600 }));
34
+ }).toThrow('"cacheMaxAge" option was removed in v3.0.0');
145
35
  });
146
36
 
147
- describe('Using both old and new names (new takes precedence)', () => {
148
- let app;
149
- let server;
150
- let consoleWarnSpy;
151
-
152
- beforeAll(() => {
153
- consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
154
-
155
- app = new Koa();
37
+ test('should throw even when new option is also provided alongside "enableCaching"', () => {
38
+ expect(() => {
39
+ const app = new Koa();
40
+ app.use(koaClassicServer(rootDir, { enableCaching: false, browserCacheEnabled: false }));
41
+ }).toThrow('"enableCaching" option was removed in v3.0.0');
42
+ });
43
+ });
156
44
 
157
- // Use BOTH old and new names - new should take precedence
158
- app.use(koaClassicServer(rootDir, {
159
- enableCaching: true, // OLD (deprecated)
160
- browserCacheEnabled: false, // NEW (should take precedence)
161
- cacheMaxAge: 7200, // OLD (deprecated)
162
- browserCacheMaxAge: 9999 // NEW (should take precedence)
163
- }));
45
+ describe('New option names (browserCacheEnabled / browserCacheMaxAge)', () => {
46
+ const supertest = require('supertest');
47
+ let app;
48
+ let server;
49
+ let consoleWarnSpy;
164
50
 
165
- server = app.listen();
166
- });
51
+ beforeAll(() => {
52
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
167
53
 
168
- afterAll(() => {
169
- server.close();
170
- consoleWarnSpy.mockRestore();
171
- });
54
+ app = new Koa();
55
+ app.use(koaClassicServer(rootDir, {
56
+ browserCacheEnabled: true,
57
+ browserCacheMaxAge: 3600
58
+ }));
172
59
 
173
- test('should NOT display warnings when new names are also provided', () => {
174
- // When both old and new names are provided, no warning should be shown
175
- const cachingWarnings = consoleWarnSpy.mock.calls.filter(call =>
176
- call[1] && (call[1].includes('enableCaching') || call[1].includes('cacheMaxAge'))
177
- );
60
+ server = app.listen();
61
+ });
178
62
 
179
- expect(cachingWarnings.length).toBe(0);
180
- });
63
+ afterAll(() => {
64
+ server.close();
65
+ consoleWarnSpy.mockRestore();
66
+ });
181
67
 
182
- test('new option values should take precedence over old ones', async () => {
183
- const response = await supertest(server).get('/test-page.html');
68
+ test('should not warn about removed options', () => {
69
+ const removedWarnings = consoleWarnSpy.mock.calls.filter(call =>
70
+ call[1] && (call[1].includes('enableCaching') || call[1].includes('cacheMaxAge'))
71
+ );
72
+ expect(removedWarnings.length).toBe(0);
73
+ });
184
74
 
185
- expect(response.status).toBe(200);
75
+ test('should work correctly with new option names', async () => {
76
+ const response = await supertest(server).get('/test-page.html');
186
77
 
187
- // browserCacheEnabled: false should take precedence over enableCaching: true
188
- // So there should be NO caching headers
189
- expect(response.headers['etag']).toBeUndefined();
190
- expect(response.headers['cache-control']).toContain('no-cache');
191
- });
78
+ expect(response.status).toBe(200);
79
+ expect(response.headers['etag']).toBeDefined();
80
+ expect(response.headers['cache-control']).toContain('max-age=3600');
192
81
  });
82
+ });
193
83
 
194
- describe('Default behavior (no caching options specified)', () => {
195
- let app;
196
- let server;
197
-
198
- beforeAll(() => {
199
- app = new Koa();
200
- app.use(koaClassicServer(rootDir));
201
- server = app.listen();
202
- });
84
+ describe('Default behavior (no caching options specified)', () => {
85
+ const supertest = require('supertest');
86
+ let app;
87
+ let server;
203
88
 
204
- afterAll(() => {
205
- server.close();
206
- });
89
+ beforeAll(() => {
90
+ app = new Koa();
91
+ app.use(koaClassicServer(rootDir));
92
+ server = app.listen();
93
+ });
207
94
 
208
- test('should default to browserCacheEnabled: false', async () => {
209
- const response = await supertest(server).get('/test-page.html');
95
+ afterAll(() => {
96
+ server.close();
97
+ });
210
98
 
211
- expect(response.status).toBe(200);
99
+ test('should default to browserCacheEnabled: false', async () => {
100
+ const response = await supertest(server).get('/test-page.html');
212
101
 
213
- // Default is caching disabled
214
- expect(response.headers['cache-control']).toContain('no-cache');
215
- });
102
+ expect(response.status).toBe(200);
103
+ expect(response.headers['cache-control']).toContain('no-cache');
216
104
  });
217
105
  });
@@ -190,13 +190,17 @@ describe('DT_UNKNOWN filesystem support (NixOS buildFHSEnv, overlayfs, FUSE)', (
190
190
  });
191
191
 
192
192
  test('should return false for DT_UNKNOWN entry pointing to a regular file', async () => {
193
- // Files should NOT be treated as directories
194
- // If only files exist and no index pattern matches, dir listing should show
193
+ // Files should NOT be treated as directories.
194
+ // Use a RegExp index pattern to force the readdir-based slow-path in
195
+ // findIndexFile (the string fast-path uses stat() directly and would bypass
196
+ // the mock). The regex never matches, so no index file is found and the
197
+ // directory listing is shown, confirming the DT_UNKNOWN files are treated
198
+ // as regular files (not directories).
195
199
  const spy = mockReaddirWithDtUnknown(tmpDir, ['style.css', 'readme.txt']);
196
200
 
197
201
  const app = new Koa();
198
202
  app.use(koaClassicServer(tmpDir, {
199
- index: ['index.html'],
203
+ index: [/NOMATCH_SENTINEL/],
200
204
  showDirContents: true
201
205
  }));
202
206
  const server = app.listen();
@@ -254,8 +258,9 @@ describe('DT_UNKNOWN filesystem support (NixOS buildFHSEnv, overlayfs, FUSE)', (
254
258
  try {
255
259
  const res = await request.get('/');
256
260
  expect(res.status).toBe(200);
257
- expect(res.text).toContain('EJS DT_UNKNOWN');
258
- expect(res.text).not.toContain('Index of');
261
+ const body = res.text !== undefined ? res.text : res.body.toString('utf8');
262
+ expect(body).toContain('EJS DT_UNKNOWN');
263
+ expect(body).not.toContain('Index of');
259
264
  } finally {
260
265
  server.close();
261
266
  spy.mockRestore();
@@ -276,8 +281,9 @@ describe('DT_UNKNOWN filesystem support (NixOS buildFHSEnv, overlayfs, FUSE)', (
276
281
  try {
277
282
  const res = await request.get('/');
278
283
  expect(res.status).toBe(200);
279
- expect(res.text).toContain('EJS DT_UNKNOWN');
280
- expect(res.text).not.toContain('Index of');
284
+ const body = res.text !== undefined ? res.text : res.body.toString('utf8');
285
+ expect(body).toContain('EJS DT_UNKNOWN');
286
+ expect(body).not.toContain('Index of');
281
287
  } finally {
282
288
  server.close();
283
289
  spy.mockRestore();
@@ -285,11 +291,16 @@ describe('DT_UNKNOWN filesystem support (NixOS buildFHSEnv, overlayfs, FUSE)', (
285
291
  });
286
292
 
287
293
  test('should not find index in directory with only subdirectories (all DT_UNKNOWN)', async () => {
294
+ // Use a RegExp to force the readdir-based slow-path (the string fast-path
295
+ // would stat() index.html directly, finding the real file on disk).
296
+ // The regex never matches, so the mocked readdir result (['subdir']) is used:
297
+ // 'subdir' has DT_UNKNOWN → stat fallback → isDirectory() → not a file →
298
+ // fileNames stays empty → no index found → directory listing shown.
288
299
  const spy = mockReaddirWithDtUnknown(tmpDir, ['subdir']);
289
300
 
290
301
  const app = new Koa();
291
302
  app.use(koaClassicServer(tmpDir, {
292
- index: ['index.html'],
303
+ index: [/NOMATCH_SENTINEL/],
293
304
  showDirContents: true
294
305
  }));
295
306
  const server = app.listen();
@@ -298,7 +309,7 @@ describe('DT_UNKNOWN filesystem support (NixOS buildFHSEnv, overlayfs, FUSE)', (
298
309
  try {
299
310
  const res = await request.get('/');
300
311
  expect(res.status).toBe(200);
301
- // No file matches index.html, so directory listing should appear
312
+ // No file matches the pattern, so directory listing should appear
302
313
  expect(res.text).toContain('Index of');
303
314
  } finally {
304
315
  server.close();
@@ -0,0 +1 @@
1
+ dot dir file
@@ -0,0 +1,2 @@
1
+ SECRET_KEY=mysecretvalue
2
+ DB_PASSWORD=hidden123
@@ -0,0 +1 @@
1
+ key data
@@ -0,0 +1 @@
1
+ secret data
@@ -0,0 +1 @@
1
+ regular
@@ -0,0 +1 @@
1
+ normal
@@ -0,0 +1 @@
1
+ NESTED_SECRET=nestedvalue
@@ -0,0 +1 @@
1
+ regular in subdir