create-nowaki 0.14.0 → 0.16.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/index.js +3 -0
- package/package.json +1 -1
- package/templates/_shared/AGENTS.md +42 -3
- package/templates/_shared/README.md +11 -1
- package/templates/_shared/env.example +7 -0
- package/templates/_shared/package.json +2 -2
- package/templates/basics/actions/echo.ts +22 -0
- package/templates/basics/routes/_404.tsx +13 -0
- package/templates/basics/routes/api/health.ts +6 -0
package/index.js
CHANGED
|
@@ -141,6 +141,9 @@ await cp(path.join(templatesDir, template), dest, { recursive: true });
|
|
|
141
141
|
try {
|
|
142
142
|
await rename(path.join(dest, "_gitignore"), path.join(dest, ".gitignore"));
|
|
143
143
|
} catch {}
|
|
144
|
+
try {
|
|
145
|
+
await rename(path.join(dest, "env.example"), path.join(dest, ".env.example"));
|
|
146
|
+
} catch {}
|
|
144
147
|
|
|
145
148
|
try {
|
|
146
149
|
const pkgPath = path.join(dest, "package.json");
|
package/package.json
CHANGED
|
@@ -84,18 +84,35 @@ nowaki.config.mjs plugins (optional)
|
|
|
84
84
|
islands to cut work on first load. The directives are typed (no prop leaks).
|
|
85
85
|
7. **Server functions** — a module with a top-of-file `"use server"` directive
|
|
86
86
|
becomes RPC; the client gets a tiny fetch proxy, the implementation stays on
|
|
87
|
-
the server. Validate
|
|
87
|
+
the server. Validate the input with `serverFn(schema, impl)` (any Standard
|
|
88
|
+
Schema library: zod, valibot, arktype); `nowaki build` warns about exports that
|
|
89
|
+
take arguments without it. Read auth via `getContext()`, and throw
|
|
90
|
+
`ServerFnError` for messages the client may see (other errors are a generic
|
|
91
|
+
500 in production):
|
|
88
92
|
```ts
|
|
89
93
|
// actions/todos.ts
|
|
90
94
|
"use server";
|
|
91
|
-
import {
|
|
92
|
-
|
|
95
|
+
import { z } from "zod";
|
|
96
|
+
import { getContext, serverFn, ServerFnError } from "@nowaki-dev/runtime";
|
|
97
|
+
export const addTodo = serverFn(z.string().min(1).max(200), async (text) => {
|
|
98
|
+
if (!getContext()?.cookies?.user) throw new ServerFnError("Sign in first", { status: 401 });
|
|
99
|
+
/* runs on the server with a validated string */
|
|
100
|
+
});
|
|
93
101
|
```
|
|
102
|
+
The **basics** template includes `actions/echo.ts` as a minimal validated example.
|
|
94
103
|
Call it from an island like a normal async function:
|
|
95
104
|
`import { addTodo } from "../actions/todos.ts";`
|
|
96
105
|
8. **Jetstream islands** (server-reactive, zero client JS) — give an island
|
|
97
106
|
`export const live = { state, on }`; buttons use `data-live="handler"` instead
|
|
98
107
|
of `onClick`. State lives on the server; the server pushes HTML patches.
|
|
108
|
+
Forms use `data-live-submit="handler"` (gets the fields as an object), inputs
|
|
109
|
+
`data-live-input` (debounced; `data-live-debounce="ms"`) or `data-live-change`;
|
|
110
|
+
give list rows `data-key`. Read the request with `getContext()` and guard
|
|
111
|
+
events with `live.authorize`; `live.subscribe` + `live.load` + `publish(topic)`
|
|
112
|
+
push server-side changes. `live.room(props)` + `live.shared()` share one state
|
|
113
|
+
between everyone in a room (`data-nowaki-presence="<room>"` shows who's there).
|
|
114
|
+
Rooms, `publish`, and presence live in one server process (`nowaki start`);
|
|
115
|
+
on the deploy adapters live islands work per event over POST, without push.
|
|
99
116
|
9. **Loading / error UI** (client navigation) — `routes/loading.tsx` renders
|
|
100
117
|
while a navigation is in flight; `routes/error.tsx` (props `{ error, reset }`,
|
|
101
118
|
type `ErrorPageProps`) renders if it fails. Both nest by directory. In
|
|
@@ -112,6 +129,28 @@ nowaki.config.mjs plugins (optional)
|
|
|
112
129
|
```
|
|
113
130
|
Unknown paths, missing params, and wrong param keys are compile errors.
|
|
114
131
|
|
|
132
|
+
## Security
|
|
133
|
+
|
|
134
|
+
- **`nowaki dev` is for local development.** Do not expose it to untrusted networks.
|
|
135
|
+
Use `nowaki start` or a deploy adapter output in production.
|
|
136
|
+
- **Secrets stay server-side.** Only `PUBLIC_*` (and `MODE`) reach the browser. Copy
|
|
137
|
+
`.env.example` to `.env` and keep database URLs, API keys, and tokens unprefixed.
|
|
138
|
+
- **Server functions and API routes are public endpoints.** Validate and bound every
|
|
139
|
+
argument; authorize with `getContext()` (cookies/headers) before mutating data.
|
|
140
|
+
Use `serverFn(schema, impl)` for `"use server"` exports that take arguments (see
|
|
141
|
+
`actions/echo.ts` in the **basics** template).
|
|
142
|
+
- **Cross-site writes.** Page `action`s reject cross-site non-GET requests by default.
|
|
143
|
+
**`routes/_middleware.ts` runs before that check** — do not perform sensitive
|
|
144
|
+
mutations in middleware unless you validate the caller yourself. API routes log a
|
|
145
|
+
warning and still accept cross-site POSTs unless you `export const csrf = true` on
|
|
146
|
+
the route (recommended for cookie-authenticated APIs).
|
|
147
|
+
- **Production checklist** (when you deploy): set `NOWAKI_LIVE_SECRET` if the app
|
|
148
|
+
uses Jetstream live islands; try `NOWAKI_CSP=report-only` before `enforce`; behind
|
|
149
|
+
a reverse proxy on `nowaki start`, set `NOWAKI_TRUST_PROXY=1` and forward the public
|
|
150
|
+
host as `x-forwarded-host`. See the upstream [SECURITY.md](https://github.com/Tettu0530/nowaki/blob/main/SECURITY.md).
|
|
151
|
+
- **Do not embed secrets** in client islands, loaders returned to the page, or static
|
|
152
|
+
prerender output. ISR pages must not include per-user secrets.
|
|
153
|
+
|
|
115
154
|
## Don't
|
|
116
155
|
|
|
117
156
|
- Don't add a build tool (Vite/webpack/Babel) or a `tsconfig` paths setup — the
|
|
@@ -8,6 +8,7 @@ components under `islands/` ship JavaScript and hydrate.
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install
|
|
11
|
+
cp .env.example .env # optional — only PUBLIC_* vars belong in client bundles
|
|
11
12
|
npm run dev # http://localhost:3000
|
|
12
13
|
```
|
|
13
14
|
|
|
@@ -43,6 +44,15 @@ actions/ "use server" RPC modules (optional)
|
|
|
43
44
|
`preact/compat`, so many React libraries work.)
|
|
44
45
|
- Use **explicit file extensions** in relative imports: `../islands/Counter.tsx`.
|
|
45
46
|
- A route's `loader` runs only on the server; `action` handles non-GET requests.
|
|
46
|
-
-
|
|
47
|
+
- The **basics** template adds `routes/_404.tsx`, `routes/api/health.ts`, and
|
|
48
|
+
`actions/echo.ts`; **minimal** is a single page with no islands.
|
|
49
|
+
- `AGENTS.md` documents conventions (including security) for AI coding agents.
|
|
50
|
+
|
|
51
|
+
## Production
|
|
52
|
+
|
|
53
|
+
Use `npm run build` then `npm run start` (or a deploy adapter). Do not run
|
|
54
|
+
`nowaki dev` on the public internet. Cookie-authenticated API routes should export
|
|
55
|
+
`csrf = true`; live islands need a stable `NOWAKI_LIVE_SECRET`. See `AGENTS.md`
|
|
56
|
+
§ Security and [nowaki.dev/docs](https://nowaki.dev/docs).
|
|
47
57
|
|
|
48
58
|
Docs: <https://nowaki.dev/docs>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Copy to `.env` for local development. Never commit `.env` (see `.gitignore`).
|
|
2
|
+
#
|
|
3
|
+
# Only variables prefixed with PUBLIC_ are inlined into client bundles.
|
|
4
|
+
# Everything else stays on the server (loaders, actions, server functions).
|
|
5
|
+
#
|
|
6
|
+
# PUBLIC_SITE_NAME=My App
|
|
7
|
+
# DATABASE_URL=postgres://... # server-only — do not prefix with PUBLIC_
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"prerender": "nowaki prerender"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@nowaki-dev/runtime": "^0.
|
|
12
|
+
"@nowaki-dev/runtime": "^0.14.0",
|
|
13
13
|
"preact": "^10.25.4",
|
|
14
14
|
"preact-render-to-string": "^6.8.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"nowaki": "^0.
|
|
17
|
+
"nowaki": "^0.15.0"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { getContext, serverFn } from "@nowaki-dev/runtime";
|
|
4
|
+
|
|
5
|
+
// Hand-written Standard Schema (no zod/valibot) — same pattern as examples/hello/actions/todos.ts.
|
|
6
|
+
const message = {
|
|
7
|
+
"~standard": {
|
|
8
|
+
version: 1 as const,
|
|
9
|
+
vendor: "example",
|
|
10
|
+
validate: (v: unknown) =>
|
|
11
|
+
typeof v === "string" && v.length <= 120
|
|
12
|
+
? { value: v.trim() }
|
|
13
|
+
: { issues: [{ message: "message must be a string of at most 120 characters" }] },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Example server function — validated input and getContext() for auth in real apps. */
|
|
18
|
+
export const echo = serverFn(message, async (text) => ({
|
|
19
|
+
ok: true,
|
|
20
|
+
echo: text,
|
|
21
|
+
hasRequest: getContext() != null,
|
|
22
|
+
}));
|