create-nowaki 0.14.0 → 0.15.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.15.0",
4
4
  "description": "Scaffold a new Nowaki app",
5
5
  "license": "MIT",
6
6
  "author": "Tettu0530",
@@ -84,13 +84,22 @@ 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
@@ -112,6 +121,17 @@ nowaki.config.mjs plugins (optional)
112
121
  ```
113
122
  Unknown paths, missing params, and wrong param keys are compile errors.
114
123
 
124
+ ## Security
125
+
126
+ - **`nowaki dev` is for local development.** Do not expose it to untrusted networks.
127
+ Use `nowaki start` or a deploy adapter output in production.
128
+ - **Secrets stay server-side.** Only `PUBLIC_*` (and `MODE`) reach the browser. Copy
129
+ `.env.example` to `.env` and keep database URLs, API keys, and tokens unprefixed.
130
+ - **Server functions and API routes are public endpoints.** Validate and bound every
131
+ argument; authorize with `getContext()` (cookies/headers) before mutating data.
132
+ - **Do not embed secrets** in client islands, loaders returned to the page, or static
133
+ prerender output. ISR pages must not include per-user secrets.
134
+
115
135
  ## Don't
116
136
 
117
137
  - 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,7 @@ 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.
47
+ - `routes/api/health.ts` is a minimal JSON API example; `routes/_404.tsx` handles unknown paths.
46
48
  - `AGENTS.md` documents the conventions for AI coding agents.
47
49
 
48
50
  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.13.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.14.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
+ }