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.
- package/README.md +68 -10
- package/__tests__/caching-headers.test.js +30 -30
- package/__tests__/compression-fixtures/data.json +1 -0
- package/__tests__/compression-fixtures/large.txt +1 -0
- package/__tests__/compression-fixtures/small.txt +1 -0
- package/__tests__/compression.test.js +270 -0
- package/__tests__/customTest/serversToLoad.util.js +1 -1
- package/__tests__/deprecation-warnings.test.js +71 -183
- package/__tests__/dt-unknown.test.js +20 -9
- package/__tests__/hidden-fixtures/.dot-dir/inside.txt +1 -0
- package/__tests__/hidden-fixtures/.env +2 -0
- package/__tests__/hidden-fixtures/.well-known/acme-challenge.txt +1 -0
- package/__tests__/hidden-fixtures/config/secrets/password.txt +1 -0
- package/__tests__/hidden-fixtures/data.key +1 -0
- package/__tests__/hidden-fixtures/file.secret +1 -0
- package/__tests__/hidden-fixtures/index.html +1 -0
- package/__tests__/hidden-fixtures/normal.txt +1 -0
- package/__tests__/hidden-fixtures/subdir/.env +1 -0
- package/__tests__/hidden-fixtures/subdir/regular.txt +1 -0
- package/__tests__/hidden-option.test.js +422 -0
- package/__tests__/index-option.test.js +18 -16
- package/__tests__/index.test.js +8 -4
- package/__tests__/range-fixtures/sample.txt +1 -0
- package/__tests__/range.test.js +223 -0
- package/__tests__/security-headers.test.js +153 -0
- package/__tests__/security.test.js +145 -159
- package/__tests__/server-cache-fixtures/large.txt +1 -0
- package/__tests__/server-cache-fixtures/small.txt +1 -0
- package/__tests__/server-cache.test.js +423 -0
- package/__tests__/symlink.test.js +8 -5
- package/docs/ACTION_PLAN.md +293 -0
- package/docs/CHANGELOG.md +84 -0
- package/docs/EXAMPLES_INDEX_OPTION.md +2 -2
- package/docs/FLOW_DIAGRAM.md +13 -13
- package/docs/OPTIMIZATION_ROADMAP_for_V3.md +864 -0
- package/docs/PERFORMANCE_COMPARISON.md +7 -7
- package/eslint.config.mjs +17 -0
- package/index.cjs +1096 -391
- package/index.mjs +1 -5
- package/package.json +4 -1
|
@@ -1,217 +1,105 @@
|
|
|
1
1
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
2
|
//
|
|
3
|
-
// TEST FOR
|
|
4
|
-
//
|
|
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('
|
|
14
|
+
describe('Removed option names (v3.0.0 breaking changes)', () => {
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
166
|
-
});
|
|
51
|
+
beforeAll(() => {
|
|
52
|
+
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
167
53
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
54
|
+
app = new Koa();
|
|
55
|
+
app.use(koaClassicServer(rootDir, {
|
|
56
|
+
browserCacheEnabled: true,
|
|
57
|
+
browserCacheMaxAge: 3600
|
|
58
|
+
}));
|
|
172
59
|
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
180
|
-
|
|
63
|
+
afterAll(() => {
|
|
64
|
+
server.close();
|
|
65
|
+
consoleWarnSpy.mockRestore();
|
|
66
|
+
});
|
|
181
67
|
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
75
|
+
test('should work correctly with new option names', async () => {
|
|
76
|
+
const response = await supertest(server).get('/test-page.html');
|
|
186
77
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
89
|
+
beforeAll(() => {
|
|
90
|
+
app = new Koa();
|
|
91
|
+
app.use(koaClassicServer(rootDir));
|
|
92
|
+
server = app.listen();
|
|
93
|
+
});
|
|
207
94
|
|
|
208
|
-
|
|
209
|
-
|
|
95
|
+
afterAll(() => {
|
|
96
|
+
server.close();
|
|
97
|
+
});
|
|
210
98
|
|
|
211
|
-
|
|
99
|
+
test('should default to browserCacheEnabled: false', async () => {
|
|
100
|
+
const response = await supertest(server).get('/test-page.html');
|
|
212
101
|
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
//
|
|
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: [
|
|
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
|
-
|
|
258
|
-
expect(
|
|
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
|
-
|
|
280
|
-
expect(
|
|
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: [
|
|
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
|
|
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 @@
|
|
|
1
|
+
acme
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
password
|
|
@@ -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
|