@webjsdev/cli 0.10.14 → 0.10.16
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/package.json
CHANGED
|
@@ -96,7 +96,7 @@ self-review loop.
|
|
|
96
96
|
## Framework rules
|
|
97
97
|
|
|
98
98
|
- No build step: ES modules served directly.
|
|
99
|
-
- **Erasable TypeScript only.** Node 24+ strips types via
|
|
99
|
+
- **Erasable TypeScript only.** The runtime (Node 24+ or Bun) strips types via
|
|
100
100
|
`module.stripTypeScriptTypes` (whitespace replacement, byte-exact position
|
|
101
101
|
preservation, no sourcemap). The scaffold's `tsconfig.json` sets
|
|
102
102
|
`erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace`
|
|
@@ -144,14 +144,18 @@ self-review loop.
|
|
|
144
144
|
`${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in
|
|
145
145
|
`firstUpdated`) instead of Lit's `classMap` / `styleMap` / `ref` / `when` /
|
|
146
146
|
`choose` / `guard`.
|
|
147
|
-
- Use Context for cross-component data
|
|
147
|
+
- Use Context for cross-component data. For async data in a component, prefer
|
|
148
|
+
an `async render()` (`const u = await getUser(this.uid)`, awaited at SSR so
|
|
149
|
+
the data is in the first paint); keep `Task` for genuinely client-only data.
|
|
148
150
|
- **Progressive enhancement is the default.** Pages AND every web component
|
|
149
151
|
are SSR'd to real HTML. Write components so the first paint is the right
|
|
150
152
|
content. Read SSR-meaningful defaults in `constructor()`. `connectedCallback`
|
|
151
153
|
is never called on the server, so anything there only runs after
|
|
152
154
|
hydration. Initial data for components comes from the page function
|
|
153
|
-
(server-side fetch plus pass as attribute/property)
|
|
154
|
-
in
|
|
155
|
+
(server-side fetch plus pass as attribute/property) OR from an `async
|
|
156
|
+
render()` in the component itself (preferred over prop-drilling;
|
|
157
|
+
`renderFallback()` is the optional re-fetch loading state, never first
|
|
158
|
+
paint), NOT from `fetch` calls in `connectedCallback`. For write-paths, prefer `<form action=...>` plus
|
|
155
159
|
server action over `fetch` plus click handler. The framework upgrades plain
|
|
156
160
|
forms to partial-swap submissions automatically.
|
|
157
161
|
- **Client navigation is auto-magic.** Real `<a href>` and `<form action>`
|
package/templates/.cursorrules
CHANGED
|
@@ -102,13 +102,13 @@ self-review loop.
|
|
|
102
102
|
## Framework rules
|
|
103
103
|
|
|
104
104
|
- No build step: source files are served as ES modules
|
|
105
|
-
- **Erasable TypeScript only.**
|
|
105
|
+
- **Erasable TypeScript only.** The runtime strips types via `module.stripTypeScriptTypes` (Node's built-in, or `amaro` on Bun) (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback.
|
|
106
106
|
- **Tailwind-first styling.** Tailwind utilities are the strong default for pages AND light-DOM components (the default DOM mode): layout, spacing, color (via `@theme` tokens), typography, borders, radius, shadows, interaction states. Light DOM does not scope, so utilities apply directly. The lit reflex to scope CSS (`static styles = css`) or write an inline `<style>` with semantic class names (`.hero`, `.card`) in a light-DOM component is wrong: the scoped block needs `static shadow = true`, and inline class names leak globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper returning an `html` fragment, not a CSS class. Reserve raw CSS for the allowlist (design tokens / `@theme`, `@property` + `@keyframes`, `::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` / gradients); when unavoidable in a light-DOM component, prefix every class selector with the component tag.
|
|
107
107
|
- Shadow-DOM components opt in with `static shadow = true` and use `static styles = css` for scoped CSS, not inline styles. That is the right home for scoped CSS.
|
|
108
108
|
- One function per server action file (*.server.ts)
|
|
109
109
|
- Components must call customElements.define('tag', Class)
|
|
110
110
|
- Server-only code (@prisma/client, node:*, anything that needs Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap the access in a .server.{js,ts} file; the framework rewrites that import into an RPC stub for the browser. lib/ holds both server-only infra (lib/prisma.server.ts) and browser-safe utilities (lib/utils/cn.ts with cn); follow the same rule per file: if a lib/ file needs Node APIs, only import it from server-only files.
|
|
111
111
|
- Directives are deliberately minimal: only `unsafeHTML`, `live`, and `repeat` ship. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported. Use plain template-literal expressions (`class=${cond ? 'a' : 'b'}`, `${cond ? a : b}`) and lifecycle hooks (`this.query('#el')` in `firstUpdated`) instead.
|
|
112
|
-
- **Progressive enhancement is the default.** Pages AND every web component are SSR'd to real HTML. Write components so the first paint is the right content. Read SSR-meaningful defaults in `constructor()`. `connectedCallback` is never called on the server, so anything there only runs after hydration. Initial data for components comes from the page function (server-side fetch plus pass as attribute/property), NOT from `fetch` calls in `connectedCallback`. For write-paths, prefer `<form action=...>` plus server action over `fetch` plus click handler. The framework upgrades plain forms to partial-swap submissions automatically.
|
|
112
|
+
- **Progressive enhancement is the default.** Pages AND every web component are SSR'd to real HTML. Write components so the first paint is the right content. Read SSR-meaningful defaults in `constructor()`. `connectedCallback` is never called on the server, so anything there only runs after hydration. Initial data for components comes from the page function (server-side fetch plus pass as attribute/property) OR from an `async render()` in the component itself (`const u = await getUser(this.uid)`, which SSR awaits so the data is in the first paint), NOT from `fetch` calls in `connectedCallback`. Prefer the co-located `async render()` over prop-drilling; `renderFallback()` is the optional re-fetch loading state (never first paint), and a `Task` is for genuinely client-only data. For write-paths, prefer `<form action=...>` plus server action over `fetch` plus click handler. The framework upgrades plain forms to partial-swap submissions automatically.
|
|
113
113
|
- **Client navigation is auto-magic.** Real `<a href>` and `<form action>` get partial-swap behavior with no opt-in. Because layouts persist across navigation, put shared chrome (sidenav, header) in `layout.ts` and page-specific content in `page.ts`. For validation errors, return 4xx HTML from a `route.ts` POST handler; the router renders it in place preserving the user's input. For non-layout swap regions, wrap in `<webjs-frame id="...">`. See "Client navigation patterns" in AGENTS.md.
|
|
114
114
|
- See AGENTS.md for the complete directive decision guide
|
|
@@ -98,10 +98,11 @@ each change must include.
|
|
|
98
98
|
|
|
99
99
|
- No build step: source files are served as ES modules. Don't introduce
|
|
100
100
|
build tools or bundlers in the critical path.
|
|
101
|
-
- **Erasable TypeScript only.**
|
|
101
|
+
- **Erasable TypeScript only.** The runtime strips types via `module.stripTypeScriptTypes` (Node's built-in, or `amaro` on Bun) (whitespace replacement, byte-exact position preservation, no sourcemap). The scaffold's tsconfig.json sets `erasableSyntaxOnly: true`, so the TS compiler rejects `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Use erasable equivalents: `const X = { ... } as const` plus a derived union type instead of `enum`; explicit fields plus constructor body assignments instead of parameter properties. If `erasableSyntaxOnly` is disabled and non-erasable syntax is used, the dev server fails at strip time and returns a 500 pointing at the `no-non-erasable-typescript` lint rule. webjs is buildless end-to-end and has no bundler fallback.
|
|
102
102
|
- Tagged template: html`<div>${value}</div>` with css`...` for styles.
|
|
103
103
|
- **Tailwind-first styling.** Tailwind utilities are the strong default for pages AND light-DOM components (the default DOM mode): layout, spacing, color (via `@theme` tokens), typography, borders, radius, shadows, interaction states. Light DOM does not scope, so utilities apply directly. The lit reflex to scope CSS (`static styles = css\`...\``) or write an inline `<style>` with semantic class names (`.hero`, `.card`) in a light-DOM component is wrong: the scoped block needs `static shadow = true`, and inline class names leak globally. When a utility bundle repeats, extract a `lib/utils/ui.ts` helper returning an `html` fragment, not a CSS class. Reserve raw CSS for the allowlist (design tokens / `@theme`, `@property` + `@keyframes`, `::-webkit-scrollbar`, `prefers-reduced-motion`, complex `color-mix()` / gradients); when unavoidable in a light-DOM component, prefix every class selector with the component tag. Shadow-DOM components (`static shadow = true`) legitimately author `static styles = css\`...\`` for scoped CSS; don't use inline `style="..."` there.
|
|
104
104
|
- Components: extend WebComponent, declare `static properties` (and `static styles` for shadow-DOM components), call `Class.register('tag-name')` at the bottom of the file. The tag name is the argument to `.register()`, not a static field.
|
|
105
|
+
- Async data in a component: prefer an `async render()` (`const u = await getUser(this.uid)`), which SSR awaits so the data is in the first paint, over prop-drilling from the page or fetching in `connectedCallback`. `renderFallback()` is the optional re-fetch loading state (never first paint); error isolation is automatic (`renderError()` customizes it); a `Task` is for genuinely client-only data.
|
|
105
106
|
- Server actions: *.server.ts files with one exported async function each.
|
|
106
107
|
- Server-only code (@prisma/client, node:*, anything needing Node APIs) goes only in .server.{js,ts} files, route.ts handlers, or middleware.ts. Never in pages, layouts, or components. Wrap in a .server.{js,ts} file; the framework rewrites that import to an RPC stub for the browser. lib/ holds both server-only infra (lib/prisma.server.ts) and browser-safe utilities (lib/utils/cn.ts with cn); apply the same rule per file.
|
|
107
108
|
- Directives: webjs ships only `unsafeHTML`, `live`, and `repeat`. Lit's `classMap` / `styleMap` / `ref` / `when` / `choose` / `guard` are NOT exported. Use plain template-literal expressions and lifecycle hooks instead.
|
package/templates/AGENTS.md
CHANGED
|
@@ -86,7 +86,8 @@ node_modules/@webjsdev/
|
|
|
86
86
|
auth, sessions, cache, rate-limit, WebSocket
|
|
87
87
|
src/ssr.js ← how metadata becomes <head> tags
|
|
88
88
|
src/router.js ← file convention → route table
|
|
89
|
-
src/actions.js ← .server.ts scanner, RPC,
|
|
89
|
+
src/actions.js ← .server.ts scanner, RPC stubs, action endpoint
|
|
90
|
+
src/action-route.js ← route() adapter (action over REST via route.ts)
|
|
90
91
|
src/auth.js, session.js, cache.js, rate-limit.js, csrf.js
|
|
91
92
|
cli/ webjs CLI (dev / start / build / test / check / create / db)
|
|
92
93
|
intellisense/ tsserver plugin: go-to-definition + diagnostic suppression
|
|
@@ -392,6 +393,23 @@ In Docker / Railway, prefer `npm start` (or `node node_modules/.bin/npm
|
|
|
392
393
|
start`) as the CMD over `node ... webjs.js start ...`. The npm form
|
|
393
394
|
fires `prestart`; the direct binary form skips it.
|
|
394
395
|
|
|
396
|
+
### Running on Bun instead of Node
|
|
397
|
+
|
|
398
|
+
webjs runs on **Node 24+ or Bun**. The same `package.json` scripts work on
|
|
399
|
+
either; to run under Bun, force it with `--bun` so the server executes on Bun
|
|
400
|
+
rather than the `webjs` bin's Node shebang:
|
|
401
|
+
|
|
402
|
+
```sh
|
|
403
|
+
bun install
|
|
404
|
+
bun --bun run dev # or: bun --bun run start
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
On Node the `.ts` type-stripping is the built-in `module.stripTypeScriptTypes`;
|
|
408
|
+
on Bun (which has no built-in) it comes from `amaro` automatically, so the same
|
|
409
|
+
source serves identically. SSR action-result seeding (an internal hydration
|
|
410
|
+
optimization) is off on Bun (it needs `module.registerHooks`), which only means
|
|
411
|
+
an async-render component re-fetches once on hydration, no behavior change.
|
|
412
|
+
|
|
395
413
|
**Containerized deploy ships with the scaffold.** `Dockerfile`,
|
|
396
414
|
`compose.yaml`, and `.dockerignore` are scaffolded at the app root. The
|
|
397
415
|
Dockerfile pins `node:24-alpine` (the same Node major CI uses), installs
|
|
@@ -677,7 +695,9 @@ Practical consequences for agents writing webjs code.
|
|
|
677
695
|
| Lit pattern | What breaks in webjs | Webjs equivalent |
|
|
678
696
|
|---|---|---|
|
|
679
697
|
| Fetch in `connectedCallback` / `firstUpdated` | Empty first paint (neither hook runs in SSR) | Fetch in the page function, pass as props |
|
|
680
|
-
| `Task` for initial-paint data | SSR ships the pending state, flashes to resolved on hydration | Page function fetch, pass as props (`Task` is fine for client-time async) |
|
|
698
|
+
| `Task` for initial-paint data | SSR ships the pending state, flashes to resolved on hydration | Page function fetch, pass as props, OR an `async render()` in the component (`Task` is fine for client-time async) |
|
|
699
|
+
| Expecting a sync `render()` only | webjs allows `async render() { const d = await getData(); ... }`; SSR bakes the data into the first paint | Use it for request-time server data; `renderFallback()` is the re-fetch loading UI (never first paint); error isolation is automatic |
|
|
700
|
+
| Assuming an `async render()` always ships its module | A bare one (no other client signal) is ELIDED, so it costs zero JS and skips the on-hydration re-fetch, first paint unchanged | Rely on it for a fetch-and-display leaf. `static refresh = true` keeps the on-load refresh, `static shadow = true` always ships |
|
|
681
701
|
| `window.X` / `document.X` in constructor or `render()` | SSR crash | Move to `connectedCallback` |
|
|
682
702
|
| Top-level `import` of a browser-only library | SSR crash | Dynamic `import()` inside `connectedCallback` |
|
|
683
703
|
| Class-field initializer for a reactive property (`student: Student = {...}`) | Silently breaks reactivity (overwrites the framework accessor) | `declare student: Student` plus constructor default |
|
|
@@ -812,6 +832,14 @@ here is for the "submit → server processes → render new page" flow.)
|
|
|
812
832
|
|
|
813
833
|
### 4. `<webjs-frame id="...">` for non-layout swap regions
|
|
814
834
|
|
|
835
|
+
`<webjs-frame>` is webjs's take on **Turbo Frames** (Hotwire Turbo), so
|
|
836
|
+
`<turbo-frame>` muscle memory transfers directly: a lazy, URL-addressable
|
|
837
|
+
region that swaps on its own, driven by a link/form targeting its id. Use it
|
|
838
|
+
for a region that loads or refreshes INDEPENDENTLY of a full navigation
|
|
839
|
+
(a self-refreshing widget, a `loading="lazy"` below-the-fold region, a
|
|
840
|
+
URL-addressable panel); it ships zero component JS. Its route can itself use
|
|
841
|
+
`<webjs-suspense>` so a lazy frame's slow data streams in behind a fallback.
|
|
842
|
+
|
|
815
843
|
For a widget that should swap on click but isn't a route boundary
|
|
816
844
|
(e.g. a tab strip inside a page), wrap it:
|
|
817
845
|
|
|
@@ -880,6 +908,14 @@ over the outcome (e.g. `location.assign(e.detail.url)`).
|
|
|
880
908
|
|
|
881
909
|
### 5. Stream actions for surgical element-level updates
|
|
882
910
|
|
|
911
|
+
`<webjs-stream>` is webjs's take on **Turbo Streams** (Hotwire Turbo); the
|
|
912
|
+
action set (`append` / `prepend` / `before` / `after` / `replace` / `update` /
|
|
913
|
+
`remove`) mirrors `<turbo-stream>`, so that muscle memory transfers directly.
|
|
914
|
+
It is the ONLY surgical single-element update primitive AND the live-channel
|
|
915
|
+
applier (`connectWS` / `broadcast` -> `renderStream`); a region swap or a
|
|
916
|
+
`<webjs-frame>` reload redraws a whole region, so reach for `<webjs-stream>`
|
|
917
|
+
when only one element changes.
|
|
918
|
+
|
|
883
919
|
When a region swap is too coarse (append ONE comment, remove ONE row, bump a
|
|
884
920
|
count, insert a toast), a server response can declare per-element actions as
|
|
885
921
|
plain HTML, a `<webjs-stream action target>` wrapping one `<template>`:
|
|
@@ -1107,10 +1143,11 @@ composition, so a nested shell ends up dropped by the HTML parser.
|
|
|
1107
1143
|
browser-only). Never fetch initial data in `connectedCallback` /
|
|
1108
1144
|
`firstUpdated`. Fetch in the page function (server) and pass it as
|
|
1109
1145
|
a prop. See *Component pattern* above.
|
|
1110
|
-
8. **Erasable TypeScript only.**
|
|
1111
|
-
`module.stripTypeScriptTypes
|
|
1112
|
-
|
|
1113
|
-
|
|
1146
|
+
8. **Erasable TypeScript only.** The runtime strips types at the runtime
|
|
1147
|
+
layer (Node 24+'s built-in `module.stripTypeScriptTypes`, or `amaro`
|
|
1148
|
+
on Bun, which is byte-identical), with whitespace replacement so
|
|
1149
|
+
line and column positions are byte-exact and no sourcemap ships to
|
|
1150
|
+
the browser. Your `tsconfig.json` sets `erasableSyntaxOnly: true`, so
|
|
1114
1151
|
the TS compiler rejects: `enum`, `namespace` with values,
|
|
1115
1152
|
constructor parameter properties, legacy decorators with
|
|
1116
1153
|
`emitDecoratorMetadata`, and `import = require`. Use the erasable
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -387,6 +387,7 @@ modules/
|
|
|
387
387
|
- Components must call `Class.register('tag')`
|
|
388
388
|
- **Server-only code goes in `.server.{js,ts}` files, `route.ts` handlers, or `middleware.ts`. Never in pages, layouts, or components.** Direct imports of `@prisma/client` or `node:*` from pages, layouts, or components crash the browser at module load. Wrap in a `.server.{js,ts}` file; the framework rewrites that import to an RPC stub on the browser side. `lib/` holds both server-only infra (`lib/prisma.server.ts`) and browser-safe utilities (`lib/utils/cn.ts` with `cn`); the convention is "if a `lib/` file needs Node APIs, only import it from server-only files."
|
|
389
389
|
- Routes (`app/**/page.ts`, `app/**/route.ts`) must be thin: import logic from modules
|
|
390
|
+
- **Fetch server data in the component that needs it, with an `async render()`, not by prop-drilling.** A leaf component can write `const u = await getUser(this.uid)` directly in `render()`; SSR awaits it so the data is in the first paint, and the client uses stale-while-revalidate on a re-fetch. Reach for `renderFallback()` only to show a re-fetch loading state, and `Task` / signals only for genuinely client-only data (a `Task` shows its pending state at SSR, losing first-paint data). Do not put `await getData()` in a page / layout when a leaf component can own it (page fetches run sequentially, a route-level waterfall).
|
|
390
391
|
|
|
391
392
|
---
|
|
392
393
|
|
|
@@ -907,27 +908,37 @@ SSR content is visible immediately. Only the JS download is deferred.
|
|
|
907
908
|
|
|
908
909
|
---
|
|
909
910
|
|
|
910
|
-
##
|
|
911
|
+
## REST endpoints from server actions (route.ts)
|
|
911
912
|
|
|
912
913
|
<!-- OVERRIDE -->
|
|
913
|
-
|
|
914
|
+
A server action is RPC-callable from components. To ALSO reach the same
|
|
915
|
+
function over plain HTTP (mobile apps, webhooks, third parties), put it behind
|
|
916
|
+
a `route.ts` handler. The action stays a normal `'use server'` function; the
|
|
917
|
+
route imports and calls it.
|
|
914
918
|
|
|
915
919
|
```ts
|
|
916
920
|
// modules/posts/actions/create-post.server.ts
|
|
917
921
|
'use server';
|
|
918
|
-
|
|
919
|
-
export const createPost = expose('POST /api/posts', async ({ title, body }) => {
|
|
922
|
+
export async function createPost({ title, body }) {
|
|
920
923
|
return prisma.post.create({ data: { title, body } });
|
|
921
|
-
}
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
// app/api/posts/route.ts
|
|
929
|
+
import { route } from '@webjsdev/server';
|
|
930
|
+
import { createPost } from '../../../modules/posts/actions/create-post.server.ts';
|
|
931
|
+
// The route() adapter merges query + route params + JSON body into one input
|
|
932
|
+
// object and JSON-responds the result. Pass { validate } to guard the input.
|
|
933
|
+
export const POST = route(createPost);
|
|
922
934
|
```
|
|
923
935
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
to call your action. For internal-only actions, plain server actions are
|
|
927
|
-
simpler and CSRF-protected.
|
|
936
|
+
A hand-written `route.ts` (a `POST(req)` that reads the body and calls the
|
|
937
|
+
action) is always available for full control (custom headers, streaming).
|
|
928
938
|
|
|
929
|
-
**Security:** `
|
|
930
|
-
via bearer tokens, API keys, or
|
|
939
|
+
**Security:** a `route.ts` REST endpoint is NOT CSRF-protected (only the RPC
|
|
940
|
+
path is). Authenticate every mutating endpoint via bearer tokens, API keys, or
|
|
941
|
+
auth middleware.
|
|
931
942
|
|
|
932
943
|
---
|
|
933
944
|
|
|
@@ -1043,7 +1054,7 @@ export async function createPost(input: {
|
|
|
1043
1054
|
|
|
1044
1055
|
<!-- OVERRIDE -->
|
|
1045
1056
|
- TypeScript with explicit `.ts` extensions in imports
|
|
1046
|
-
- **Erasable TypeScript only.** The
|
|
1057
|
+
- **Erasable TypeScript only.** The runtime strips types at the runtime layer (Node 24+'s built-in `module.stripTypeScriptTypes`, or `amaro` on Bun, byte-identical) with whitespace replacement, so line + column positions are byte-exact and no sourcemap ships. Your `tsconfig.json` sets `erasableSyntaxOnly: true`, so the compiler rejects: `enum`, `namespace` with values, constructor parameter properties, legacy decorators with `emitDecoratorMetadata`, and `import = require`. Write the erasable equivalents:
|
|
1047
1058
|
```ts
|
|
1048
1059
|
// Not allowed
|
|
1049
1060
|
enum Color { Red, Green, Blue }
|
package/templates/Dockerfile
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
# Works with a plain `docker build` / `docker compose up`, and is the same
|
|
4
4
|
# artifact the webdeploy hosting tool (ubicloud + uncloud) builds and ships.
|
|
5
5
|
#
|
|
6
|
-
# webjs serves .ts directly
|
|
7
|
-
# NO JavaScript build step
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# webjs serves .ts directly by stripping types at the runtime layer, so there is
|
|
7
|
+
# NO JavaScript build step (webjs is buildless end to end; there is no bundler or
|
|
8
|
+
# esbuild fallback). This image runs the app on **Node 24+**, where the strip is
|
|
9
|
+
# the built-in `module.stripTypeScriptTypes`. webjs ALSO runs on **Bun** (where
|
|
10
|
+
# the strip comes from `amaro` automatically), so you can swap this base for an
|
|
11
|
+
# `oven/bun` image and start the app with `bun --bun run start`. Do not lower the
|
|
12
|
+
# Node base below 24 (the floor the CI workflow and the framework pin enforce),
|
|
13
|
+
# since the built-in stripper and recursive fs.watch need it.
|
|
11
14
|
#
|
|
12
15
|
# Security headers are set by the framework, not the proxy. webjs emits
|
|
13
16
|
# X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and
|