kempo-server 1.7.5 → 1.7.7

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 (33) hide show
  1. package/package.json +1 -1
  2. package/tests/getFiles.node-test.js +3 -3
  3. package/tests/index.node-test.js +3 -2
  4. package/tests/router-middleware.node-test.js +2 -3
  5. package/tests/router-wildcard-double-asterisk.node-test.js +19 -20
  6. package/tests/router-wildcard.node-test.js +13 -13
  7. package/tests/router.node-test.js +7 -8
  8. package/tests/serveFile.node-test.js +5 -8
  9. package/tests/test-server-root/.config.json +5 -0
  10. package/tests/test-server-root/a.txt +1 -0
  11. package/tests/test-server-root/api/GET.js +1 -0
  12. package/tests/test-server-root/api/no-default.js +1 -0
  13. package/tests/test-server-root/b/1.txt +1 -0
  14. package/tests/test-server-root/custom/data.json +1 -0
  15. package/tests/test-server-root/docs/.config.json +5 -0
  16. package/tests/test-server-root/docs/api/data.json +1 -0
  17. package/tests/test-server-root/docs/src/components/Button.js +1 -0
  18. package/tests/test-server-root/docs/src/nested/file.js +1 -0
  19. package/tests/test-server-root/docs/src/utils/helpers/format.js +1 -0
  20. package/tests/test-server-root/hello.html +1 -0
  21. package/tests/test-server-root/index.html +1 -0
  22. package/tests/test-server-root/late.html +1 -0
  23. package/tests/test-server-root/public/src/file.txt +1 -0
  24. package/tests/test-server-root/src/components/Button.js +1 -0
  25. package/tests/test-server-root/src/components/Import.js +1 -0
  26. package/tests/test-server-root/src/deep/nested/file.js +1 -0
  27. package/tests/test-server-root/src/deep/nested/folder/file.js +1 -0
  28. package/tests/test-server-root/src/file.js +1 -0
  29. package/tests/test-server-root/src/file.txt +1 -0
  30. package/tests/test-server-root/src/nested/file.js +1 -0
  31. package/tests/test-server-root/src/utils/helpers/format.js +1 -0
  32. package/tests/test-server-root/src/utils/helpers.js +1 -0
  33. package/tests/test-utils.js +50 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kempo-server",
3
3
  "type": "module",
4
- "version": "1.7.5",
4
+ "version": "1.7.7",
5
5
  "description": "A lightweight, zero-dependency, file based routing server.",
6
6
  "main": "dist/index.js",
