kempo-server 1.0.0 → 1.2.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 CHANGED
@@ -288,6 +288,7 @@ An array of regex patterns for paths that should not trigger a file system resca
288
288
 
289
289
  An object mapping custom route paths to file paths. Useful for aliasing or serving files from outside the document root.
290
290
 
291
+ **Basic Routes:**
291
292
  ```json
292
293
  {
293
294
  "customRoutes": {
@@ -297,6 +298,26 @@ An object mapping custom route paths to file paths. Useful for aliasing or servi
297
298
  }
298
299
  ```
299
300
 
301
+ **Wildcard Routes:**
302
+ Wildcard routes allow you to map entire directory structures using the `*` wildcard:
303
+
304
+ ```json
305
+ {
306
+ "customRoutes": {
307
+ "kempo/*": "./node_modules/kempo/dust/*",
308
+ "assets/*": "./static-files/*",
309
+ "docs/*": "./documentation/*"
310
+ }
311
+ }
312
+ ```
313
+
314
+ With wildcard routes:
315
+ - `kempo/styles.css` would serve `./node_modules/kempo/dust/styles.css`
316
+ - `assets/logo.png` would serve `./static-files/logo.png`
317
+ - `docs/readme.md` would serve `./documentation/readme.md`
318
+
319
+ The `*` wildcard matches any single path segment (anything between `/` characters). Multiple wildcards can be used in a single route pattern.
320
+
300
321
  ### maxRescanAttempts
301
322
 
302
323
  The maximum number of times to attempt rescanning the file system when a file is not found. Defaults to 3.
package/defaultConfig.js CHANGED
@@ -89,5 +89,6 @@ export default {
89
89
  maxRescanAttempts: 3,
90
90
  customRoutes: {
91
91
  // Example: "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css"
92
+ // Wildcard example: "kempo/*": "./node_modules/kempo/dust/*"
92
93
  }
93
94
  }
package/docs/index.html CHANGED
@@ -152,8 +152,22 @@
152
152
 
153
153
  <h4 id="customRoutes">customRoutes</h4>
154
154
  <p>An object mapping custom route paths to file paths. Useful for aliasing or serving files from outside the document root.</p>
155
+
156
+ <p><strong>Basic Routes:</strong></p>
155
157
  <pre><code class="hljs json">{<br /> <span class="hljs-attr">"customRoutes"</span>: {<br /> <span class="hljs-attr">"/vendor/bootstrap.css"</span>: <span class="hljs-string">"./node_modules/bootstrap/dist/css/bootstrap.min.css"</span>,<br /> <span class="hljs-attr">"/api/status"</span>: <span class="hljs-string">"./status.js"</span><br /> }<br />}</code></pre>
156
158
 
159
+ <p><strong>Wildcard Routes:</strong></p>
160
+ <p>Wildcard routes allow you to map entire directory structures using the <code>*</code> wildcard:</p>
161
+ <pre><code class="hljs json">{<br /> <span class="hljs-attr">"customRoutes"</span>: {<br /> <span class="hljs-attr">"kempo/*"</span>: <span class="hljs-string">"./node_modules/kempo/dust/*"</span>,<br /> <span class="hljs-attr">"assets/*"</span>: <span class="hljs-string">"./static-files/*"</span>,<br /> <span class="hljs-attr">"docs/*"</span>: <span class="hljs-string">"./documentation/*"</span><br /> }<br />}</code></pre>
162
+
163
+ <p>With wildcard routes:</p>
164
+ <ul>
165
+ <li><code>kempo/styles.css</code> would serve <code>./node_modules/kempo/dust/styles.css</code></li>
166
+ <li><code>assets/logo.png</code> would serve <code>./static-files/logo.png</code></li>
167
+ <li><code>docs/readme.md</code> would serve <code>./documentation/readme.md</code></li>
168
+ </ul>
169
+ <p>The <code>*</code> wildcard matches any single path segment (anything between <code>/</code> characters). Multiple wildcards can be used in a single route pattern.</p>
170
+
157
171
  <h4 id="maxRescanAttempts">maxRescanAttempts</h4>
158
172
  <p>The maximum number of times to attempt rescanning the file system when a file is not found. Defaults to 3.</p>
159
173
  <pre><code class="hljs json">{<br /> <span class="hljs-attr">"maxRescanAttempts"</span>: <span class="hljs-number">3</span><br />}</code></pre>
@@ -163,6 +177,7 @@
163
177
  <li><strong>Zero Dependencies</strong> - No external dependencies required</li>
164
178
  <li><strong>File-based Routing</strong> - Routes are defined by your directory structure</li>
165
179
  <li><strong>Dynamic Routes</strong> - Support for parameterized routes with square bracket syntax</li>
180
+ <li><strong>Wildcard Routes</strong> - Map entire directory structures with wildcard patterns</li>
166
181
  <li><strong>Multiple HTTP Methods</strong> - Support for GET, POST, PUT, DELETE, and more</li>
167
182
  <li><strong>Static File Serving</strong> - Automatically serves static files with proper MIME types</li>
168
183
  <li><strong>HTML Routes</strong> - Support for both JavaScript and HTML route handlers</li>
package/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "kempo-server",
3
3
  "type": "module",
4
- "version": "1.0.0",
5
- "description": "",
4
+ "version": "1.2.0",
5
+ "description": "A lightweight, zero-dependency, file based routing server.",
6
6
  "main": "index.js",
