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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nowaki",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Scaffold a new Nowaki app",
5
5
  "license": "MIT",
6
6
  "author": "Tettu0530",
@@ -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 arguments; read auth via `getContext()`:
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 { getContext } from "@nowaki-dev/runtime";
92
- export async function addTodo(text: string) { /* runs on the server */ }
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
- - `AGENTS.md` documents the conventions for AI coding agents.
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.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.13.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
+ }));
@@ -0,0 +1,13 @@
1
+ export const title = "404 — Not found";
2
+
3
+ export default function NotFound({ url }: { url: URL }) {
4
+ return (
5
+ <main>
6
+ <h1>404</h1>
7
+ <p>
8
+ Page not found: <code>{url.pathname}</code>
9
+ </p>
10
+ <a href="/">Back to home</a>
11
+ </main>
12
+ );
13
+ }
@@ -0,0 +1,6 @@
1
+ // Example API route (server-only). No secrets in responses; validate inputs on real endpoints.
2
+ type Ctx = { json: (data: unknown) => unknown };
3
+
4
+ export function GET(ctx: Ctx) {
5
+ return ctx.json({ ok: true });
6
+ }