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.
- package/README.md +301 -8
- package/adapters/bun/index.js +22 -0
- package/adapters/cloudflare/index.js +138 -0
- package/adapters/node/index.js +75 -0
- package/adapters/types.js +16 -0
- package/bin/commands/add-deploy.js +84 -0
- package/bin/commands/add-tailwind.js +3 -3
- package/bin/commands/build.js +57 -0
- package/bin/commands/dev.js +5 -0
- package/bin/webspresso.js +7 -1
- package/core/auth/middleware.js +17 -98
- package/core/build/README.md +39 -0
- package/core/build/cache/build-cache.js +79 -0
- package/core/build/config/load-build-config.js +39 -0
- package/core/build/errors/build-error.js +35 -0
- package/core/build/graph/build-graph.js +63 -0
- package/core/build/graph/hash.js +46 -0
- package/core/build/index.js +128 -0
- package/core/build/phases/01-discover.js +89 -0
- package/core/build/phases/02-analyze.js +177 -0
- package/core/build/phases/03-compile/index.js +112 -0
- package/core/build/phases/03-compile/middleware.js +42 -0
- package/core/build/phases/03-compile/models.js +37 -0
- package/core/build/phases/03-compile/plugins.js +90 -0
- package/core/build/phases/03-compile/routes-api.js +103 -0
- package/core/build/phases/03-compile/routes-ssr.js +38 -0
- package/core/build/phases/03-compile/templates.js +180 -0
- package/core/build/phases/04-manifest.js +66 -0
- package/core/build/phases/05-bundle.js +122 -0
- package/core/build/phases/06-validate.js +67 -0
- package/core/build/runtime/create-app-from-manifest-node.js +27 -0
- package/core/build/runtime/create-app-from-manifest.js +43 -0
- package/core/build/runtime/create-worker-app.js +271 -0
- package/core/build/runtime/mount-manifest.js +372 -0
- package/core/build/runtime/resolve-worker-db.js +32 -0
- package/core/build/types.d.ts +109 -0
- package/core/orm/d1-knex-client.js +64 -0
- package/core/orm/index.js +126 -49
- package/index.d.ts +92 -21
- package/index.js +5 -0
- package/package.json +35 -14
- package/plugins/admin-panel/core/api-extensions.js +1 -1
- package/plugins/admin-panel/index.js +11 -15
- package/plugins/audit-log/index.js +8 -2
- package/plugins/audit-log/middleware.js +72 -10
- package/plugins/data-exchange/import.js +21 -29
- package/plugins/rate-limit/index.js +52 -98
- package/plugins/upload/index.js +27 -101
- package/src/app-context.js +2 -23
- package/src/client-runtime/mount.js +14 -14
- package/src/file-router.js +41 -348
- package/src/helpers.js +0 -10
- package/src/http/compat-app.js +240 -0
- package/src/http/context.js +329 -0
- package/src/http/cookies.js +103 -0
- package/src/http/errors.js +31 -0
- package/src/http/index.js +28 -0
- package/src/http/middleware.js +102 -0
- package/src/http/multipart.js +61 -0
- package/src/http/node-serve.js +33 -0
- package/src/http/secure-headers.js +128 -0
- package/src/http/session.js +47 -0
- package/src/router-edge.js +325 -0
- package/src/server.js +180 -314
- package/templates/deploy/cloudflare/src/worker.js +5 -0
- package/templates/deploy/cloudflare/stubs/empty.mjs +2 -0
- package/templates/deploy/cloudflare/webspresso.build.js.example +10 -0
- package/templates/deploy/cloudflare/webspresso.db.js.example +24 -0
- package/templates/deploy/cloudflare/wrangler.toml +26 -0
- package/templates/deploy/docker/.dockerignore +8 -0
- package/templates/deploy/docker/Dockerfile +17 -0
- package/templates/deploy/docker/docker-compose.yml +9 -0
- package/templates/deploy/node/webspresso.build.js.example +7 -0
- package/templates/deploy/pm2/ecosystem.config.js +17 -0
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +5 -5
- 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 `
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
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` |
|
|
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
|
|
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 };
|