webspresso 0.0.75 → 0.1.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.
Files changed (76) hide show
  1. package/README.md +301 -8
  2. package/adapters/bun/index.js +22 -0
  3. package/adapters/cloudflare/index.js +138 -0
  4. package/adapters/node/index.js +75 -0
  5. package/adapters/types.js +16 -0
  6. package/bin/commands/add-deploy.js +84 -0
  7. package/bin/commands/add-tailwind.js +3 -3
  8. package/bin/commands/build.js +57 -0
  9. package/bin/commands/dev.js +5 -0
  10. package/bin/webspresso.js +7 -1
  11. package/core/auth/middleware.js +17 -98
  12. package/core/build/README.md +39 -0
  13. package/core/build/cache/build-cache.js +79 -0
  14. package/core/build/config/load-build-config.js +39 -0
  15. package/core/build/errors/build-error.js +35 -0
  16. package/core/build/graph/build-graph.js +63 -0
  17. package/core/build/graph/hash.js +46 -0
  18. package/core/build/index.js +128 -0
  19. package/core/build/phases/01-discover.js +89 -0
  20. package/core/build/phases/02-analyze.js +177 -0
  21. package/core/build/phases/03-compile/index.js +112 -0
  22. package/core/build/phases/03-compile/middleware.js +42 -0
  23. package/core/build/phases/03-compile/models.js +37 -0
  24. package/core/build/phases/03-compile/plugins.js +90 -0
  25. package/core/build/phases/03-compile/routes-api.js +103 -0
  26. package/core/build/phases/03-compile/routes-ssr.js +38 -0
  27. package/core/build/phases/03-compile/templates.js +180 -0
  28. package/core/build/phases/04-manifest.js +66 -0
  29. package/core/build/phases/05-bundle.js +122 -0
  30. package/core/build/phases/06-validate.js +67 -0
  31. package/core/build/runtime/create-app-from-manifest-node.js +27 -0
  32. package/core/build/runtime/create-app-from-manifest.js +43 -0
  33. package/core/build/runtime/create-worker-app.js +271 -0
  34. package/core/build/runtime/mount-manifest.js +372 -0
  35. package/core/build/runtime/resolve-worker-db.js +32 -0
  36. package/core/build/types.d.ts +109 -0
  37. package/core/orm/d1-knex-client.js +64 -0
  38. package/core/orm/index.js +126 -49
  39. package/index.d.ts +92 -21
  40. package/index.js +5 -0
  41. package/package.json +35 -14
  42. package/plugins/admin-panel/core/api-extensions.js +1 -1
  43. package/plugins/admin-panel/index.js +11 -15
  44. package/plugins/audit-log/index.js +8 -2
  45. package/plugins/audit-log/middleware.js +72 -10
  46. package/plugins/data-exchange/import.js +21 -29
  47. package/plugins/rate-limit/index.js +52 -98
  48. package/plugins/upload/index.js +27 -101
  49. package/src/app-context.js +2 -23
  50. package/src/client-runtime/mount.js +14 -14
  51. package/src/file-router.js +41 -348
  52. package/src/helpers.js +0 -10
  53. package/src/http/compat-app.js +240 -0
  54. package/src/http/context.js +329 -0
  55. package/src/http/cookies.js +103 -0
  56. package/src/http/errors.js +31 -0
  57. package/src/http/index.js +28 -0
  58. package/src/http/middleware.js +102 -0
  59. package/src/http/multipart.js +61 -0
  60. package/src/http/node-serve.js +33 -0
  61. package/src/http/secure-headers.js +128 -0
  62. package/src/http/session.js +47 -0
  63. package/src/router-edge.js +325 -0
  64. package/src/server.js +180 -314
  65. package/templates/deploy/cloudflare/src/worker.js +5 -0
  66. package/templates/deploy/cloudflare/stubs/empty.mjs +2 -0
  67. package/templates/deploy/cloudflare/webspresso.build.js.example +10 -0
  68. package/templates/deploy/cloudflare/webspresso.db.js.example +24 -0
  69. package/templates/deploy/cloudflare/wrangler.toml +26 -0
  70. package/templates/deploy/docker/.dockerignore +8 -0
  71. package/templates/deploy/docker/Dockerfile +17 -0
  72. package/templates/deploy/docker/docker-compose.yml +9 -0
  73. package/templates/deploy/node/webspresso.build.js.example +7 -0
  74. package/templates/deploy/pm2/ecosystem.config.js +17 -0
  75. package/templates/skills/webspresso-usage/REFERENCE-framework.md +5 -5
  76. package/templates/skills/webspresso-usage/SKILL.md +3 -0
package/README.md CHANGED
@@ -5,6 +5,20 @@
5
5
 
6
6
  A minimal, file-based SSR framework for Node.js with Nunjucks templating.
7
7
 
