jskelet 0.5.5 → 0.6.1
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/AGENTS.md +19 -15
- package/CHANGELOG.md +165 -15
- package/README.md +16 -21
- package/bin/jskelet.mjs +23 -9
- package/docs/01-baslangic.md +4 -3
- package/docs/02-mimari.md +10 -4
- package/docs/03-routing.md +14 -7
- package/docs/04-render-ve-sablonlar.md +60 -43
- package/docs/05-islands.md +12 -8
- package/docs/06-cache.md +18 -7
- package/docs/07-yapilandirma.md +69 -27
- package/docs/08-build.md +15 -9
- package/docs/09-dev-araclari.md +22 -8
- package/docs/10-dagitim.md +14 -13
- package/docs/11-tasima.md +51 -17
- package/docs/12-panel-ve-oturum.md +10 -4
- package/docs/README.md +10 -33
- package/docs/en/01-getting-started.md +4 -3
- package/docs/en/02-architecture.md +12 -6
- package/docs/en/03-routing.md +15 -8
- package/docs/en/04-rendering.md +71 -59
- package/docs/en/05-islands.md +13 -8
- package/docs/en/06-caching.md +21 -7
- package/docs/en/07-configuration.md +69 -29
- package/docs/en/08-build.md +16 -10
- package/docs/en/09-dev-tools.md +24 -8
- package/docs/en/10-deployment.md +14 -14
- package/docs/en/11-migration.md +51 -16
- package/docs/en/12-dashboards-and-sessions.md +9 -4
- package/docs/en/README.md +10 -35
- package/package.json +48 -13
- package/src/build/tasks/client.mjs +91 -10
- package/src/build/tasks/icons.mjs +11 -1
- package/src/client/index.js +2 -2
- package/src/compile/codegen.js +4 -0
- package/src/compile/compile-all.js +12 -21
- package/src/compile/expr.js +5 -0
- package/src/compile/parse.js +64 -8
- package/src/compile/resolve.js +3 -0
- package/src/config/defaults.js +48 -5
- package/src/config/index.js +138 -27
- package/src/dev-server.mjs +26 -3
- package/src/http/cookies-entry.js +1 -0
- package/src/http/cookies.js +18 -0
- package/src/logo.png +0 -0
- package/src/migrate/apply.mjs +262 -0
- package/src/migrate/babel.mjs +79 -0
- package/src/migrate/classify.mjs +155 -0
- package/src/migrate/config.mjs +126 -0
- package/src/migrate/fs-walk.mjs +191 -0
- package/src/migrate/parse.mjs +26 -0
- package/src/migrate/scan.mjs +177 -0
- package/src/migrate/transform/expr-source.mjs +168 -0
- package/src/migrate/transform/island.mjs +67 -0
- package/src/migrate/transform/jsx-to-component.mjs +302 -0
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -0
- package/src/migrate/transform/page-split.mjs +435 -0
- package/src/migrate/write.mjs +81 -0
- package/src/migrate.mjs +171 -0
- package/src/server/auth/handoff.js +94 -11
- package/src/server/create-app.js +37 -10
- package/src/server/ejs-adapter.js +59 -0
- package/src/server/html-cache.js +178 -32
- package/src/server/image-optimizer.js +94 -26
- package/src/server/middleware/dev-gate.js +21 -8
- package/src/server/middleware/robots-txt.js +341 -0
- package/src/server/port-guard.js +255 -0
- package/src/server/prewarm.js +137 -51
- package/src/server/render.js +30 -10
- package/src/server/status-page.js +105 -4
- package/src/start.mjs +18 -3
- package/src/templates/layout.ejs +8 -28
- package/src/templates/layout.jsk +30 -0
- package/src/templates/layout.render.js +41 -0
- package/src/views/helpers/tags.js +86 -3
- package/types/build/resolve-peer.d.mts +13 -0
- package/types/client/dom.d.ts +55 -0
- package/types/client/form.d.ts +19 -0
- package/types/client/index.d.ts +20 -0
- package/types/client/registry.d.ts +53 -0
- package/types/client/safe-image.d.ts +19 -0
- package/types/client/shared-cookie.d.ts +82 -0
- package/types/client/store.d.ts +18 -0
- package/types/client/swap.d.ts +46 -0
- package/types/compile/codegen.d.ts +32 -0
- package/types/compile/compile-all.d.ts +42 -0
- package/types/compile/errors.d.ts +30 -0
- package/types/compile/expr.d.ts +67 -0
- package/types/compile/index.d.ts +10 -0
- package/types/compile/parse.d.ts +82 -0
- package/types/compile/resolve.d.ts +46 -0
- package/types/compile/scan-exports.d.ts +9 -0
- package/types/config/defaults.d.ts +477 -0
- package/types/config/index.d.ts +304 -0
- package/types/config/pattern.d.ts +38 -0
- package/types/http/control-flow.d.ts +45 -0
- package/types/http/cookies-entry.d.ts +5 -0
- package/types/http/cookies.d.ts +113 -0
- package/types/http/request-cache.d.ts +13 -0
- package/types/http/request-context.d.ts +67 -0
- package/types/http/shared-cookie.d.ts +73 -0
- package/types/index.d.ts +30 -0
- package/types/log.d.mts +153 -0
- package/types/server/admin/actions.d.ts +16 -0
- package/types/server/admin/auth.d.ts +52 -0
- package/types/server/admin/event-log.d.ts +38 -0
- package/types/server/admin/gate.d.ts +43 -0
- package/types/server/admin/inventory.d.ts +40 -0
- package/types/server/admin/mount.d.ts +6 -0
- package/types/server/admin/router.d.ts +6 -0
- package/types/server/admin/snapshot.d.ts +6 -0
- package/types/server/assets.d.ts +47 -0
- package/types/server/auth/handoff.d.ts +12 -0
- package/types/server/cache-deps.d.ts +16 -0
- package/types/server/cache-vary.d.ts +30 -0
- package/types/server/cloudflare.d.ts +163 -0
- package/types/server/create-app.d.ts +25 -0
- package/types/server/data-cache.d.ts +116 -0
- package/types/server/dev/devtools.d.ts +44 -0
- package/types/server/dev/report.d.ts +229 -0
- package/types/server/dev/socket.d.ts +17 -0
- package/types/server/dev/version-check.d.mts +15 -0
- package/types/server/ejs-adapter.d.ts +11 -0
- package/types/server/head-hints.d.ts +40 -0
- package/types/server/html-cache.d.ts +207 -0
- package/types/server/image-optimizer.d.ts +68 -0
- package/types/server/logs/access-middleware.d.ts +7 -0
- package/types/server/logs/file-sink.d.ts +17 -0
- package/types/server/logs/pipeline.d.ts +37 -0
- package/types/server/logs/s3-put.d.ts +85 -0
- package/types/server/logs/s3-sink.d.ts +26 -0
- package/types/server/metadata.d.ts +38 -0
- package/types/server/middleware/compression.d.ts +17 -0
- package/types/server/middleware/csrf.d.ts +4 -0
- package/types/server/middleware/dev-gate.d.ts +2 -0
- package/types/server/middleware/headers.d.ts +2 -0
- package/types/server/middleware/redirects.d.ts +2 -0
- package/types/server/middleware/robots-txt.d.ts +33 -0
- package/types/server/middleware/static-precompressed.d.ts +5 -0
- package/types/server/middleware/trailing-slash.d.ts +11 -0
- package/types/server/middleware/upstream-proxy.d.ts +21 -0
- package/types/server/og-image.d.ts +149 -0
- package/types/server/port-guard.d.ts +50 -0
- package/types/server/prewarm.d.ts +131 -0
- package/types/server/redis.d.ts +163 -0
- package/types/server/render.d.ts +101 -0
- package/types/server/router.d.ts +5 -0
- package/types/server/status-page.d.ts +24 -0
- package/types/server/upstream-limiter.d.ts +123 -0
- package/types/server/upstream-tracking.d.ts +42 -0
- package/types/shared/cookie-domain.d.ts +29 -0
- package/types/templates/layout.render.d.ts +7 -0
- package/types/version.d.mts +10 -0
- package/types/views/components/loader.d.ts +5 -0
- package/types/views/helpers/html.d.ts +39 -0
- package/types/views/helpers/tags.d.ts +127 -0
package/docs/en/05-islands.md
CHANGED
|
@@ -123,25 +123,30 @@ will still mount.
|
|
|
123
123
|
```
|
|
124
124
|
client/
|
|
125
125
|
├── entries/
|
|
126
|
-
│ ├── main.js
|
|
126
|
+
│ ├── main.js shared bootstrap on every page (or main.ts)
|
|
127
127
|
│ └── chart.js only on the pages that ask for it
|
|
128
128
|
└── islands/
|
|
129
|
-
├── counter.js
|
|
129
|
+
├── counter.ts .js or .ts
|
|
130
130
|
└── chart.js
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
**Every file** under `client/entries/*.js` **is an esbuild entry**.
|
|
134
|
-
is loaded by the layout on every page (if it is in the
|
|
135
|
-
are loaded only on the pages that ask for them
|
|
133
|
+
**Every file** under `client/entries/*.{js,ts,mts}` **is an esbuild entry**.
|
|
134
|
+
`main.js` (or `main.ts`) is loaded by the layout on every page (if it is in the
|
|
135
|
+
manifest). Extra entries are loaded only on the pages that ask for them. Two
|
|
136
|
+
extensions for the same stem (`main.js` + `main.ts`) fail the build.
|
|
136
137
|
|
|
137
138
|
```js
|
|
138
|
-
// controller
|
|
139
|
+
// controller — the manifest key is always *.js
|
|
139
140
|
return { view: "pages/markets", entries: ["chart.js"] };
|
|
140
141
|
```
|
|
141
142
|
|
|
142
143
|
The layout resolves every name in the `entries` array with `asset(entry)` and
|
|
143
|
-
emits a `<script type="module">`. The name is the manifest key
|
|
144
|
-
|
|
144
|
+
emits a `<script type="module">`. The name is the manifest key (`chart.js`);
|
|
145
|
+
even when the source is `chart.ts`, the unhashed key stays `.js`.
|
|
146
|
+
|
|
147
|
+
Shared `@/lib` modules imported on the server must stay **`.js`** — the Node
|
|
148
|
+
runtime does not resolve `.ts`; TypeScript is compiled only on the esbuild
|
|
149
|
+
client path.
|
|
145
150
|
|
|
146
151
|
Code splitting (`splitting: true`) is on: modules shared by two entries end up
|
|
147
152
|
in a common chunk and are not downloaded twice.
|
package/docs/en/06-caching.md
CHANGED
|
@@ -231,7 +231,12 @@ price is acceptable, because live fields such as prices are updated on the
|
|
|
231
231
|
client over WebSocket.
|
|
232
232
|
|
|
233
233
|
The store is an LRU: an accessed entry is moved to the end, and once the limit
|
|
234
|
-
(`cache().maxEntries`, 500 by default) is exceeded the oldest is evicted.
|
|
234
|
+
(`cache().maxEntries`, 500 by default) is exceeded the oldest is evicted. Config
|
|
235
|
+
may ask for more than 500; it **cannot exceed 800** — a higher value is clamped
|
|
236
|
+
to 800 with a warning. Separately, in-process HTML strings plus compressed
|
|
237
|
+
bodies **cannot exceed 256 MB**. A fat page or a `vary.host` copy that is still
|
|
238
|
+
under the count limit is evicted by this budget too. A single page larger than
|
|
239
|
+
256 MB is not stored; that response is still sent.
|
|
235
240
|
|
|
236
241
|
## What gets written to the cache
|
|
237
242
|
|
|
@@ -1145,9 +1150,9 @@ export default {
|
|
|
1145
1150
|
html: { "/": 60, "/news/:slug": 300 },
|
|
1146
1151
|
prewarm: {
|
|
1147
1152
|
onVisit: {
|
|
1148
|
-
perPage: 20, // at most this many links per page
|
|
1149
|
-
concurrency: 2, //
|
|
1150
|
-
rps:
|
|
1153
|
+
perPage: 20, // at most this many links per page; ceiling 20
|
|
1154
|
+
concurrency: 2, // ceiling 2
|
|
1155
|
+
rps: 2, // ceiling 2; 0 is clamped to 2 as well
|
|
1151
1156
|
},
|
|
1152
1157
|
},
|
|
1153
1158
|
};
|
|
@@ -1163,7 +1168,15 @@ Rules:
|
|
|
1163
1168
|
degraded or `no-store` responses do not extract links.
|
|
1164
1169
|
- The warmer's own UA (`brand.prewarmUserAgent`) does not trigger — no crawl
|
|
1165
1170
|
loop.
|
|
1166
|
-
- Paths that are already fresh are not enqueued.
|
|
1171
|
+
- Paths that are already fresh are not enqueued. Real keys look like
|
|
1172
|
+
`h=host|/path?`; the check sees the vary prefix and the trailing `?`.
|
|
1173
|
+
With `vary.host`, only this request's host counts as fresh.
|
|
1174
|
+
- The pending queue holds at most 64 paths; links beyond that are left for a
|
|
1175
|
+
later response.
|
|
1176
|
+
- `perPage` 20, `rps` 2 and `concurrency` 2 are ceilings. A higher value (and
|
|
1177
|
+
`rps: 0`) is clamped with a warning. Warm requests stay on loopback; when
|
|
1178
|
+
`vary.host` is on, the public host is sent as `x-forwarded-host`, so a second
|
|
1179
|
+
`h=127.0.0.1` entry is not created.
|
|
1167
1180
|
- `nofollow`, `target="_blank"`, `data-no-prefetch`, `prewarmSkip` and
|
|
1168
1181
|
`navigation.exclude` share the same exemptions as Speculation Rules.
|
|
1169
1182
|
- Query strings are not warmed (default cache policy treats query as dynamic).
|
|
@@ -1308,8 +1321,9 @@ The requests go out with the headers `user-agent: jskelet-prewarm`
|
|
|
1308
1321
|
(`brand.prewarmUserAgent`) and `accept-encoding: br, gzip`; the second one so
|
|
1309
1322
|
that the compressed body enters the cache too.
|
|
1310
1323
|
|
|
1311
|
-
If
|
|
1312
|
-
|
|
1324
|
+
If the dev gate is on, the warm-up carries the token as a cookie; otherwise the
|
|
1325
|
+
gate returns 404 for all pages and the cache never fills. `DEV_TOKEN` alone
|
|
1326
|
+
does not turn the gate on.
|
|
1313
1327
|
|
|
1314
1328
|
The request list in the dev panel and the terminal filter out requests carrying
|
|
1315
1329
|
`prewarmUserAgent`: so that hundreds of warm-up requests do not flood the view.
|
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
lang: "tr",
|
|
56
56
|
},
|
|
57
57
|
|
|
58
|
-
layout: "views/layout.
|
|
58
|
+
layout: "views/layout.jsk",
|
|
59
59
|
routes: ["./routes/10-pages.mjs", "./routes/99-catch-all.mjs"],
|
|
60
60
|
trailingSlash: false,
|
|
61
61
|
|
|
@@ -213,27 +213,32 @@ cross-subdomain handoff bridge for a short session id.
|
|
|
213
213
|
|
|
214
214
|
| Field | Type | Default | Meaning |
|
|
215
215
|
| --- | --- | --- | --- |
|
|
216
|
-
| `crossSubdomainHandoff` | `boolean \| object` | `false` |
|
|
216
|
+
| `crossSubdomainHandoff` | `boolean \| object` | `false` | When on: `POST /_jskelet/auth/handoff` + `?handoff=` redeem. Object: `allowedCookieNames` (required), `ttlSeconds?`, `path?`, `maxValueBytes?`, `maxPendingTickets?`, `maxMintsPerIpPerMinute?` |
|
|
217
217
|
|
|
218
218
|
```js
|
|
219
219
|
auth: {
|
|
220
|
-
crossSubdomainHandoff: {
|
|
220
|
+
crossSubdomainHandoff: {
|
|
221
|
+
allowedCookieNames: ["sid"],
|
|
222
|
+
ttlSeconds: 60,
|
|
223
|
+
},
|
|
221
224
|
},
|
|
222
225
|
```
|
|
223
226
|
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
The mint endpoint is mounted **after** the CSRF middleware (origin checks).
|
|
228
|
+
Cookie names outside the allowlist or that are not RFC 6265 tokens get 400.
|
|
229
|
+
Details: [12-dashboards-and-sessions.md](./12-dashboards-and-sessions.md).
|
|
226
230
|
|
|
227
231
|
## `layout`
|
|
228
232
|
|
|
229
233
|
**Type:** `string` — **Default:** none (automatic resolution)
|
|
230
234
|
|
|
231
|
-
Path of the layout `.ejs`
|
|
232
|
-
**parent directory of the views directory**, so with the default
|
|
233
|
-
`"views/custom.
|
|
235
|
+
Path of the layout file (`.jsk` or legacy `.ejs`). The value given is resolved
|
|
236
|
+
relative to the **parent directory of the views directory**, so with the default
|
|
237
|
+
`views`, `"views/custom.jsk"` → `<root>/views/custom.jsk`.
|
|
234
238
|
|
|
235
|
-
If not given, in order: `views/layout.ejs`
|
|
236
|
-
framework's
|
|
239
|
+
If not given, in order: `views/layout.jsk`, `views/layout.ejs` (legacy),
|
|
240
|
+
otherwise the framework's `src/templates/layout.jsk` default. Details:
|
|
241
|
+
[04-rendering.md](./04-rendering.md).
|
|
237
242
|
|
|
238
243
|
## `routes`
|
|
239
244
|
|
|
@@ -293,6 +298,21 @@ static: {
|
|
|
293
298
|
}
|
|
294
299
|
```
|
|
295
300
|
|
|
301
|
+
## `devGate`
|
|
302
|
+
|
|
303
|
+
**Type:** `boolean` — **Default:** `false`
|
|
304
|
+
|
|
305
|
+
Hides an environment that is not public yet. **`DEV_TOKEN` alone does not lock
|
|
306
|
+
the site.** A shared task definition can carry the same variable into
|
|
307
|
+
production; visitors are not required to present a token, and the site stays
|
|
308
|
+
open.
|
|
309
|
+
|
|
310
|
+
Turn the gate on with `devGate: true` or `DEV_GATE=1`. Then a request without
|
|
311
|
+
the token gets a 404. `DEV_GATE=0` also turns off a gate the config enabled.
|
|
312
|
+
If the token is empty, requests still pass even when the gate is on.
|
|
313
|
+
|
|
314
|
+
Details: [09-dev-tools.md](./09-dev-tools.md).
|
|
315
|
+
|
|
296
316
|
## `devGateBypass`
|
|
297
317
|
|
|
298
318
|
**Type:** `string[]` — **Default:**
|
|
@@ -300,8 +320,7 @@ static: {
|
|
|
300
320
|
|
|
301
321
|
**Exact** paths the dev gate never closes off under any circumstances (not a
|
|
302
322
|
prefix, an exact match). This is so that the health check and the robots files
|
|
303
|
-
stay reachable
|
|
304
|
-
replaces the default.
|
|
323
|
+
stay reachable while the gate is on. If provided, it replaces the default.
|
|
305
324
|
|
|
306
325
|
Details: [09-dev-tools.md](./09-dev-tools.md).
|
|
307
326
|
|
|
@@ -335,7 +354,7 @@ field reference.
|
|
|
335
354
|
| `trustProxy` | `boolean` | `true` | Express's `trust proxy` setting. Needed behind a reverse proxy for the correct protocol and client IP. |
|
|
336
355
|
| `cookieSecret` | `string \| null` | `null` | The signed cookie secret. When absent, `JSKELET_SECRET` is read. |
|
|
337
356
|
| `csrf.enabled` | `boolean` | `true` | The origin / `Sec-Fetch-Site` check. |
|
|
338
|
-
| `csrf.token` | `boolean` | `false` | The double-submit token layer. |
|
|
357
|
+
| `csrf.token` | `boolean` | `false` | The double-submit token layer. **Turn on** for cookie-session forms. |
|
|
339
358
|
| `csrf.allowedOrigins` | `string[]` | `[]` | Origins accepted alongside our own host. |
|
|
340
359
|
| `csrf.exclude` | `string[]` | `[]` | Paths exempt from the check; `source` pattern syntax. |
|
|
341
360
|
| `csrf.cookieName` | `string` | `"csrf_token"` | Name of the token cookie. |
|
|
@@ -343,14 +362,17 @@ field reference.
|
|
|
343
362
|
| `csrf.headerName` | `string` | `"x-csrf-token"` | Header the token is also accepted in. |
|
|
344
363
|
|
|
345
364
|
`trustProxy` should be **turned off** on a server exposed directly to the
|
|
346
|
-
internet: while it is on, a client can forge
|
|
347
|
-
|
|
365
|
+
internet: while it is on, a client can forge `X-Forwarded-For` /
|
|
366
|
+
`X-Forwarded-Proto` / Host, and rate limits, admin IP allowlists, Secure
|
|
367
|
+
cookies, and cache `vary.host` see the wrong address. Behind a reverse proxy
|
|
368
|
+
(nginx, Caddy, Cloudflare), `true` is the right default.
|
|
348
369
|
|
|
349
370
|
The CSRF check only rejects requests that are **known** to be cross-site — when
|
|
350
371
|
`Origin` does not match or `Sec-Fetch-Site: cross-site` arrives. If neither is
|
|
351
372
|
present the request passes, because browsers always send `Origin` on a
|
|
352
|
-
cross-origin POST while webhooks never do.
|
|
353
|
-
|
|
373
|
+
cross-origin POST while webhooks never do. For cookie-session dashboards,
|
|
374
|
+
enable `csrf.token: true` and `csrfField()` as a second layer; put webhook
|
|
375
|
+
paths in `csrf.exclude`.
|
|
354
376
|
|
|
355
377
|
## `navigation`
|
|
356
378
|
|
|
@@ -433,7 +455,8 @@ body > footer { view-transition-name: site-footer; }
|
|
|
433
455
|
::view-transition-new(root) { animation-duration: 180ms; }
|
|
434
456
|
```
|
|
435
457
|
|
|
436
|
-
|
|
458
|
+
Copy the Tailwind `@source` directives and view-transition CSS into your own
|
|
459
|
+
app's `styles/globals.css`; the blocks above are a starting point.
|
|
437
460
|
|
|
438
461
|
**If you use CSP**, the rules are emitted as an inline
|
|
439
462
|
`<script type="speculationrules">`; your `script-src` policy needs to allow it.
|
|
@@ -555,8 +578,9 @@ When `remote.allowHosts` is set, also proxies remote images at runtime
|
|
|
555
578
|
|
|
556
579
|
If `false` is given, neither surface runs. The build step requires `sharp` and
|
|
557
580
|
never runs on a watch pass. With remote enabled, `sharp` is also needed at
|
|
558
|
-
**runtime**; without it the optimizer 302-redirects to the source URL.
|
|
559
|
-
|
|
581
|
+
**runtime**; without it the optimizer 302-redirects to the source URL. Fetch
|
|
582
|
+
does not auto-follow redirects: every hop is re-checked against `allowHosts`
|
|
583
|
+
and private addresses. Details: [08-build.md](./08-build.md).
|
|
560
584
|
|
|
561
585
|
```js
|
|
562
586
|
images: {
|
|
@@ -588,7 +612,10 @@ that is not in the list returns `undefined` instead of crashing.
|
|
|
588
612
|
clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
589
613
|
```
|
|
590
614
|
|
|
591
|
-
**Do not put secrets here** — the values sit in the bundle in plain text.
|
|
615
|
+
**Do not put secrets here** — the values sit in the bundle in plain text. Keys
|
|
616
|
+
whose names look secret-like (`SECRET`, `PASSWORD`, `TOKEN`, `API_KEY`,
|
|
617
|
+
`PRIVATE`, …) are **rejected at build time** (`PUBLIC` / `PUBLISHABLE` names
|
|
618
|
+
are exempt).
|
|
592
619
|
|
|
593
620
|
## `headers()`
|
|
594
621
|
|
|
@@ -598,6 +625,7 @@ clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
|
598
625
|
Response headers by path pattern. The framework only writes long-lived cache
|
|
599
626
|
headers for static files; every other header (CSP, COOP, HSTS,
|
|
600
627
|
X-Frame-Options…) comes from here and takes precedence over the defaults.
|
|
628
|
+
Production sites should at least define the security headers below.
|
|
601
629
|
|
|
602
630
|
**All** matching rules are applied (unlike redirects, it does not stop at the
|
|
603
631
|
first match), in order; if two rules write the same header, the later one wins.
|
|
@@ -612,11 +640,18 @@ async headers() {
|
|
|
612
640
|
source: "/:path*",
|
|
613
641
|
headers: [
|
|
614
642
|
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
643
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
615
644
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
645
|
+
{
|
|
646
|
+
key: "Permissions-Policy",
|
|
647
|
+
value: "camera=(), microphone=(), geolocation=()",
|
|
648
|
+
},
|
|
616
649
|
{
|
|
617
650
|
key: "Content-Security-Policy",
|
|
618
|
-
value: "default-src 'self'; img-src 'self' https://cdn.example.com data
|
|
651
|
+
value: "default-src 'self'; img-src 'self' https://cdn.example.com data:; script-src 'self'",
|
|
619
652
|
},
|
|
653
|
+
// Only when you terminate HTTPS yourself:
|
|
654
|
+
// { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
|
|
620
655
|
],
|
|
621
656
|
},
|
|
622
657
|
{
|
|
@@ -751,6 +786,10 @@ raising this number burns through memory quickly; trying to solve a site with
|
|
|
751
786
|
tens of thousands of paths from here is the wrong layer — the right place is
|
|
752
787
|
`cache().data`.
|
|
753
788
|
|
|
789
|
+
**Ceiling 800.** A higher value is clamped to 800 with a warning at load.
|
|
790
|
+
In-process HTML plus compressed bodies also cannot exceed 256 MB; config
|
|
791
|
+
cannot raise that budget.
|
|
792
|
+
|
|
754
793
|
### `cache().data`
|
|
755
794
|
|
|
756
795
|
The upstream data cache (`withDataCache`). Details:
|
|
@@ -758,7 +797,7 @@ The upstream data cache (`withDataCache`). Details:
|
|
|
758
797
|
|
|
759
798
|
| Field | Type | Default | Meaning |
|
|
760
799
|
| --- | --- | --- | --- |
|
|
761
|
-
| `maxEntries` | `number` | `10000` | The LRU entry limit. The limit is high because JSON is tens of times smaller than HTML. |
|
|
800
|
+
| `maxEntries` | `number` | `10000` | The LRU entry limit. The limit is high because JSON is tens of times smaller than HTML. **Ceiling 20,000**; a higher value is clamped with a warning. |
|
|
762
801
|
| `staleFactor` | `number` | `10` | For how many TTLs an entry stays usable after the TTL expired. `0` → no stale serving. |
|
|
763
802
|
|
|
764
803
|
### `cache().trackUpstream`
|
|
@@ -1003,13 +1042,13 @@ prewarm: {
|
|
|
1003
1042
|
| Field | Type | Default | Meaning |
|
|
1004
1043
|
| --- | --- | --- | --- |
|
|
1005
1044
|
| `onVisit` | `true \| false \| object` | off | Visit-driven warming |
|
|
1006
|
-
| `onVisit.perPage` | `number` | `20` | At most how many links per page (top to bottom) |
|
|
1007
|
-
| `onVisit.concurrency` | `number` |
|
|
1008
|
-
| `onVisit.rps` | `number` |
|
|
1045
|
+
| `onVisit.perPage` | `number` | `20` | At most how many links per page (top to bottom). **Ceiling 20** |
|
|
1046
|
+
| `onVisit.concurrency` | `number` | `2` | Parallel workers. **Ceiling 2** |
|
|
1047
|
+
| `onVisit.rps` | `number` | `2` | Requests per second cap. **Ceiling 2**; `0` is clamped to 2 as well |
|
|
1009
1048
|
|
|
1010
1049
|
```js
|
|
1011
1050
|
prewarm: {
|
|
1012
|
-
onVisit: { perPage: 20, rps:
|
|
1051
|
+
onVisit: { perPage: 20, rps: 2 },
|
|
1013
1052
|
}
|
|
1014
1053
|
```
|
|
1015
1054
|
|
|
@@ -1106,10 +1145,11 @@ and no warning is printed.
|
|
|
1106
1145
|
| Variable | Who reads it | Default | Meaning |
|
|
1107
1146
|
| --- | --- | --- | --- |
|
|
1108
1147
|
| `NODE_ENV` | everywhere | `production` (start/build), `development` (dev) | Determines the dev overlay, EJS cache, manifest re-reading, route error behaviour and prewarm defaults. `jskelet dev` sets it itself — `cross-env` is not needed. |
|
|
1109
|
-
| `PORT` | `startServer` | `3000` | Port to listen on |
|
|
1148
|
+
| `PORT` | `startServer` | `3000` | Port to listen on. If busy, the process refuses to start; `jskelet start|dev --murder` kills the listener |
|
|
1110
1149
|
| `HOST` | `startServer` | `::` | Interface to bind to. The default listens dual-stack (IPv6 + IPv4); it falls back to `0.0.0.0` where IPv6 is unavailable |
|
|
1111
1150
|
| `JSKELET_SECRET` | `jskelet/cookies` | — | The signed cookie secret. Read when `security.cookieSecret` is not set; if neither exists, the signed cookie API throws. [12](./12-dashboards-and-sessions.md) |
|
|
1112
|
-
| `
|
|
1151
|
+
| `DEV_GATE` | `devGate` | off | `1` turns the gate on, `0` turns it off even when config enabled it. `DEV_TOKEN` alone does not turn it on. [09](./09-dev-tools.md) |
|
|
1152
|
+
| `DEV_TOKEN` | `devGate`, `prewarm` | — | The secret expected while the gate is on. If it is missing, or the gate is off, the site stays public. Prewarming carries the token as a cookie only while the gate is on. [09](./09-dev-tools.md) |
|
|
1113
1153
|
| `JSKELET_ADMIN` | `createApp` | — | When set, turns the admin panel on; `0` turns off a panel enabled in the config. The env wins because the panel is usually opened once during an incident. [06](./06-caching.md) |
|
|
1114
1154
|
| `JSKELET_LOG_BUCKET` | `logs.s3` | — | Log target: bucket or `bucket/prefix` path. With credentials, the sink turns on automatically |
|
|
1115
1155
|
| `JSKELET_S3_BUCKET` | `logs.s3` | — | Bucket when `JSKELET_LOG_BUCKET` is unset; joins with `JSKELET_S3_KEY_PREFIX` |
|
package/docs/en/08-build.md
CHANGED
|
@@ -174,8 +174,10 @@ a new utility is written. Changes are coalesced over 120 ms.
|
|
|
174
174
|
|
|
175
175
|
## Client JS — esbuild
|
|
176
176
|
|
|
177
|
-
Every
|
|
178
|
-
|
|
177
|
+
Every source file under `client/entries/*.{js,ts,mts}` is an entry (no `.tsx`).
|
|
178
|
+
The manifest key is always `*.js` (`main.ts` → `main.js`). Multiple extensions
|
|
179
|
+
for the same stem fail the build. If the directory does not exist or is empty,
|
|
180
|
+
the step is skipped.
|
|
179
181
|
|
|
180
182
|
esbuild settings:
|
|
181
183
|
|
|
@@ -185,7 +187,7 @@ esbuild settings:
|
|
|
185
187
|
| `format` | `esm` | `type="module"` scripts |
|
|
186
188
|
| `target` | `chrome111`, `edge111`, `firefox111`, `safari16.4` | The lower bound of the ESM + dynamic import + `IntersectionObserver` island model; transpiling to anything older grows the output without winning a single visitor |
|
|
187
189
|
| `minify` | `true` | — |
|
|
188
|
-
| `sourcemap` | `
|
|
190
|
+
| `sourcemap` | only when `NODE_ENV=development` | Production builds do not publish `.map` files under `public/assets` |
|
|
189
191
|
| `entryNames` | `[name].[hash]` | `immutable` cache |
|
|
190
192
|
| `chunkNames` | `chunks/[name].[hash]` | — |
|
|
191
193
|
| `legalComments` | `none` | — |
|
|
@@ -196,17 +198,19 @@ The output lands under `public/assets/js/` and is cleaned first on every pass.
|
|
|
196
198
|
### The `@/` alias
|
|
197
199
|
|
|
198
200
|
On the esbuild side, `@/` resolves to the project root and extension completion
|
|
199
|
-
is performed (`.js`, `.mjs`, `.json`, `/index.js`).
|
|
200
|
-
`alias-hooks.mjs`
|
|
201
|
-
|
|
201
|
+
is performed (`.js`, `.mjs`, `.ts`, `.mts`, `.json`, `/index.js`, `/index.ts`).
|
|
202
|
+
Node `alias-hooks.mjs` resolves only `.js` / `.mjs` / `.json` on the server, so
|
|
203
|
+
shared `@/lib` modules must stay `.js`. Client-only `.ts` imports work on the
|
|
204
|
+
esbuild path.
|
|
202
205
|
|
|
203
206
|
### Inlining `clientEnv`
|
|
204
207
|
|
|
205
208
|
There is no `process` in the browser; modules shared with the server still read
|
|
206
209
|
`process.env`. The keys declared through `config.clientEnv` plus `NODE_ENV` are
|
|
207
210
|
defined as a single object at build time, which means that reading a key not in
|
|
208
|
-
the list returns `undefined` instead of crashing.
|
|
209
|
-
|
|
211
|
+
the list returns `undefined` instead of crashing. Secret-like key names
|
|
212
|
+
(`SECRET`, `API_KEY`, …) fail the build; names containing `PUBLIC` /
|
|
213
|
+
`PUBLISHABLE` are exempt. Details: [07-configuration.md](./07-configuration.md).
|
|
210
214
|
|
|
211
215
|
### Manifest keys
|
|
212
216
|
|
|
@@ -285,7 +289,7 @@ Local file names:
|
|
|
285
289
|
`0 0 256 256` (recommended for Phosphor / `icon()` compatibility).
|
|
286
290
|
- The scanned directories default to `views`, `client`, `routes`, `lib`,
|
|
287
291
|
`features`, `shared`; they can be changed with `icons.scan`. Scanned
|
|
288
|
-
extensions: `.ejs`, `.jsk`, `.js`, `.mjs`.
|
|
292
|
+
extensions: `.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
|
|
289
293
|
- Weights: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. An
|
|
290
294
|
unrecognised weight counts as `regular`.
|
|
291
295
|
|
|
@@ -355,7 +359,9 @@ and `image()` falls back to the original file. It never runs on a watch pass.
|
|
|
355
359
|
When `images.remote.allowHosts` is set, `createApp` mounts `/_jskelet/image`.
|
|
356
360
|
CMS / CDN covers never enter the build, so `image()` rewrites those host URLs to
|
|
357
361
|
`?url=&w=`; the endpoint encodes webp with sharp and stores files under
|
|
358
|
-
`.jskelet/image-cache/`.
|
|
362
|
+
`.jskelet/image-cache/`. Upstream fetch follows redirects manually: every hop is
|
|
363
|
+
re-checked against the allowlist and private IP / DNS rules (open-redirect SSRF
|
|
364
|
+
is closed). Details: [07-configuration.md](./07-configuration.md).
|
|
359
365
|
|
|
360
366
|
## Precompress
|
|
361
367
|
|
package/docs/en/09-dev-tools.md
CHANGED
|
@@ -23,6 +23,11 @@ jskelet dev
|
|
|
23
23
|
(suppresses the build banner) and, if a TTY is present, `JSKELET_COLOR=1`
|
|
24
24
|
(forces color on piped output).
|
|
25
25
|
|
|
26
|
+
If the listen port (`PORT`, default `3000`) is already taken, the server
|
|
27
|
+
**does not start**; the error line includes the PID and a `--murder` hint.
|
|
28
|
+
`jskelet dev --murder` kills the listener and binds the same port (for a
|
|
29
|
+
process left running in another terminal).
|
|
30
|
+
|
|
26
31
|
Startup order: banner → build steps → server ready → `Ready` summary. The
|
|
27
32
|
summary is printed once both the build and the server are ready; otherwise it
|
|
28
33
|
got buried among the build lines arriving afterwards.
|
|
@@ -75,7 +80,7 @@ WATCH_DIRS = [
|
|
|
75
80
|
The `jskelet.config.mjs` file itself is watched as well: when the config
|
|
76
81
|
changes, both the server and the build must come up with the new settings.
|
|
77
82
|
|
|
78
|
-
Watched extensions: `.js`, `.mjs`, `.json`, `.ejs`.
|
|
83
|
+
Watched extensions: `.js`, `.mjs`, `.json`, `.jsk`, `.ejs`.
|
|
79
84
|
|
|
80
85
|
`views` is watched too, because most components live in
|
|
81
86
|
`views/components/**.js` and, since those modules are imported into the server
|
|
@@ -298,11 +303,20 @@ production process.
|
|
|
298
303
|
|
|
299
304
|
## Dev gate — `DEV_TOKEN`
|
|
300
305
|
|
|
301
|
-
To hide an environment that is not public yet
|
|
302
|
-
|
|
306
|
+
To hide an environment that is not public yet. **The framework does not require
|
|
307
|
+
the token:** a `DEV_TOKEN` sitting in the environment does not lock the site.
|
|
308
|
+
You turn the gate on.
|
|
303
309
|
|
|
304
310
|
```bash
|
|
305
|
-
DEV_TOKEN=a-long-random-string npm start
|
|
311
|
+
DEV_GATE=1 DEV_TOKEN=a-long-random-string npm start
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The same thing from config:
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
export default {
|
|
318
|
+
devGate: true,
|
|
319
|
+
};
|
|
306
320
|
```
|
|
307
321
|
|
|
308
322
|
Access:
|
|
@@ -323,10 +337,12 @@ Behavior:
|
|
|
323
337
|
`/site.webmanifest`, `/favicon.ico`. If your health check lives at a different
|
|
324
338
|
path, remember to add it to this list, otherwise your orchestrator will see a
|
|
325
339
|
404.
|
|
326
|
-
-
|
|
327
|
-
production
|
|
328
|
-
|
|
329
|
-
|
|
340
|
+
- While `devGate` is off (the default) or `DEV_TOKEN` is empty, the middleware
|
|
341
|
+
passes the request through. A `DEV_TOKEN` that leaked into a production task
|
|
342
|
+
does not ask visitors for a token; startup prints a warning.
|
|
343
|
+
- `DEV_GATE=0` turns the gate off even when config says `devGate: true`.
|
|
344
|
+
- While the gate is on, warming carries the token as a cookie; without it every
|
|
345
|
+
page gets a 404 and the cache never fills up
|
|
330
346
|
([06-caching.md](./06-caching.md)).
|
|
331
347
|
|
|
332
348
|
In the middleware chain the gate sits after `headers` and **before**
|
package/docs/en/10-deployment.md
CHANGED
|
@@ -23,6 +23,10 @@ runs the build itself. In a Docker image the build has already happened, so this
|
|
|
23
23
|
is a no-op; the point is that someone running `npm start` directly does not end
|
|
24
24
|
up with an unstyled page.
|
|
25
25
|
|
|
26
|
+
If the listen port is already taken, the process **does not start** (PID + hint).
|
|
27
|
+
`jskelet start --murder` kills that listener and binds — useful for a leftover
|
|
28
|
+
dev process; production orchestrators usually do not need it.
|
|
29
|
+
|
|
26
30
|
When the server is ready it prints a single line:
|
|
27
31
|
|
|
28
32
|
```
|
|
@@ -47,7 +51,7 @@ considering in production:
|
|
|
47
51
|
| `HOST` | `0.0.0.0` | Only if you need to listen on IPv4 alone; the `::` default already listens dual-stack |
|
|
48
52
|
| `PREWARM_MAX` | Depends on site size | Number of pages warmed at startup |
|
|
49
53
|
| `PREWARM_INTERVAL_SECONDS` | `0` or a long value | If you want to keep never-visited pages warm |
|
|
50
|
-
| `DEV_TOKEN` | Staging only | Hides an environment that is not public yet |
|
|
54
|
+
| `DEV_GATE` + `DEV_TOKEN` | Staging only | Hides an environment that is not public yet. The token alone does not lock the site |
|
|
51
55
|
| `JSKELET_S3_*` | If you write access logs to S3 | Bucket + credentials; details in [07](./07-configuration.md) |
|
|
52
56
|
|
|
53
57
|
When a file or S3 sink is enabled in production, the HTTP access log middleware
|
|
@@ -64,7 +68,8 @@ Using both sources together blurs which value actually applies; not shipping a
|
|
|
64
68
|
`.env` in the prod image is the cleanest option.
|
|
65
69
|
|
|
66
70
|
**Secret keys must not go into the `clientEnv` list:** those values are embedded
|
|
67
|
-
into the client bundle as plain text ([08-build.md](./08-build.md)).
|
|
71
|
+
into the client bundle as plain text ([08-build.md](./08-build.md)). Secret-like
|
|
72
|
+
names (`SECRET`, `API_KEY`, …) now fail the build.
|
|
68
73
|
|
|
69
74
|
## Docker
|
|
70
75
|
|
|
@@ -152,16 +157,11 @@ The build stage produces these itself with `npx jskelet build`.
|
|
|
152
157
|
|
|
153
158
|
The examples in this repo pull jskelet with `"jskelet": "file:../.."` rather
|
|
154
159
|
than from npm. In tools like Coolify, Railway or Render, if you set the "base
|
|
155
|
-
directory" to `examples/
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
docker build -f examples/marketing/Dockerfile -t jskelet-marketing .
|
|
163
|
-
docker run --rm -p 3000:3000 -e SITE_URL=https://example.com jskelet-marketing
|
|
164
|
-
```
|
|
160
|
+
directory" to `examples/blog`, the build context becomes only that directory,
|
|
161
|
+
`../..` falls outside the context, and installation fails at `npm ci`. The
|
|
162
|
+
correct setting: **base directory `/`** (the repo root) and adapt the
|
|
163
|
+
multi-stage Dockerfile above to the app directory — or install jskelet as a
|
|
164
|
+
normal npm dependency and use the app directory as the context.
|
|
165
165
|
|
|
166
166
|
In your own application jskelet will be an ordinary dependency, so this
|
|
167
167
|
constraint does not apply; the multi-stage image above is enough.
|
|
@@ -171,7 +171,7 @@ constraint does not apply; the multi-stage image above is enough.
|
|
|
171
171
|
The framework does **not** add a ready-made health check endpoint; you have to
|
|
172
172
|
put it in your own route. Since the default `devGateBypass` list contains
|
|
173
173
|
`/api/healthcheck`, using that name is the least surprising option: it stays
|
|
174
|
-
reachable even
|
|
174
|
+
reachable even while the dev gate is on.
|
|
175
175
|
|
|
176
176
|
```js
|
|
177
177
|
// routes/00-health.mjs
|
|
@@ -328,7 +328,7 @@ goes up, the work per request drops to almost zero.
|
|
|
328
328
|
- [ ] `hooks.prewarmPaths()` puts the most important pages first
|
|
329
329
|
- [ ] CSP and security headers are defined in `headers()`
|
|
330
330
|
- [ ] A health check endpoint exists and is in the `devGateBypass` list
|
|
331
|
-
- [ ] `
|
|
331
|
+
- [ ] Staging has `DEV_GATE=1` and `DEV_TOKEN`; production leaves the gate **off**
|
|
332
332
|
- [ ] The reverse proxy forwards `Accept-Encoding` and does not do its own
|
|
333
333
|
compression
|
|
334
334
|
- [ ] There are no secret keys in the `clientEnv` list
|