webspresso 0.0.2 → 0.0.4
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 +261 -5
- package/index.js +25 -2
- package/package.json +1 -1
- package/src/file-router.js +80 -6
- package/src/helpers.js +206 -1
- package/src/plugin-manager.js +451 -0
- package/src/server.js +163 -55
package/src/server.js
CHANGED
|
@@ -8,6 +8,8 @@ const helmet = require('helmet');
|
|
|
8
8
|
const nunjucks = require('nunjucks');
|
|
9
9
|
|
|
10
10
|
const { mountPages } = require('./file-router');
|
|
11
|
+
const { configureAssets } = require('./helpers');
|
|
12
|
+
const { createPluginManager } = require('./plugin-manager');
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Get default Helmet configuration
|
|
@@ -51,6 +53,62 @@ function getDefaultHelmetConfig(isDev) {
|
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Default 404 page HTML
|
|
58
|
+
*/
|
|
59
|
+
function default404Html() {
|
|
60
|
+
return `<!DOCTYPE html>
|
|
61
|
+
<html>
|
|
62
|
+
<head>
|
|
63
|
+
<title>404 - Not Found</title>
|
|
64
|
+
<style>
|
|
65
|
+
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
|
66
|
+
.container { text-align: center; }
|
|
67
|
+
h1 { font-size: 4rem; margin: 0; color: #333; }
|
|
68
|
+
p { color: #666; margin: 1rem 0; }
|
|
69
|
+
a { color: #0066cc; text-decoration: none; }
|
|
70
|
+
a:hover { text-decoration: underline; }
|
|
71
|
+
</style>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<div class="container">
|
|
75
|
+
<h1>404</h1>
|
|
76
|
+
<p>Page not found</p>
|
|
77
|
+
<a href="/">← Back to Home</a>
|
|
78
|
+
</div>
|
|
79
|
+
</body>
|
|
80
|
+
</html>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Default 500 page HTML
|
|
85
|
+
*/
|
|
86
|
+
function default500Html(err, isDev) {
|
|
87
|
+
return `<!DOCTYPE html>
|
|
88
|
+
<html>
|
|
89
|
+
<head>
|
|
90
|
+
<title>500 - Server Error</title>
|
|
91
|
+
<style>
|
|
92
|
+
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
|
93
|
+
.container { text-align: center; }
|
|
94
|
+
h1 { font-size: 4rem; margin: 0; color: #333; }
|
|
95
|
+
p { color: #666; margin: 1rem 0; }
|
|
96
|
+
pre { background: #fff; padding: 1rem; border-radius: 4px; text-align: left; overflow: auto; max-width: 600px; }
|
|
97
|
+
a { color: #0066cc; text-decoration: none; }
|
|
98
|
+
a:hover { text-decoration: underline; }
|
|
99
|
+
</style>
|
|
100
|
+
</head>
|
|
101
|
+
<body>
|
|
102
|
+
<div class="container">
|
|
103
|
+
<h1>500</h1>
|
|
104
|
+
<p>Internal Server Error</p>
|
|
105
|
+
${isDev && err ? `<pre>${err.stack || err.message}</pre>` : ''}
|
|
106
|
+
<a href="/">← Back to Home</a>
|
|
107
|
+
</div>
|
|
108
|
+
</body>
|
|
109
|
+
</html>`;
|
|
110
|
+
}
|
|
111
|
+
|
|
54
112
|
/**
|
|
55
113
|
* Create and configure the Express app
|
|
56
114
|
* @param {Object} options - Configuration options
|
|
@@ -59,7 +117,16 @@ function getDefaultHelmetConfig(isDev) {
|
|
|
59
117
|
* @param {string} options.publicDir - Path to public/static directory
|
|
60
118
|
* @param {boolean} options.logging - Enable request logging (default: isDev)
|
|
61
119
|
* @param {Object|boolean} options.helmet - Helmet configuration (default: auto-configured, false to disable)
|
|
62
|
-
* @
|
|
120
|
+
* @param {Object} options.middlewares - Named middleware registry for route configs
|
|
121
|
+
* @param {Array} options.plugins - Array of plugin definitions
|
|
122
|
+
* @param {Object} options.assets - Asset manager configuration
|
|
123
|
+
* @param {string} options.assets.manifestPath - Path to asset manifest file (Vite, Webpack)
|
|
124
|
+
* @param {string} options.assets.version - Asset version for cache busting
|
|
125
|
+
* @param {string} options.assets.prefix - URL prefix for assets
|
|
126
|
+
* @param {Object} options.errorPages - Custom error page handlers
|
|
127
|
+
* @param {Function|string} options.errorPages.notFound - Custom 404 handler or template path
|
|
128
|
+
* @param {Function|string} options.errorPages.serverError - Custom 500 handler or template path
|
|
129
|
+
* @returns {Object} { app, nunjucksEnv, pluginManager }
|
|
63
130
|
*/
|
|
64
131
|
function createApp(options = {}) {
|
|
65
132
|
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
@@ -71,9 +138,22 @@ function createApp(options = {}) {
|
|
|
71
138
|
viewsDir,
|
|
72
139
|
publicDir,
|
|
73
140
|
logging = isDev && !isTest,
|
|
74
|
-
helmet: helmetConfig
|
|
141
|
+
helmet: helmetConfig,
|
|
142
|
+
middlewares = {},
|
|
143
|
+
plugins = [],
|
|
144
|
+
assets: assetsConfig = {},
|
|
145
|
+
errorPages = {}
|
|
75
146
|
} = options;
|
|
76
147
|
|
|
148
|
+
// Create plugin manager
|
|
149
|
+
const pluginManager = createPluginManager();
|
|
150
|
+
|
|
151
|
+
// Configure asset manager
|
|
152
|
+
configureAssets({
|
|
153
|
+
publicDir: publicDir || 'public',
|
|
154
|
+
...assetsConfig
|
|
155
|
+
});
|
|
156
|
+
|
|
77
157
|
if (!pagesDir) {
|
|
78
158
|
throw new Error('pagesDir is required');
|
|
79
159
|
}
|
|
@@ -139,6 +219,10 @@ function createApp(options = {}) {
|
|
|
139
219
|
return d.toString();
|
|
140
220
|
});
|
|
141
221
|
|
|
222
|
+
// Register plugins (synchronous part)
|
|
223
|
+
const pluginContext = { app, nunjucksEnv, options };
|
|
224
|
+
pluginManager.register(plugins, pluginContext);
|
|
225
|
+
|
|
142
226
|
// Request logging middleware
|
|
143
227
|
if (logging) {
|
|
144
228
|
app.use((req, res, next) => {
|
|
@@ -155,80 +239,104 @@ function createApp(options = {}) {
|
|
|
155
239
|
if (!isTest) {
|
|
156
240
|
console.log('\nMounting routes:');
|
|
157
241
|
}
|
|
158
|
-
mountPages(app, {
|
|
242
|
+
const routeMetadata = mountPages(app, {
|
|
159
243
|
pagesDir,
|
|
160
244
|
nunjucks: nunjucksEnv,
|
|
245
|
+
middlewares,
|
|
246
|
+
pluginManager,
|
|
161
247
|
silent: isTest
|
|
162
248
|
});
|
|
163
249
|
|
|
250
|
+
// Set route metadata in plugin manager
|
|
251
|
+
pluginManager.setRoutes(routeMetadata);
|
|
252
|
+
|
|
253
|
+
// Call onRoutesReady hook synchronously (plugins should not be async in this phase)
|
|
254
|
+
// and mount any custom routes added by plugins
|
|
255
|
+
for (const [name, plugin] of pluginManager.plugins) {
|
|
256
|
+
if (typeof plugin.onRoutesReady === 'function') {
|
|
257
|
+
const ctx = {
|
|
258
|
+
app,
|
|
259
|
+
nunjucksEnv,
|
|
260
|
+
options,
|
|
261
|
+
routes: pluginManager.routes,
|
|
262
|
+
usePlugin: (n) => pluginManager.getPluginAPI(n),
|
|
263
|
+
addHelper: (n, fn) => pluginManager.registeredHelpers.set(n, fn),
|
|
264
|
+
addFilter: (n, fn) => pluginManager.registeredFilters.set(n, fn),
|
|
265
|
+
addRoute: (method, path, ...handlers) => {
|
|
266
|
+
app[method.toLowerCase()](path, ...handlers);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
plugin.onRoutesReady(ctx);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
164
273
|
// 404 handler
|
|
165
274
|
app.use((req, res) => {
|
|
166
275
|
res.status(404);
|
|
276
|
+
|
|
277
|
+
// Custom handler function
|
|
278
|
+
if (typeof errorPages.notFound === 'function') {
|
|
279
|
+
return errorPages.notFound(req, res);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Custom template
|
|
283
|
+
if (typeof errorPages.notFound === 'string') {
|
|
284
|
+
try {
|
|
285
|
+
const html = nunjucksEnv.render(errorPages.notFound, {
|
|
286
|
+
url: req.url,
|
|
287
|
+
method: req.method
|
|
288
|
+
});
|
|
289
|
+
return res.send(html);
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.error('Error rendering 404 template:', e);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Default response
|
|
167
296
|
if (req.accepts('html')) {
|
|
168
|
-
res.send(
|
|
169
|
-
<!DOCTYPE html>
|
|
170
|
-
<html>
|
|
171
|
-
<head>
|
|
172
|
-
<title>404 - Not Found</title>
|
|
173
|
-
<style>
|
|
174
|
-
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
|
175
|
-
.container { text-align: center; }
|
|
176
|
-
h1 { font-size: 4rem; margin: 0; color: #333; }
|
|
177
|
-
p { color: #666; margin: 1rem 0; }
|
|
178
|
-
a { color: #0066cc; text-decoration: none; }
|
|
179
|
-
a:hover { text-decoration: underline; }
|
|
180
|
-
</style>
|
|
181
|
-
</head>
|
|
182
|
-
<body>
|
|
183
|
-
<div class="container">
|
|
184
|
-
<h1>404</h1>
|
|
185
|
-
<p>Page not found</p>
|
|
186
|
-
<a href="/">← Back to Home</a>
|
|
187
|
-
</div>
|
|
188
|
-
</body>
|
|
189
|
-
</html>
|
|
190
|
-
`);
|
|
297
|
+
res.send(default404Html());
|
|
191
298
|
} else {
|
|
192
|
-
res.json({ error: 'Not Found' });
|
|
299
|
+
res.json({ error: 'Not Found', status: 404 });
|
|
193
300
|
}
|
|
194
301
|
});
|
|
195
302
|
|
|
196
303
|
// Error handler
|
|
197
304
|
app.use((err, req, res, next) => {
|
|
198
305
|
console.error('Server error:', err);
|
|
199
|
-
res.status(500);
|
|
306
|
+
res.status(err.status || 500);
|
|
307
|
+
|
|
308
|
+
// Custom handler function
|
|
309
|
+
if (typeof errorPages.serverError === 'function') {
|
|
310
|
+
return errorPages.serverError(err, req, res);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Custom template
|
|
314
|
+
if (typeof errorPages.serverError === 'string') {
|
|
315
|
+
try {
|
|
316
|
+
const html = nunjucksEnv.render(errorPages.serverError, {
|
|
317
|
+
error: isDev ? err : { message: 'Internal Server Error' },
|
|
318
|
+
status: err.status || 500,
|
|
319
|
+
isDev
|
|
320
|
+
});
|
|
321
|
+
return res.send(html);
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error('Error rendering 500 template:', e);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Default response
|
|
200
328
|
if (req.accepts('html')) {
|
|
201
|
-
res.send(
|
|
202
|
-
<!DOCTYPE html>
|
|
203
|
-
<html>
|
|
204
|
-
<head>
|
|
205
|
-
<title>500 - Server Error</title>
|
|
206
|
-
<style>
|
|
207
|
-
body { font-family: system-ui, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
|
208
|
-
.container { text-align: center; }
|
|
209
|
-
h1 { font-size: 4rem; margin: 0; color: #333; }
|
|
210
|
-
p { color: #666; margin: 1rem 0; }
|
|
211
|
-
pre { background: #fff; padding: 1rem; border-radius: 4px; text-align: left; overflow: auto; max-width: 600px; }
|
|
212
|
-
a { color: #0066cc; text-decoration: none; }
|
|
213
|
-
a:hover { text-decoration: underline; }
|
|
214
|
-
</style>
|
|
215
|
-
</head>
|
|
216
|
-
<body>
|
|
217
|
-
<div class="container">
|
|
218
|
-
<h1>500</h1>
|
|
219
|
-
<p>Internal Server Error</p>
|
|
220
|
-
${isDev ? `<pre>${err.stack || err.message}</pre>` : ''}
|
|
221
|
-
<a href="/">← Back to Home</a>
|
|
222
|
-
</div>
|
|
223
|
-
</body>
|
|
224
|
-
</html>
|
|
225
|
-
`);
|
|
329
|
+
res.send(default500Html(err, isDev));
|
|
226
330
|
} else {
|
|
227
|
-
res.json({
|
|
331
|
+
res.json({
|
|
332
|
+
error: 'Internal Server Error',
|
|
333
|
+
status: err.status || 500,
|
|
334
|
+
...(isDev && { message: err.message, stack: err.stack })
|
|
335
|
+
});
|
|
228
336
|
}
|
|
229
337
|
});
|
|
230
338
|
|
|
231
|
-
return { app, nunjucksEnv };
|
|
339
|
+
return { app, nunjucksEnv, pluginManager };
|
|
232
340
|
}
|
|
233
341
|
|
|
234
342
|
// Export for use as library
|