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/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
- * @returns {Object} { app, nunjucksEnv }
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({ error: 'Internal Server Error' });
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