7
7
  "exports": {
@@ -1,13 +1,13 @@
1
1
  import getFiles from '../src/getFiles.js';
2
2
  import defaultConfig from '../src/defaultConfig.js';
3
3
  import path from 'path';
4
- import {withTempDir, write, log} from './test-utils.js';
4
+ import {withTestDir, write, log} from './test-utils.js';
5
5
 
6
6
  export default {
7
7
  'scans directories recursively and filters by mime and disallowed': async ({pass, fail}) => {
8
- await withTempDir(async (dir) => {
8
+ await withTestDir(async (dir) => {
9
9
  const cfg = JSON.parse(JSON.stringify(defaultConfig));
10
- await write(dir, 'index.html', '<!doctype html>');
10
+ // Create test-specific files for this test
11
11
  await write(dir, '.env', 'SECRET=1');
12
12
  await write(dir, 'notes.xyz', 'unknown');
13
13
  await write(dir, 'sub/app.js', 'console.log(1)');
@@ -1,9 +1,10 @@
1
- import {startNode, randomPort, httpGet, withTempDir, write} from './test-utils.js';
1
+ import {startNode, randomPort, httpGet, withTestDir, write} from './test-utils.js';
2
2
  import path from 'path';
3
3
 
4
4
  export default {
5
5
  'index.js CLI starts server and serves root': async ({pass, fail}) => {
6
- await withTempDir(async (dir) => {
6
+ await withTestDir(async (dir) => {
7
+ // Create custom index.html for this test
7
8
  await write(dir, 'index.html', 'home');
8
9
  const port = randomPort();
9
10
  const args = [path.join(process.cwd(), 'dist/index.js'), '-r', '.', '-p', String(port), '-l', '0'];
@@ -1,10 +1,10 @@
1
1
  import http from 'http';
2
- import {withTempDir, write, randomPort} from './test-utils.js';
2
+ import {withTestDir, write, randomPort} from './test-utils.js';
3
3
  import router from '../src/router.js';
4
4
 
5
5
  export default {
6
6
  'built-in middleware can be configured on router': async ({pass, fail}) => {
7
- await withTempDir(async (dir) => {
7
+ await withTestDir(async (dir) => {
8
8
  await write(dir, '.config.json', JSON.stringify({
9
9
  middleware: {
10
10
  cors: {enabled: true, origin: '*', methods: ['GET'], headers: ['X']},
@@ -14,7 +14,6 @@ export default {
14
14
  logging: {enabled: true, includeUserAgent: false, includeResponseTime: true}
15
15
  }
16
16
  }));
17
- await write(dir, 'index.html', 'hello world');
18
17
  const prev = process.cwd();
19
18
  process.chdir(dir);
20
19
  const flags = {root: '.', logging: 0, scan: false};
@@ -13,8 +13,8 @@ export default {
13
13
  const fileC = await write(dir, 'src/deep/nested/folder/file.js', 'export const deep = true');
14
14
 
15
15
  // Create files in docs directory that should be ignored when custom routes match
16
- await write(dir, 'docs/src/components/Button.js', 'WRONG FILE');
17
- await write(dir, 'docs/src/utils/helpers/format.js', 'WRONG FILE');
16
+ await write(dir, 'docs/src/components/Button.js', '// WRONG FILE');
17
+ await write(dir, 'docs/src/utils/helpers/format.js', '// WRONG FILE');
18
18
 
19
19
  const prev = process.cwd();
20
20
  process.chdir(dir);
@@ -70,12 +70,12 @@ export default {
70
70
  'single asterisk only matches single path segments': async ({pass, fail, log}) => {
71
71
  try {
72
72
  await withTempDir(async (dir) => {
73
- // Create nested file structure
74
- await write(dir, 'src/file.js', 'single level');
75
- await write(dir, 'src/nested/file.js', 'nested level');
73
+ // Create files at different nesting levels
74
+ await write(dir, 'src/file.js', '// single level');
75
+ await write(dir, 'src/nested/file.js', '// nested level');
76
76
 
77
- // Create fallback file in docs
78
- await write(dir, 'docs/src/nested/file.js', 'fallback file');
77
+ // Create docs directory with fallback file
78
+ await write(dir, 'docs/src/nested/file.js', '// fallback file');
79
79
 
80
80
  const prev = process.cwd();
81
81
  process.chdir(dir);
@@ -103,13 +103,13 @@ export default {
103
103
  const r1 = await httpGet(`http://localhost:${port}/src/file.js`);
104
104
  log('single level status: ' + r1.res.statusCode);
105
105
  if(r1.res.statusCode !== 200) throw new Error('single level 200');
106
- if(r1.body.toString() !== 'single level') throw new Error('single level content');
106
+ if(r1.body.toString() !== '// single level') throw new Error('single level content');
107
107
 
108
108
  // Nested level should NOT work with single asterisk, should fall back to docs
109
109
  const r2 = await httpGet(`http://localhost:${port}/src/nested/file.js`);
110
110
  log('nested level status: ' + r2.res.statusCode);
111
111
  if(r2.res.statusCode !== 200) throw new Error('nested level 200');
112
- if(r2.body.toString() !== 'fallback file') throw new Error('nested level should use fallback');
112
+ if(r2.body.toString() !== '// fallback file') throw new Error('nested level should use fallback');
113
113
 
114
114
  } finally {
115
115
  server.close();
@@ -125,12 +125,12 @@ export default {
125
125
  'wildcard routes take precedence over static files': async ({pass, fail, log}) => {
126
126
  try {
127
127
  await withTempDir(async (dir) => {
128
- // Create source files
129
- await write(dir, 'custom/data.json', '{"source": "custom"}');
130
-
131
- // Create static files in docs that should be overridden
128
+ // Create static file in docs
132
129
  await write(dir, 'docs/api/data.json', '{"source": "static"}');
133
130
 
131
+ // Create custom file outside docs
132
+ await write(dir, 'custom/data.json', '{"source": "custom"}');
133
+
134
134
  const prev = process.cwd();
135
135
  process.chdir(dir);
136
136
 
@@ -174,13 +174,11 @@ export default {
174
174
  'wildcard routes serve nested files from server root': async ({pass, fail, log}) => {
175
175
  try {
176
176
  await withTempDir(async (dir) => {
177
- // Create nested file structure in src directory
178
- const importFile = await write(dir, 'src/components/Import.js', 'export default ImportComponent');
179
- const utilsFile = await write(dir, 'src/utils/helpers.js', 'export const helpers = {}');
180
- const deepFile = await write(dir, 'src/deep/nested/file.js', 'export const nested = true');
181
-
182
- // Create index.html in server root
183
- await write(dir, 'index.html', '<h1>Home</h1>');
177
+ // Create additional test files needed for this specific test
178
+ await write(dir, 'src/components/Import.js', 'export default ImportComponent');
179
+ await write(dir, 'src/utils/helpers.js', 'export const helpers = {}');
180
+ await write(dir, 'src/deep/nested/file.js', 'export const deep = true');
181
+ await write(dir, 'index.html', '<html><body>Test Index</body></html>');
184
182
 
185
183
  const prev = process.cwd();
186
184
  process.chdir(dir);
@@ -220,6 +218,7 @@ export default {
220
218
  const r3 = await httpGet(`http://localhost:${port}/src/deep/nested/file.js`);
221
219
  log('deep nested file status: ' + r3.res.statusCode);
222
220
  if(r3.res.statusCode !== 200) throw new Error(`Expected 200 for deep nested file, got ${r3.res.statusCode}`);
221
+ if(r3.body.toString() !== 'export const deep = true') throw new Error('Deep nested file content mismatch');
223
222
 
224
223
  // Test that index.html still works (non-wildcard route)
225
224
  const r4 = await httpGet(`http://localhost:${port}/index.html`);
@@ -13,8 +13,8 @@ export default {
13
13
  const fileC = await write(dir, 'src/deep/nested/folder/file.js', 'export const deep = true');
14
14
 
15
15
  // Create files in docs directory that should be ignored when custom routes match
16
- await write(dir, 'docs/src/components/Button.js', 'WRONG FILE');
17
- await write(dir, 'docs/src/utils/helpers/format.js', 'WRONG FILE');
16
+ await write(dir, 'docs/src/components/Button.js', '// WRONG FILE');
17
+ await write(dir, 'docs/src/utils/helpers/format.js', '// WRONG FILE');
18
18
 
19
19
  const prev = process.cwd();
20
20
  process.chdir(dir);
@@ -70,12 +70,12 @@ export default {
70
70
  'single asterisk only matches single path segments': async ({pass, fail, log}) => {
71
71
  try {
72
72
  await withTempDir(async (dir) => {
73
- // Create nested file structure
74
- await write(dir, 'src/file.js', 'single level');
75
- await write(dir, 'src/nested/file.js', 'nested level');
73
+ // Create files at different nesting levels
74
+ await write(dir, 'src/file.js', '// single level');
75
+ await write(dir, 'src/nested/file.js', '// nested level');
76
76
 
77
- // Create fallback file in docs
78
- await write(dir, 'docs/src/nested/file.js', 'fallback file');
77
+ // Create docs directory with fallback file
78
+ await write(dir, 'docs/src/nested/file.js', '// fallback file');
79
79
 
80
80
  const prev = process.cwd();
81
81
  process.chdir(dir);
@@ -103,13 +103,13 @@ export default {
103
103
  const r1 = await httpGet(`http://localhost:${port}/src/file.js`);
104
104
  log('single level status: ' + r1.res.statusCode);
105
105
  if(r1.res.statusCode !== 200) throw new Error('single level 200');
106
- if(r1.body.toString() !== 'single level') throw new Error('single level content');
106
+ if(r1.body.toString() !== '// single level') throw new Error('single level content');
107
107
 
108
108
  // Nested level should NOT work with single asterisk, should fall back to docs
109
109
  const r2 = await httpGet(`http://localhost:${port}/src/nested/file.js`);
110
110
  log('nested level status: ' + r2.res.statusCode);
111
111
  if(r2.res.statusCode !== 200) throw new Error('nested level 200');
112
- if(r2.body.toString() !== 'fallback file') throw new Error('nested level should use fallback');
112
+ if(r2.body.toString() !== '// fallback file') throw new Error('nested level should use fallback');
113
113
 
114
114
  } finally {
115
115
  server.close();
@@ -125,12 +125,12 @@ export default {
125
125
  'wildcard routes take precedence over static files': async ({pass, fail, log}) => {
126
126
  try {
127
127
  await withTempDir(async (dir) => {
128
- // Create source files
129
- await write(dir, 'custom/data.json', '{"source": "custom"}');
130
-
131
- // Create static files in docs that should be overridden
128
+ // Create static file in docs
132
129
  await write(dir, 'docs/api/data.json', '{"source": "static"}');
133
130
 
131
+ // Create custom file outside docs
132
+ await write(dir, 'custom/data.json', '{"source": "custom"}');
133
+
134
134
  const prev = process.cwd();
135
135
  process.chdir(dir);
136
136
 
@@ -1,13 +1,12 @@
1
1
  import http from 'http';
2
2
  import path from 'path';
3
- import {withTempDir, write, randomPort, httpGet, log} from './test-utils.js';
3
+ import {withTestDir, write, randomPort, httpGet, log} from './test-utils.js';
4
4
  import router from '../src/router.js';
5
5
  import defaultConfig from '../src/defaultConfig.js';
6
6
 
7
7
  export default {
8
8
  'serves static files and 404s unknown': async ({pass, fail}) => {
9
- await withTempDir(async (dir) => {
10
- await write(dir, 'index.html', '<h1>Home</h1>');
9
+ await withTestDir(async (dir) => {
11
10
  const prev = process.cwd();
12
11
  process.chdir(dir);
13
12
  const flags = {root: '.', logging: 0, scan: false};
@@ -38,7 +37,7 @@ export default {
38
37
  pass('static + 404');
39
38
  },
40
39
  'rescan on 404 when enabled and not blacklisted': async ({pass, fail}) => {
41
- await withTempDir(async (dir) => {
40
+ await withTestDir(async (dir) => {
42
41
  const prev = process.cwd();
43
42
  process.chdir(dir);
44
43
  const flags = {root: '.', logging: 0, scan: true};
@@ -55,7 +54,7 @@ export default {
55
54
  return fail('first 404');
56
55
  }
57
56
 
58
- await write(dir, 'late.html', 'later');
57
+ // File already exists, should be found on rescan
59
58
  const hit = await httpGet(`http://localhost:${port}/late.html`);
60
59
  if(hit.res.statusCode !== 200) {
61
60
  server.close();
@@ -69,9 +68,9 @@ export default {
69
68
  pass('rescan');
70
69
  },
71
70
  'custom and wildcard routes serve mapped files': async ({pass, fail}) => {
72
- await withTempDir(async (dir) => {
73
- const fileA = await write(dir, 'a.txt', 'A');
74
- const fileB = await write(dir, 'b/1.txt', 'B1');
71
+ await withTestDir(async (dir) => {
72
+ const fileA = path.join(dir, 'a.txt');
73
+ const fileB = path.join(dir, 'b/1.txt');
75
74
  const prev = process.cwd();
76
75
  process.chdir(dir);
77
76
  const flags = {root: '.', logging: 0, scan: false};
@@ -2,14 +2,13 @@ import serveFile from '../src/serveFile.js';
2
2
  import findFile from '../src/findFile.js';
3
3
  import defaultConfig from '../src/defaultConfig.js';
4
4
  import path from 'path';
5
- import {createMockReq, createMockRes, withTempDir, write, log} from './test-utils.js';
5
+ import {createMockReq, createMockRes, withTestDir, write, log} from './test-utils.js';
6
6
 
7
7
  export default {
8
8
  'serves static file with correct mime': async ({pass, fail}) => {
9
9
  try {
10
- await withTempDir(async (dir) => {
10
+ await withTestDir(async (dir) => {
11
11
  const cfg = JSON.parse(JSON.stringify(defaultConfig));
12
- await write(dir, 'index.html', '<h1>Hi</h1>');
13
12
  const files = [path.join(dir, 'index.html')];
14
13
  const res = createMockRes();
15
14
  const ok = await serveFile(files, dir, '/index.html', 'GET', cfg, createMockReq(), res, log);
@@ -22,9 +21,8 @@ export default {
22
21
  },
23
22
  'executes route files by calling default export': async ({pass, fail}) => {
24
23
  try {
25
- await withTempDir(async (dir) => {
24
+ await withTestDir(async (dir) => {
26
25
  const cfg = JSON.parse(JSON.stringify(defaultConfig));
27
- await write(dir, 'api/GET.js', "export default async (req, res) => { res.status(201).json({ok:true, params:req.params}); }\n");
28
26
  const files = [path.join(dir, 'api/GET.js')];
29
27
  const res = createMockRes();
30
28
  const ok = await serveFile(files, dir, '/api', 'GET', cfg, createMockReq(), res, log);
@@ -37,10 +35,9 @@ export default {
37
35
  },
38
36
  'handles route file without default function': async ({pass, fail}) => {
39
37
  try {
40
- await withTempDir(async (dir) => {
38
+ await withTestDir(async (dir) => {
41
39
  const cfg = JSON.parse(JSON.stringify(defaultConfig));
42
- await write(dir, 'api/GET.js', "export const x = 1;\n");
43
- const files = [path.join(dir, 'api/GET.js')];
40
+ const files = [path.join(dir, 'api/no-default.js')];
44
41
  const res = createMockRes();
45
42
  const ok = await serveFile(files, dir, '/api', 'GET', cfg, createMockReq(), res, log);
46
43
  if(ok !== true) return fail('handled');
@@ -0,0 +1,5 @@
1
+ {
2
+ "middleware": {
3
+ "cors": {}
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ A
@@ -0,0 +1 @@
1
+ export default async (req, res) => { res.status(201).json({ok:true, params:req.params}); }
@@ -0,0 +1 @@
1
+ export const x = 1;
@@ -0,0 +1 @@
1
+ B1
@@ -0,0 +1 @@
1
+ {"source": "custom"}
@@ -0,0 +1,5 @@
1
+ {
2
+ "customRoutes": {
3
+ "/src/**": "../src/**"
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ {"source": "static"}
@@ -0,0 +1 @@
1
+ // WRONG FILE
@@ -0,0 +1 @@
1
+ // fallback file
@@ -0,0 +1 @@
1
+ // WRONG FILE
@@ -0,0 +1 @@
1
+ hello world
@@ -0,0 +1 @@
1
+ <h1>Home</h1>
@@ -0,0 +1 @@
1
+ later
@@ -0,0 +1 @@
1
+ static
@@ -0,0 +1 @@
1
+ export default Button
@@ -0,0 +1 @@
1
+ export const Import = () => 'imported';
@@ -0,0 +1 @@
1
+ export const nested = true
@@ -0,0 +1 @@
1
+ export const deep = true
@@ -0,0 +1 @@
1
+ // single level
@@ -0,0 +1 @@
1
+ custom
@@ -0,0 +1 @@
1
+ // nested level
@@ -0,0 +1 @@
1
+ export const format = () => {}
@@ -0,0 +1 @@
1
+ export const helpers = {}
@@ -1,7 +1,11 @@
1
1
  import {Readable} from 'stream';
2
- import {mkdtemp, rm, writeFile, mkdir} from 'fs/promises';
2
+ import {mkdtemp, rm, writeFile, mkdir, cp} from 'fs/promises';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
+ import {fileURLToPath} from 'url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const TEST_SERVER_ROOT = path.join(__dirname, 'test-server-root');
5
9
 
6
10
  export const createMockReq = ({method = 'GET', url = '/', headers = {}, body = null, remoteAddress = '127.0.0.1'} = {}) => {
7
11
  const stream = new Readable({read(){}});
@@ -43,6 +47,7 @@ export const createMockRes = () => {
43
47
  };
44
48
  };
45
49
 
50
+ // Legacy function for backward compatibility - will be deprecated
46
51
  export const withTempDir = async (fn) => {
47
52
  const dir = await mkdtemp(path.join(os.tmpdir(), 'kempo-tests-'));
48
53
  try {
@@ -52,6 +57,50 @@ export const withTempDir = async (fn) => {
52
57
  }
53
58
  };
54
59
 
60
+ // New function that uses the persistent test server root as a base
61
+ export const withTestDir = async (fn, {subdir = null} = {}) => {
62
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), 'kempo-tests-'));
63
+ try {
64
+ // Copy the test server root to the temporary directory
65
+ await cp(TEST_SERVER_ROOT, tempDir, {recursive: true});
66
+ let workingDir = tempDir;
67
+
68
+ if (subdir) {
69
+ workingDir = path.join(tempDir, subdir);
70
+ }
71
+
72
+ return await fn(workingDir);
73
+ } finally {
74
+ await rm(tempDir, {recursive: true, force: true});
75
+ }
76
+ };
77
+
78
+ // Helper to get the path to a file in the test server root
79
+ export const getTestFilePath = (relativePath) => {
80
+ return path.join(TEST_SERVER_ROOT, relativePath);
81
+ };
82
+
83
+ // Helper to reset or prepare specific test scenarios
84
+ export const prepareTestScenario = async (dir, scenario) => {
85
+ switch (scenario) {
86
+ case 'basic-server':
87
+ // Already has index.html, api/GET.js, etc.
88
+ break;
89
+ case 'wildcard-routes':
90
+ await write(dir, 'docs/.config.json', JSON.stringify({
91
+ customRoutes: { '/src/**': '../src/**' }
92
+ }));
93
+ break;
94
+ case 'middleware':
95
+ await write(dir, '.config.json', JSON.stringify({
96
+ middleware: { cors: {enabled: true} }
97
+ }));
98
+ break;
99
+ default:
100
+ // No special preparation needed
101
+ }
102
+ };
103
+
55
104
  export const write = async (root, rel, content = '') => {
56
105
  const full = path.join(root, rel);
57
106
  await mkdir(path.dirname(full), {recursive: true});