8
+ > **Current release:** `0.1.0-alpha.0` — Hono-based HTTP stack, production build compiler, and Cloudflare Workers adapter. See **[What's new](#whats-new)** and **[CHANGELOG.md](CHANGELOG.md)**.
9
+
10
+ ## What's new
11
+
12
+ Highlights in **`0.1.0-alpha.0`** (see **[CHANGELOG.md](CHANGELOG.md)** for the full list):
13
+
14
+ | Area | Summary |
15
+ |------|---------|
16
+ | **HTTP runtime** | **Hono** under the hood; handlers still use **`(req, res, next)`** and **`app.listen()`** via compat wrappers. |
17
+ | **Production build** | **`webspresso build --adapter node\|cloudflare\|bun`** — discover → analyze → compile → manifest → bundle → validate; output under **`.webspresso/`**. |
18
+ | **Cloudflare Workers** | **`webspresso add deploy --provider cloudflare`** scaffolds Wrangler + **`webspresso.build.js`**; build emits **precompiled `templates.mjs`** (walks **`extends` / `include`** from `views/`). Worker entry uses **`createWorkerApp`** via **`webspresso/build/runtime/create-app-from-manifest`** (not the Node server bundle). |
19
+ | **D1 on Workers** | Wrangler **`env.DB`** → **`req.db`** / **`getDb()`** when the generated entry wires **`knex`** + **`knex-cloudflare-d1`**. |
20
+ | **Package exports** | Subpaths: **`webspresso/build`**, **`webspresso/core/auth`**, **`webspresso/core/orm`**, **`webspresso/plugins/*`**, plus split manifest helpers (**`create-app-from-manifest`** vs **`create-app-from-manifest-node`**). |
21
+
8
22
  ## Features
9
23
 
10
24
  - **File-Based Routing**: Create pages by adding `.njk` files to a `pages/` directory
@@ -16,10 +30,11 @@ A minimal, file-based SSR framework for Node.js with Nunjucks templating.
16
30
  - **Template Helpers**: Laravel-inspired helper functions available in templates
17
31
  - **Plugin System**: Extensible architecture with version control and inter-plugin communication
18
32
  - **Built-in Plugins**: Development dashboard, sitemap generator, SEO checker, analytics integration (Google, Yandex, Bing), self-hosted site analytics, optional Swagger UI for HTTP APIs, configurable HTTP health probe endpoint, optional REST CRUD routes from ORM models, optional admin UI for ORM query cache metrics and purge, optional **admin-only spreadsheet exchange** (Excel export, CSV/XLSX import via `dataExchangePlugin`)
19
- - **Session authentication** (optional): `createAuth` / `quickAuth` in **`webspresso/core/auth`** — pass the manager to **`createApp({ auth })`** for `express-session`, `req.user` / `req.auth`, remember-me tokens, and policy-style authorization. Full walkthrough: **[`doc/index.html#authentication`](doc/index.html#authentication)**.
33
+ - **Session authentication** (optional): `createAuth` / `quickAuth` in **`webspresso/core/auth`** — pass the manager to **`createApp({ auth })`** for encrypted cookie sessions (`hono-sessions`), `req.user` / `req.auth`, remember-me tokens, and policy-style authorization. Full walkthrough: **[`doc/index.html#authentication`](doc/index.html#authentication)**.
20
34
  - **Optional client runtime** (Alpine.js + [swup](https://swup.js.org/)): **`createApp({ clientRuntime: { alpine, swup } })`** serves scripts under **`/__webspresso/client-runtime/`** and exposes **`clientRuntime`** in Nunjucks; layouts can include **`views/partials/webspresso-client-runtime.njk`**. Env overrides: **`WEBSPRESSO_ALPINE`**, **`WEBSPRESSO_SWUP`**. Demo: **`examples/alpine-swup-demo/`**. Details: **[`doc/index.html#client-runtime`](doc/index.html#client-runtime)**.
21
35
  - **TypeScript**: Published **`index.d.ts`** (via `package.json` `"types"`) for `createApp`, ORM, plugins, and router helpers — use from TS/JS with IDE autocomplete; runtime stays CommonJS
22
36
  - **Application kernel (optional)**: In-process **`kernel`** API (`require('webspresso').kernel`) — event bus (`dispatch` / `publish`), **`kernel.createApp()`** (namespaced differently from SSR **`createApp`**), **`definePlugin`** / **`defineFlow`**, minimal **`{{ }}` view resolver**, and simulated **`BaseRepository`** with `orm.<resource>.*` events. Ships as **`core/kernel/`** on npm. Demo: **`node core/kernel/run-demo.js`**. Docs: **[`doc/index.html#application-kernel`](doc/index.html#application-kernel)**.
37
+ - **Production builds & Cloudflare Workers**: **`webspresso build --adapter cloudflare`** emits a Wrangler-ready worker (manifest, precompiled Nunjucks, static assets). Full guide: **[`doc/index.html#cloudflare-workers`](doc/index.html#cloudflare-workers)** · summary below in **[Deployment](#deployment)**.
23
38
 
24
39
  ## Installation
25
40
 
@@ -37,13 +52,47 @@ The npm package ships with **[`index.d.ts`](index.d.ts)** so consumers get typin
37
52
  import { createApp, defineModel, zdb } from 'webspresso';
38
53
  ```
39
54
 
40
- Install **`@types/express`** in your app if you want full **`Express.Application`** / **`Request`** / **`Response`** inference when you touch `createApp().app` or write middleware. **`knex`** and **`zod`** bring their own types.
55
+ **`index.d.ts`** exports **`WebspressoCompatApp`**, **`WebspressoRequest`**, and **`WebspressoResponse`** for route handlers and middleware (Express-shaped API on Hono). Install **`hono`** in your app if you need Hono core types when extending `createApp().app`. **`knex`** and **`zod`** bring their own types.
41
56
 
42
57
  Framework development (this repo): run **`npm run check:types`** to typecheck the declarations against a small smoke file (`tests/ts-smoke/`).
43
58
 
59
+ ### Migrating from the Express-based Webspresso stack
60
+
61
+ Major versions use **Hono** instead of Express. Breaking changes:
62
+
63
+ | Before (Express) | After (Hono) |
64
+ |----------------|--------------|
65
+ | `createApp().app` is `express.Application` | `createApp().app` is **`WebspressoCompatApp`** (Hono + `listen`, `get`, `post`, …) |
66
+ | `express-session`, `cookie-parser`, `helmet`, `multer` | Built into Webspresso (`hono-sessions`, secure-headers, `parseBody` / upload plugin) |
67
+ | `app.listen(port)` via Express | Same call — implemented with **`@hono/node-server`** |
68
+ | Custom middleware `(req, res, next)` | Same signature; `req` / `res` are compat wrappers |
69
+ | `supertest` in tests | Use **`app.fetch`** or project test helper (`tests/helpers/http.js`) |
70
+
71
+ **`server.js` scaffold** (from `webspresso new`) still uses `app.listen(PORT, callback)` — no change required for basic apps.
72
+
73
+ **Rate limiting:** optional peer **`hono-rate-limiter`**; built-in **`rateLimitPlugin`** provides in-memory limiters for file routes.
74
+
75
+ **Raw Hono:** `createApp().app._hono` exposes the underlying Hono instance for advanced routing.
76
+
77
+ ### Package subpath exports
78
+
79
+ The npm **`exports`** field exposes focused entry points (CommonJS, with types on the root):
80
+
81
+ | Import | Use |
82
+ |--------|-----|
83
+ | `webspresso` | `createApp`, router utils, ORM re-exports, plugins index |
84
+ | `webspresso/build` | `runBuild`, `BuildConfig`, build diagnostics |
85
+ | `webspresso/build/runtime/create-app-from-manifest` | **Cloudflare Worker** manifest bootstrap → **`createWorkerApp`** |
86
+ | `webspresso/build/runtime/create-app-from-manifest-node` | **Node** manifest bootstrap (full `server.js` feature set) |
87
+ | `webspresso/core/auth` | `createAuth`, `quickAuth`, session middleware |
88
+ | `webspresso/core/orm` | `defineModel`, `createDatabase`, `zdb` |
89
+ | `webspresso/plugins/*` | Individual built-in plugins |
90
+
91
+ Use the **worker** manifest path in generated **`.webspresso/worker/index.mjs`** so Wrangler does not bundle bcrypt, admin plugins, or filesystem route scanning.
92
+
44
93
  ## Application kernel (`kernel`)
45
94
 
46
- Do **not** confuse **`kernel.createApp()`** with the package root **`createApp`** used for SSR—it returns a different object (event bus, optional flows, and a minimal view resolver). It does **not** modify Knex ORM behavior or Express routing.
95
+ Do **not** confuse **`kernel.createApp()`** with the package root **`createApp`** used for SSR—it returns a different object (event bus, optional flows, and a minimal view resolver). It does **not** modify Knex ORM behavior or HTTP routing.
47
96
 
48
97
  ```javascript
49
98
  const { kernel } = require('webspresso');
@@ -283,6 +332,59 @@ npm run watch:css # Watch and rebuild CSS on changes
283
332
  npm run dev # Starts both CSS watch and dev server
284
333
  ```
285
334
 
335
+ ### `webspresso add deploy`
336
+
337
+ Scaffold deployment provider files in the current project.
338
+
339
+ ```bash
340
+ # Cloudflare Workers + Wrangler + D1 example config
341
+ webspresso add deploy --provider cloudflare
342
+
343
+ # Docker or PM2 (Node process)
344
+ webspresso add deploy --provider docker
345
+ webspresso add deploy --provider pm2
346
+
347
+ # Multiple providers at once
348
+ webspresso add deploy --provider cloudflare,docker
349
+ ```
350
+
351
+ | Provider | Files created |
352
+ |----------|----------------|
353
+ | `cloudflare` | `wrangler.toml`, `webspresso.build.js`, optional `webspresso.db.js` (D1), legacy `src/worker.js` stub |
354
+ | `docker` | `Dockerfile`, `.dockerignore`, `docker-compose.yml` |
355
+ | `pm2` | `ecosystem.config.js` |
356
+
357
+ After scaffolding, run **`webspresso build --adapter <name>`** then deploy with the provider’s tool (Wrangler, Docker, PM2). See **[Deployment](#deployment)** and **[`doc/index.html#cloudflare-workers`](doc/index.html#cloudflare-workers)**.
358
+
359
+ ### `webspresso build`
360
+
361
+ Compile routes into a **build manifest** and write adapter-specific output under **`.webspresso/`**.
362
+
363
+ ```bash
364
+ # Node production entry (default when webspresso.build.js sets adapter: 'node')
365
+ webspresso build
366
+ webspresso build --adapter node
367
+
368
+ # Cloudflare Workers (Wrangler bundles .webspresso/worker/)
369
+ webspresso build --adapter cloudflare
370
+
371
+ # Manifest only — skip esbuild pre-bundle (Cloudflare always skips; Wrangler bundles)
372
+ webspresso build --adapter node --skip-bundle
373
+
374
+ # Fail CI on validation warnings (edge-incompatible imports, unresolved templates, …)
375
+ webspresso build --adapter cloudflare --fail-on-warnings
376
+ ```
377
+
378
+ | Adapter | Output directory | Runtime entry |
379
+ |---------|------------------|---------------|
380
+ | `node` | `.webspresso/server/` | `index.mjs` + `manifest.json` + `handlers.mjs` |
381
+ | `cloudflare` | `.webspresso/worker/` | same layout + **`templates.mjs`** (precompiled Nunjucks) |
382
+ | `bun` | `.webspresso/server/` (Bun adapter) | experimental |
383
+
384
+ Configure defaults in **`webspresso.build.js`** at the project root (see templates under `templates/deploy/`).
385
+
386
+ **Build phases** (summary): discover `pages/` → analyze edge imports & Nunjucks graph → compile handlers + inline i18n → write **`manifest.json`** → bundle adapter output (Node: optional esbuild; Cloudflare: **`templates.mjs`** + skip framework esbuild) → validate. Compiler internals: [`core/build/README.md`](core/build/README.md).
387
+
286
388
  ### `webspresso favicon:generate <source.png>`
287
389
 
288
390
  Generate favicon PNG files and `favicons.njk` partial from a single source PNG.
@@ -369,7 +471,7 @@ my-app/
369
471
 
370
472
  ### `createApp(options)`
371
473
 
372
- Creates and configures the Express app.
474
+ Creates and configures the **Hono-based compat app** (`WebspressoCompatApp`).
373
475
 
374
476
  **Options:**
375
477
  - `pagesDir` (required): Path to pages directory
@@ -384,7 +486,7 @@ Creates and configures the Express app.
384
486
  - `middlewares` (optional): Named middleware registry for routes
385
487
  - `clientRuntime` (optional): **`{ alpine?: boolean | object, swup?: boolean | object }`**. When either flag is enabled, mounts vendored Alpine 3 / swup 4 (+ Head + Scripts plugins) at **`/__webspresso/client-runtime/*`** and passes resolved **`{ alpine, swup }`** into SSR templates as **`clientRuntime`**. Override with env **`WEBSPRESSO_ALPINE`** / **`WEBSPRESSO_SWUP`** (`1` or `true`). Package exports **`resolveClientRuntime`** and **`CLIENT_RUNTIME_BASE`**. See **[Client runtime](#client-runtime)** below and **[`doc/index.html#client-runtime`](doc/index.html#client-runtime)**.
386
488
  - `auth` (optional): `AuthManager` from **`createAuth()`** / **`quickAuth()`** (`require('webspresso/core/auth')`). Registers session + cookie parsing, attaches **`req.auth`** / **`req.user`**, and injects named route middleware **`auth`** and **`guest`** (do not reuse those names for custom handlers if you pass `auth`). See **[`doc/index.html#authentication`](doc/index.html#authentication)**.
387
- - `setupRoutes(app, ctx)` (optional): Register custom Express routes after file routes and plugin `onRoutesReady`, before the 404 handler — use for login/logout handlers when using the auth module; **`ctx.authMiddleware`** exposes `requireAuth`, `requireGuest`, etc.; **`ctx.clientRuntime`** is **`{ alpine, swup }`**
489
+ - `setupRoutes(app, ctx)` (optional): Register custom routes on the compat **`app`** after file routes and plugin `onRoutesReady`, before the 404 handler — use for login/logout handlers when using the auth module; **`ctx.authMiddleware`** exposes `requireAuth`, `requireGuest`, etc.; **`ctx.clientRuntime`** is **`{ alpine, swup }`**
388
490
 
389
491
  **Example with middlewares:**
390
492
 
@@ -407,7 +509,7 @@ const { app } = createApp({
407
509
  }
408
510
  next();
409
511
  },
410
- rateLimit: require('express-rate-limit')({ windowMs: 60000, max: 100 })
512
+ rateLimit: require('hono-rate-limiter')({ windowMs: 60_000, limit: 100 })
411
513
  }
412
514
  });
413
515
  ```
@@ -514,7 +616,7 @@ createApp({
514
616
  | `getDb()` | Same instance as `req.db`; **throws** if no `db` was passed to `createApp` |
515
617
  | `hasDb()` | `true` if `createApp` was given `db` |
516
618
  | `getAppContext()` | `{ db }` — `db` may be `null` |
517
- | `attachDbMiddleware` | Express middleware to populate `req.db` for non–file-router routes |
619
+ | `attachDbMiddleware` | Compat middleware to populate `req.db` for non–file-router routes |
518
620
  | `resetAppContext()` | Clears context (mainly for tests) |
519
621
  | `setAppContext(partial)` | Low-level merge; normally only `createApp` uses this |
520
622
 
@@ -548,7 +650,7 @@ Error templates receive these variables:
548
650
 
549
651
  **How errors reach this handler**
550
652
 
551
- Unhandled errors from file-based routes (`pages/**/*.njk` `load()` / middleware / render, and `pages/api/**/*.js` handlers) are forwarded with `next(err)`, so they go through this 4-argument Express error middleware. That means `errorPages.serverError` and `errorPages.timeout` apply to those failures as well (not only to routes you add with `setupRoutes`).
653
+ Unhandled errors from file-based routes (`pages/**/*.njk` `load()` / middleware / render, and `pages/api/**/*.js` handlers) are forwarded with `next(err)`, so they go through the central error handler. That means `errorPages.serverError` and `errorPages.timeout` apply to those failures as well (not only to routes you add with `setupRoutes`).
552
654
 
553
655
  - **`pages/_hooks.js` `onError(ctx, err)`** runs **before** the central handler (for both SSR and API file routes). Use it for logging or APM (`Sentry.captureException`, `newrelic.noticeError`, etc.). The error is also on **`ctx.error`**.
554
656
 
@@ -2227,6 +2329,197 @@ const app = createApp({
2227
2329
 
2228
2330
  In production, keep the plugin disabled or protect it with `authorize` / your own middleware.
2229
2331
 
2332
+ ## Deployment
2333
+
2334
+ Webspresso can compile your `pages/` tree into a **static route manifest** and ship a **production entry** for Node or **Cloudflare Workers**. Development still uses `webspresso dev` + `server.js`; production uses **`webspresso build`** + the target adapter.
2335
+
2336
+ Long-form reference: **[`doc/index.html#deployment`](doc/index.html#deployment)** · Cloudflare walkthrough: **[`doc/index.html#cloudflare-workers`](doc/index.html#cloudflare-workers)**.
2337
+
2338
+ ### Quick comparison
2339
+
2340
+ | | Node (`adapter: 'node'`) | Cloudflare (`adapter: 'cloudflare'`) |
2341
+ |--|--------------------------|--------------------------------------|
2342
+ | **Dev** | `webspresso dev` | `webspresso build --adapter cloudflare` then `npx wrangler dev` |
2343
+ | **Deploy** | `node server.js` or Docker / PM2 | `npx wrangler deploy` |
2344
+ | **Full `createApp`** | Yes — plugins, auth, file-router scan | **Edge worker runtime** — manifest routes only |
2345
+ | **ORM / Knex** | All supported drivers | **D1** via `webspresso.db.js` + `knex-cloudflare-d1` (optional) |
2346
+ | **Auth (`core/auth`)** | bcrypt sessions, remember-me | **Not on Workers** — use external auth or Node adapter |
2347
+ | **Plugins** | All built-ins | Edge-compatible only (no admin panel, upload, data-exchange) |
2348
+ | **Nunjucks** | Filesystem + watch | **Precompiled at build** (Workers disallow runtime `eval`) |
2349
+ | **Static files** | `publicDir` via Hono static | Wrangler **`[assets]`** binding |
2350
+
2351
+ ### Node production
2352
+
2353
+ 1. `webspresso add deploy --provider docker` or `pm2` (optional).
2354
+ 2. `webspresso build --adapter node` → `.webspresso/server/`.
2355
+ 3. Run `node .webspresso/server/index.mjs` or use your process manager / container with `NODE_ENV=production`.
2356
+
2357
+ The generated entry calls **`createAppFromManifest`** with the full Node server (same feature set as `server.js` + manifest mode).
2358
+
2359
+ ### Cloudflare Workers
2360
+
2361
+ #### Prerequisites
2362
+
2363
+ - [Wrangler](https://developers.cloudflare.com/workers/wrangler/) (`npm i -D wrangler`)
2364
+ - Cloudflare account (for deploy)
2365
+ - Project with `pages/`, `views/` (layouts), and `public/` (CSS built with `npm run build:css`)
2366
+
2367
+ #### 1. Scaffold
2368
+
2369
+ ```bash
2370
+ webspresso add deploy --provider cloudflare
2371
+ ```
2372
+
2373
+ Creates **`wrangler.toml`**, **`webspresso.build.js`**, and optional **`webspresso.db.js`** (D1). Ensure `wrangler.toml` includes:
2374
+
2375
+ ```toml
2376
+ compatibility_flags = ["nodejs_compat"]
2377
+ ```
2378
+
2379
+ (The template ships this flag — required for some Node polyfills in the worker bundle.)
2380
+
2381
+ #### 2. Configure build
2382
+
2383
+ **`webspresso.build.js`** (example):
2384
+
2385
+ ```javascript
2386
+ /** @type {import('webspresso/build').BuildConfig} */
2387
+ module.exports = {
2388
+ adapter: 'cloudflare',
2389
+ pagesDir: 'pages',
2390
+ viewsDir: 'views',
2391
+ publicDir: 'public',
2392
+ };
2393
+ ```
2394
+
2395
+ #### 3. Build
2396
+
2397
+ ```bash
2398
+ npm run build:css # Tailwind → public/css/style.css
2399
+ webspresso build --adapter cloudflare
2400
+ ```
2401
+
2402
+ **Output** (`.webspresso/worker/`):
2403
+
2404
+ | Artifact | Purpose |
2405
+ |----------|---------|
2406
+ | `manifest.json` | Routes, templates metadata, i18n blobs, build id |
2407
+ | `handlers.mjs` | Compiled API handlers + SSR route configs |
2408
+ | `templates.mjs` | **Precompiled** Nunjucks (`index.njk`, `layout.njk`, …) |
2409
+ | `index.mjs` | Worker entry (`fetch` → `createAppFromManifest`) |
2410
+ | `assets/public/` | Copy of `public/` for Wrangler **Assets** |
2411
+
2412
+ Cloudflare builds **skip** the framework’s esbuild pre-bundle; **Wrangler** bundles `index.mjs` + dependencies when you run `wrangler dev` / `deploy`.
2413
+
2414
+ #### 4. Local worker dev
2415
+
2416
+ ```bash
2417
+ npx wrangler dev
2418
+ # or: npx wrangler dev --port 8788
2419
+ ```
2420
+
2421
+ `webspresso dev --adapter cloudflare` only reminds you to build + run Wrangler — use the two commands above for local Workers testing.
2422
+
2423
+ #### 5. Deploy
2424
+
2425
+ ```bash
2426
+ npx wrangler deploy
2427
+ ```
2428
+
2429
+ Configure `name`, routes, and D1 in **`wrangler.toml`**.
2430
+
2431
+ #### D1 database (optional)
2432
+
2433
+ When `webspresso.db.js` is present, the template wires **D1** for production:
2434
+
2435
+ ```javascript
2436
+ // webspresso.db.js — excerpt
2437
+ module.exports = {
2438
+ development: { client: 'better-sqlite3', connection: { filename: './dev.sqlite3' }, useNullAsDefault: true },
2439
+ production: {
2440
+ client: process.env.WEBSPRESSO_D1_REMOTE ? 'd1-remote' : 'd1',
2441
+ connection: process.env.WEBSPRESSO_D1_REMOTE
2442
+ ? { accountId: process.env.CF_ACCOUNT_ID, databaseId: process.env.CF_D1_DATABASE_ID, apiToken: process.env.CF_API_TOKEN }
2443
+ : {},
2444
+ migrations: { directory: './migrations' },
2445
+ },
2446
+ };
2447
+ ```
2448
+
2449
+ - **Local D1:** `wrangler dev` uses a local D1 emulator; run migrations with `webspresso db:migrate`.
2450
+ - **Remote D1:** `WEBSPRESSO_D1_REMOTE=1` plus `CF_*` env vars for CLI migrations against the remote database.
2451
+
2452
+ Pass **`env.DB`** from Wrangler into the worker via bindings (template `[[d1_databases]]`).
2453
+
2454
+ #### What works on Workers
2455
+
2456
+ - SSR pages from manifest (`pages/*.njk` + `views/` layouts)
2457
+ - API routes compiled into `handlers.mjs`
2458
+ - i18n JSON baked into the manifest
2459
+ - Static assets via **`[assets]`**
2460
+ - Zod validation on API routes
2461
+ - Optional D1 + ORM when configured — API handlers get **`req.db`** from the **`DB`** binding (same as Node when `createApp({ db })` was used)
2462
+
2463
+ **D1 in API routes (Workers):**
2464
+
2465
+ ```javascript
2466
+ // pages/api/notes.get.js — env.DB resolved at worker cold start
2467
+ module.exports = async function handler(req, res) {
2468
+ if (!req.db) return res.status(503).json({ error: 'Database not configured' });
2469
+ const notes = await req.db.getRepository('Note').query().orderBy('created_at', 'desc').list();
2470
+ res.json(notes);
2471
+ };
2472
+ ```
2473
+
2474
+ Install **`knex`** and **`knex-cloudflare-d1`** in the project; the scaffolded worker entry passes them via **`dbRuntime`** (see generated **`.webspresso/worker/index.mjs`**).
2475
+
2476
+ #### Limitations (build will error or warn)
2477
+
2478
+ | Not supported on Cloudflare adapter | Alternative |
2479
+ |-----------------------------------|-------------|
2480
+ | `admin-panel`, `upload`, `data-exchange` plugins | Deploy on **Node** or split admin to a separate service |
2481
+ | `createApp({ auth })` / bcrypt | External auth (JWT, Cloudflare Access) or Node adapter |
2482
+ | In-memory session store | KV, D1, or cookie-only JWT |
2483
+ | `clientRuntime` (Alpine/swup) in worker entry | Disabled in generated entry; enable only on Node |
2484
+ | Runtime filesystem route scan | Use **`webspresso build`** manifest instead |
2485
+ | `better-sqlite3`, `pg`, `mysql2` in worker bundle | **D1** or HTTP API to a Node backend |
2486
+
2487
+ Build-time validation reports **`WS_BUILD_PLUGIN_UNSUPPORTED`**, **`WS_BUILD_EDGE_INCOMPATIBLE`**, and **`WS_BUILD_SESSION_MEMORY`** when applicable.
2488
+
2489
+ #### Troubleshooting
2490
+
2491
+ | Symptom | Likely cause | Fix |
2492
+ |---------|----------------|-----|
2493
+ | `Could not resolve "crypto"` / `fs` during **webspresso** esbuild | Wrong adapter or old build path | Use `--adapter cloudflare` (skips framework esbuild); let Wrangler bundle |
2494
+ | `bcrypt` / `node-pre-gyp` / `aws-sdk` in **Wrangler** bundle | Full `server.js` pulled into worker | Ensure entry imports **`webspresso/build/runtime/create-app-from-manifest`** (worker-only), not Node manifest path |
2495
+ | `EvalError: Code generation from strings disallowed` | Nunjucks compiled at runtime | Rebuild — need **`templates.mjs`** from current `webspresso build` |
2496
+ | `layout.njk` not found at runtime | Missing parent template in precompile | Keep layouts in **`views/`**; rebuild — build walks **`extends` / `include`** and precompiles the full graph into **`templates.mjs`** |
2497
+ | Empty CSS on worker | Assets not built | Run **`npm run build:css`** before `webspresso build` |
2498
+ | `Address already in use` on `wrangler dev` | Port taken | `npx wrangler dev --port 8788` |
2499
+
2500
+ #### Architecture (Cloudflare)
2501
+
2502
+ ```
2503
+ pages/ + views/ → webspresso build → .webspresso/worker/
2504
+ ├── manifest.json
2505
+ ├── handlers.mjs
2506
+ ├── templates.mjs (precompiled)
2507
+ └── index.mjs → createWorkerApp()
2508
+ wrangler dev|deploy → Workers runtime → fetch(request, env)
2509
+ ```
2510
+
2511
+ Worker runtime uses **`createWorkerApp`** (`core/build/runtime/create-worker-app.js`): Hono compat app, manifest route mounting, precompiled Nunjucks — **without** `src/server.js`, file-router `fs` scan, or auth.
2512
+
2513
+ ### Docker / PM2
2514
+
2515
+ ```bash
2516
+ webspresso add deploy --provider docker # Dockerfile + compose
2517
+ webspresso add deploy --provider pm2 # ecosystem.config.js
2518
+ webspresso build --adapter node
2519
+ ```
2520
+
2521
+ Run the generated **`.webspresso/server/index.mjs`** inside your container or PM2 process (set `NODE_ENV=production`, expose `PORT`).
2522
+
2230
2523
  ## Development
2231
2524
 
2232
2525
  Native addons (**better-sqlite3**, **bcrypt**, **sharp**) are compiled for your current Node ABI. After switching Node major versions (e.g. nvm, fnm, Volta), run **`npm run rebuild:native`** or a clean install: `rm -rf node_modules && npm ci`. **chokidar** is not ABI-tied like those drivers; if file watching misbehaves, reinstall dependencies. The repo includes [`.nvmrc`](.nvmrc) (Node 20 LTS) as a known-good default for this project.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Bun adapter stub — future
3
+ * @module adapters/bun
4
+ */
5
+
6
+ const nodeAdapter = require('../node');
7
+
8
+ module.exports = {
9
+ ...nodeAdapter,
10
+ name: 'bun',
11
+ version: '0.1.0',
12
+ capabilities: {
13
+ ...nodeAdapter.capabilities,
14
+ fetch: true,
15
+ listen: true,
16
+ },
17
+
18
+ bundleOptions(manifest, outputDir) {
19
+ const base = nodeAdapter.bundleOptions(manifest, outputDir);
20
+ return { ...base, target: 'esnext' };
21
+ },
22
+ };
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Cloudflare Workers deployment adapter
3
+ * @module adapters/cloudflare
4
+ */
5
+
6
+ const path = require('path');
7
+ const { capabilityList } = require('../types');
8
+
9
+ const capabilities = {
10
+ fetch: true,
11
+ listen: false,
12
+ fs: false,
13
+ nativeModules: false,
14
+ d1: true,
15
+ kv: true,
16
+ r2: true,
17
+ assets: true,
18
+ };
19
+
20
+ const NODE_ONLY = new Set(['admin-panel', 'adminPanelPlugin', 'upload', 'uploadPlugin', 'data-exchange']);
21
+
22
+ module.exports = {
23
+ name: 'cloudflare',
24
+ version: '1.0.0',
25
+ capabilities,
26
+
27
+ /**
28
+ * @param {import('../../core/build/types').WebspressoManifest} manifest
29
+ */
30
+ validate(manifest) {
31
+ /** @type {object[]} */
32
+ const errors = [];
33
+ /** @type {object[]} */
34
+ const warnings = [];
35
+
36
+ for (const p of manifest.plugins || []) {
37
+ if (!p.edgeCompatible) {
38
+ errors.push({
39
+ code: 'WS_BUILD_PLUGIN_UNSUPPORTED',
40
+ message: `Plugin "${p.name}" is not edge-compatible`,
41
+ hint: 'Remove from config/app.js or deploy on Node adapter.',
42
+ });
43
+ }
44
+ }
45
+
46
+ if (manifest.middleware?.session?.store === 'memory') {
47
+ errors.push({
48
+ code: 'WS_BUILD_SESSION_MEMORY',
49
+ message: 'In-memory sessions are not supported on Cloudflare Workers',
50
+ hint: 'Use KV/D1 session store or cookie-only JWT.',
51
+ });
52
+ }
53
+
54
+ return { ok: errors.length === 0, errors, warnings };
55
+ },
56
+
57
+ /**
58
+ * @param {import('../../core/build/types').WebspressoManifest} _manifest
59
+ * @param {{ cwd?: string }} [ctx]
60
+ */
61
+ generateEntry(_manifest, ctx) {
62
+ const appRoot = ctx?.cwd || process.cwd();
63
+ const modulePaths = [path.join(appRoot, 'node_modules'), appRoot];
64
+ return `/**
65
+ * Webspresso Cloudflare Worker entry — generated by webspresso build
66
+ */
67
+ import knex from 'knex';
68
+ import d1Base from 'knex-cloudflare-d1';
69
+ import createD1KnexClient from 'webspresso/core/orm/d1-knex-client.js';
70
+ import { createAppFromManifest } from 'webspresso/build/runtime/create-app-from-manifest';
71
+ const d1Client = createD1KnexClient(d1Base);
72
+ import manifest from './manifest.json' assert { type: 'json' };
73
+ import { handlers } from './handlers.mjs';
74
+ import precompiledTemplates from './templates.mjs';
75
+
76
+ const modulePaths = ${JSON.stringify(modulePaths)};
77
+
78
+ /** @type {{ app: import('webspresso').WebspressoCompatApp } | null} */
79
+ let cached = null;
80
+
81
+ function getApp(env) {
82
+ if (!cached) {
83
+ cached = createAppFromManifest({
84
+ manifest,
85
+ handlers,
86
+ pagesDir: 'pages',
87
+ bindings: env,
88
+ modulePaths,
89
+ dbRuntime: { knex, d1Client },
90
+ precompiledTemplates,
91
+ clientRuntime: { alpine: false, swup: false },
92
+ logging: false,
93
+ });
94
+ }
95
+ return cached.app;
96
+ }
97
+
98
+ export default {
99
+ fetch(request, env, ctx) {
100
+ return getApp(env).fetch(request, env, ctx);
101
+ },
102
+ };
103
+ `;
104
+ },
105
+
106
+ /**
107
+ * @param {import('../../core/build/types').WebspressoManifest} _manifest
108
+ * @param {string} _outputDir
109
+ */
110
+ bundleOptions(_manifest, _outputDir) {
111
+ return {
112
+ platform: 'node',
113
+ format: 'esm',
114
+ target: 'es2022',
115
+ conditions: ['worker', 'import', 'require'],
116
+ mainFields: ['module', 'main'],
117
+ define: {
118
+ 'process.env.NODE_ENV': '"production"',
119
+ },
120
+ external: [
121
+ 'bcrypt',
122
+ 'better-sqlite3',
123
+ 'sharp',
124
+ 'mysql2',
125
+ 'pg',
126
+ 'knex',
127
+ 'sqlite3',
128
+ 'mysql',
129
+ 'tedious',
130
+ 'oracledb',
131
+ 'fsevents',
132
+ 'chokidar',
133
+ ],
134
+ };
135
+ },
136
+ };
137
+
138
+ module.exports.capabilityNames = capabilityList(capabilities);
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Node.js deployment adapter
3
+ * @module adapters/node
4
+ */
5
+
6
+ const { capabilityList } = require('../types');
7
+
8
+ const capabilities = {
9
+ fetch: true,
10
+ listen: true,
11
+ fs: true,
12
+ nativeModules: true,
13
+ };
14
+
15
+ module.exports = {
16
+ name: 'node',
17
+ version: '1.0.0',
18
+ capabilities,
19
+
20
+ /**
21
+ * @param {import('../../core/build/types').WebspressoManifest} manifest
22
+ */
23
+ validate(manifest) {
24
+ return { ok: true, errors: [], warnings: [] };
25
+ },
26
+
27
+ /**
28
+ * @param {import('../../core/build/types').WebspressoManifest} _manifest
29
+ */
30
+ generateEntry(_manifest) {
31
+ return `/**
32
+ * Webspresso Node production entry — generated by webspresso build
33
+ */
34
+ import { createAppFromManifest } from 'webspresso/build/runtime/create-app-from-manifest-node';
35
+ import manifest from './manifest.json' assert { type: 'json' };
36
+ import { handlers } from './handlers.mjs';
37
+
38
+ const { app } = createAppFromManifest({
39
+ manifest,
40
+ handlers,
41
+ pagesDir: 'pages',
42
+ });
43
+
44
+ const port = Number(process.env.PORT || 3000);
45
+ app.listen(port, () => {
46
+ console.log(\`Webspresso listening on http://localhost:\${port}\`);
47
+ });
48
+ `;
49
+ },
50
+
51
+ /**
52
+ * @param {import('../../core/build/types').WebspressoManifest} _manifest
53
+ * @param {string} outputDir
54
+ */
55
+ bundleOptions(_manifest, outputDir) {
56
+ return {
57
+ platform: 'node',
58
+ format: 'esm',
59
+ target: 'node18',
60
+ external: [
61
+ 'better-sqlite3',
62
+ 'bcrypt',
63
+ 'sharp',
64
+ 'pg',
65
+ 'mysql2',
66
+ 'knex',
67
+ 'fsevents',
68
+ 'knex-cloudflare-d1',
69
+ ],
70
+ banner: { js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);" },
71
+ };
72
+ },
73
+ };
74
+
75
+ module.exports.capabilityNames = capabilityList(capabilities);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Adapter type helpers
3
+ * @module adapters/types
4
+ */
5
+
6
+ /**
7
+ * @param {object} caps
8
+ * @returns {string[]}
9
+ */
10
+ function capabilityList(caps) {
11
+ return Object.entries(caps)
12
+ .filter(([, v]) => v === true)
13
+ .map(([k]) => k);
14
+ }
15
+
16
+ module.exports = { capabilityList };