7
+ "bin": {
8
+ "kempo-server": "./index.js"
9
+ },
7
10
  "scripts": {
8
- "start": "node index.js -r docs"
11
+ "start": "node index.js -r ./docs"
9
12
  },
10
13
  "author": "",
11
14
  "license": "ISC",
package/router.js CHANGED
@@ -41,26 +41,69 @@ export default async (flags, log) => {
41
41
 
42
42
  // Process custom routes - resolve paths and validate files exist
43
43
  const customRoutes = new Map();
44
+ const wildcardRoutes = new Map();
45
+
44
46
  if (config.customRoutes && Object.keys(config.customRoutes).length > 0) {
45
47
  log(`Processing ${Object.keys(config.customRoutes).length} custom routes`, 2);
46
48
 
47
49
  for (const [urlPath, filePath] of Object.entries(config.customRoutes)) {
48
50
  try {
49
- // Resolve the file path relative to the current working directory
50
- const resolvedPath = path.resolve(filePath);
51
-
52
- // Check if the file exists (we'll do this async)
53
- const { stat } = await import('fs/promises');
54
- await stat(resolvedPath);
55
-
56
- customRoutes.set(urlPath, resolvedPath);
57
- log(`Custom route mapped: ${urlPath} -> ${resolvedPath}`, 2);
51
+ // Check if this is a wildcard route
52
+ if (urlPath.includes('*')) {
53
+ // Store wildcard routes separately for pattern matching
54
+ wildcardRoutes.set(urlPath, filePath);
55
+ log(`Wildcard route mapped: ${urlPath} -> ${filePath}`, 2);
56
+ } else {
57
+ // Resolve the file path relative to the current working directory
58
+ const resolvedPath = path.resolve(filePath);
59
+
60
+ // Check if the file exists (we'll do this async)
61
+ const { stat } = await import('fs/promises');
62
+ await stat(resolvedPath);
63
+
64
+ customRoutes.set(urlPath, resolvedPath);
65
+ log(`Custom route mapped: ${urlPath} -> ${resolvedPath}`, 2);
66
+ }
58
67
  } catch (error) {
59
68
  log(`Custom route error for ${urlPath} -> ${filePath}: ${error.message}`, 1);
60
69
  }
61
70
  }
62
71
  }
63
72
 
73
+ // Helper function to match wildcard patterns
74
+ const matchWildcardRoute = (requestPath, pattern) => {
75
+ // Convert wildcard pattern to regex
76
+ const regexPattern = pattern
77
+ .replace(/\*/g, '([^/]+)') // Replace * with capture group for single segment
78
+ .replace(/\*\*/g, '(.+)'); // Replace ** with capture group for multiple segments
79
+
80
+ const regex = new RegExp(`^${regexPattern}$`);
81
+ return regex.exec(requestPath);
82
+ };
83
+
84
+ // Helper function to resolve wildcard file paths
85
+ const resolveWildcardPath = (filePath, matches) => {
86
+ let resolvedPath = filePath;
87
+
88
+ // Replace wildcards with captured values
89
+ for (let i = 1; i < matches.length; i++) {
90
+ resolvedPath = resolvedPath.replace('*', matches[i]);
91
+ }
92
+
93
+ return path.resolve(resolvedPath);
94
+ };
95
+
96
+ // Helper function to find matching wildcard route
97
+ const findWildcardRoute = (requestPath) => {
98
+ for (const [pattern, filePath] of wildcardRoutes) {
99
+ const matches = matchWildcardRoute(requestPath, pattern);
100
+ if (matches) {
101
+ return { filePath, matches };
102
+ }
103
+ }
104
+ return null;
105
+ };
106
+
64
107
  // Track 404 attempts to avoid unnecessary rescans
65
108
  const rescanAttempts = new Map(); // path -> attempt count
66
109
  const dynamicNoRescanPaths = new Set(); // paths that have exceeded max attempts
@@ -128,6 +171,29 @@ export default async (flags, log) => {
128
171
  }
129
172
  }
130
173
 
174
+ // Check wildcard routes
175
+ const wildcardMatch = findWildcardRoute(requestPath);
176
+ if (wildcardMatch) {
177
+ const resolvedFilePath = resolveWildcardPath(wildcardMatch.filePath, wildcardMatch.matches);
178
+ log(`Serving wildcard route: ${requestPath} -> ${resolvedFilePath}`, 2);
179
+
180
+ try {
181
+ const fileContent = await readFile(resolvedFilePath);
182
+ const fileExtension = path.extname(resolvedFilePath).toLowerCase().slice(1);
183
+ const mimeType = config.allowedMimes[fileExtension] || 'application/octet-stream';
184
+
185
+ log(`Serving wildcard file as ${mimeType} (${fileContent.length} bytes)`, 2);
186
+ res.writeHead(200, { 'Content-Type': mimeType });
187
+ res.end(fileContent);
188
+ return; // Successfully served wildcard route
189
+ } catch (error) {
190
+ log(`Error serving wildcard route ${requestPath}: ${error.message}`, 0);
191
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
192
+ res.end('Internal Server Error');
193
+ return;
194
+ }
195
+ }
196
+
131
197
  // Try to serve the file normally
132
198
  const served = await serveFile(files, rootPath, requestPath, req.method, config, req, res, log);
133
199