create-pracht 0.2.6 → 0.4.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/README.md +38 -0
- package/package.json +3 -2
- package/skills/add-auth/SKILL.md +346 -0
- package/skills/add-db/SKILL.md +316 -0
- package/skills/add-i18n/SKILL.md +239 -0
- package/skills/add-observability/SKILL.md +270 -0
- package/skills/audit-a11y/SKILL.md +170 -0
- package/skills/audit-auth/SKILL.md +144 -0
- package/skills/audit-bundles/SKILL.md +167 -0
- package/skills/audit-csrf/SKILL.md +179 -0
- package/skills/audit-deps/SKILL.md +138 -0
- package/skills/audit-headers/SKILL.md +186 -0
- package/skills/audit-islands/SKILL.md +144 -0
- package/skills/audit-loaders/SKILL.md +135 -0
- package/skills/audit-redirects/SKILL.md +146 -0
- package/skills/audit-secrets/SKILL.md +150 -0
- package/skills/audit-seo/SKILL.md +164 -0
- package/skills/audit-shells/SKILL.md +142 -0
- package/skills/configure-isg/SKILL.md +172 -0
- package/skills/migrate-nextjs/SKILL.md +519 -0
- package/skills/pracht-debug/SKILL.md +146 -0
- package/skills/pracht-deploy/SKILL.md +208 -0
- package/skills/pracht-scaffold/SKILL.md +191 -0
- package/skills/pracht-test-api/SKILL.md +165 -0
- package/skills/pre-deploy/SKILL.md +164 -0
- package/skills/scaffold-e2e/SKILL.md +189 -0
- package/skills/scaffold-tests/SKILL.md +283 -0
- package/skills/tune-render-mode/SKILL.md +168 -0
- package/skills/typed-routes/SKILL.md +203 -0
- package/skills/upgrade-pracht/SKILL.md +151 -0
- package/src/index.js +472 -31
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pracht-deploy
|
|
3
|
+
version: 1.1.0
|
|
4
|
+
description: |
|
|
5
|
+
Pracht deployment guide. Walks through adapter configuration, building, and
|
|
6
|
+
deploying to Node.js, Cloudflare Workers, or Vercel. Handles wrangler config,
|
|
7
|
+
Docker and production checklist.
|
|
8
|
+
Use when asked to "deploy", "set up deployment", "configure adapter",
|
|
9
|
+
"deploy to cloudflare", "deploy to vercel", or "production build".
|
|
10
|
+
allowed-tools:
|
|
11
|
+
- Bash
|
|
12
|
+
- Read
|
|
13
|
+
- Write
|
|
14
|
+
- Edit
|
|
15
|
+
- Grep
|
|
16
|
+
- Glob
|
|
17
|
+
- AskUserQuestion
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Pracht Deploy
|
|
21
|
+
|
|
22
|
+
Guided adapter setup and deployment for pracht applications.
|
|
23
|
+
|
|
24
|
+
## Step 1: Determine the target
|
|
25
|
+
|
|
26
|
+
Read `vite.config.ts` and `package.json` first — don't assume the current adapter.
|
|
27
|
+
Ask the user where they want to deploy if not already clear from their message.
|
|
28
|
+
|
|
29
|
+
If the pracht MCP server is registered (docs/MCP.md), prefer the `inspect_build`/`doctor`/`verify` MCP tools over shelling out. Note: `inspect_build` (like `pracht inspect build`) needs a prior `pracht build`, and `pracht inspect` requires the pracht plugin registered in the vite config.
|
|
30
|
+
|
|
31
|
+
## Supported Adapters
|
|
32
|
+
|
|
33
|
+
| Adapter | Package | Status |
|
|
34
|
+
| ------------------ | ---------------------------- | ------ |
|
|
35
|
+
| Node.js | `@pracht/adapter-node` | Stable |
|
|
36
|
+
| Cloudflare Workers | `@pracht/adapter-cloudflare` | Stable |
|
|
37
|
+
| Vercel | `@pracht/adapter-vercel` | Stable |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Node.js Deployment
|
|
42
|
+
|
|
43
|
+
### Setup
|
|
44
|
+
|
|
45
|
+
1. Ensure `@pracht/adapter-node` is installed.
|
|
46
|
+
2. In `vite.config.ts`:
|
|
47
|
+
```ts
|
|
48
|
+
import { pracht } from "@pracht/vite-plugin";
|
|
49
|
+
import { nodeAdapter } from "@pracht/adapter-node";
|
|
50
|
+
export default { plugins: [pracht({ adapter: nodeAdapter() })] };
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Build
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pracht build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Produces:
|
|
60
|
+
|
|
61
|
+
- `dist/client/` — static assets (JS, CSS, prerendered HTML)
|
|
62
|
+
- `dist/server/server.js` — Node server entry
|
|
63
|
+
- `dist/server/isg-manifest.json` — ISG revalidation config (if ISG routes exist)
|
|
64
|
+
- `dist/client/.vite/manifest.json` — asset manifest for script/style injection
|
|
65
|
+
|
|
66
|
+
### Run
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
node dist/server/server.js
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Port 3000 by default. For a local production smoke test, `pracht preview` builds and runs the server in one step (`--port <n>`, `--skip-build` to reuse an existing build). For production: reverse proxy (nginx, Caddy), process manager (PM2, systemd), `NODE_ENV=production`.
|
|
73
|
+
|
|
74
|
+
### Docker
|
|
75
|
+
|
|
76
|
+
```dockerfile
|
|
77
|
+
FROM node:22-alpine
|
|
78
|
+
WORKDIR /app
|
|
79
|
+
COPY dist/ dist/
|
|
80
|
+
COPY package.json .
|
|
81
|
+
EXPOSE 3000
|
|
82
|
+
CMD ["node", "dist/server/server.js"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Cloudflare Workers Deployment
|
|
88
|
+
|
|
89
|
+
### Setup
|
|
90
|
+
|
|
91
|
+
1. Ensure `@pracht/adapter-cloudflare` is installed.
|
|
92
|
+
2. In `vite.config.ts`:
|
|
93
|
+
```ts
|
|
94
|
+
import { pracht } from "@pracht/vite-plugin";
|
|
95
|
+
import { cloudflareAdapter } from "@pracht/adapter-cloudflare";
|
|
96
|
+
export default { plugins: [pracht({ adapter: cloudflareAdapter() })] };
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Build & Deploy
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pracht build
|
|
103
|
+
npx wrangler deploy
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
To smoke-test the built worker locally first, run `pracht preview` — it builds and then delegates to `wrangler dev`, which serves the wrangler config's `main` entry, `dist/server/worker.js`.
|
|
107
|
+
|
|
108
|
+
### Wrangler Configuration
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
// wrangler.jsonc
|
|
112
|
+
{
|
|
113
|
+
"name": "my-pracht-app",
|
|
114
|
+
"main": "dist/server/worker.js",
|
|
115
|
+
"compatibility_date": "2024-01-01",
|
|
116
|
+
"assets": {
|
|
117
|
+
"binding": "ASSETS",
|
|
118
|
+
"directory": "dist/client",
|
|
119
|
+
"run_worker_first": true,
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`"binding": "ASSETS"` and `"run_worker_first": true` are required. Without the binding, the worker's `env.ASSETS` resolves to nothing and the runtime silently falls back to `null` — headers and ISG manifests load empty, so SSG serving, ISG revalidation, and per-route headers all silently no-op. The canonical config lives at `examples/cloudflare/wrangler.jsonc`. If you rename the binding with `assetsBinding` (below), the wrangler `binding` value must match.
|
|
125
|
+
|
|
126
|
+
### Bindings (KV, D1, R2)
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
export async function loader({ context }: LoaderArgs) {
|
|
130
|
+
const value = await context.env.MY_KV.get("key");
|
|
131
|
+
return { value };
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Custom Assets Binding
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
pracht({ adapter: cloudflareAdapter({ assetsBinding: "STATIC" }) });
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### ISG via Workers Caching
|
|
142
|
+
|
|
143
|
+
ISG works out of the box: without any cache option, the default worker-managed path serves the build-time snapshot, detects staleness, and regenerates pages in the background via the Workers Cache API — per colo — and `POST /__pracht/revalidate` triggers on-demand regeneration. Enabling `cache: true` moves ISG from that per-colo worker-managed path to edge-tier Workers Caching, on both sides:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
pracht({ adapter: cloudflareAdapter({ cache: true }) });
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```jsonc
|
|
150
|
+
// wrangler.jsonc
|
|
151
|
+
{ "cache": { "enabled": true } }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Before enabling it, audit ISG URLs for unbounded query strings. Workers Caching
|
|
155
|
+
keys the exact path and query string, including parameter order and trailing
|
|
156
|
+
slashes; use a bounded query allowlist/canonical redirect or an uncached gateway
|
|
157
|
+
with a pathname-only `cf.cacheKey`, and normalize `Accept` there for routes that
|
|
158
|
+
export markdown. See `docs/ADAPTERS.md#cache-key-cardinality`.
|
|
159
|
+
|
|
160
|
+
Time-revalidated ISG pages then render on demand, are cached at the edge for
|
|
161
|
+
their `revalidate` window (stale pages served instantly while the Worker
|
|
162
|
+
re-renders in the background), and can be purged early with `purgeCache()` from
|
|
163
|
+
`@pracht/adapter-cloudflare/cache`. Webhook-only ISG routes keep their
|
|
164
|
+
build-time snapshots and the worker-managed path either way.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Vercel Deployment
|
|
169
|
+
|
|
170
|
+
### Setup
|
|
171
|
+
|
|
172
|
+
1. Ensure `@pracht/adapter-vercel` is installed.
|
|
173
|
+
2. In `vite.config.ts`:
|
|
174
|
+
```ts
|
|
175
|
+
import { pracht } from "@pracht/vite-plugin";
|
|
176
|
+
import { vercelAdapter } from "@pracht/adapter-vercel";
|
|
177
|
+
export default { plugins: [pracht({ adapter: vercelAdapter() })] };
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Build & Deploy
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pracht build
|
|
184
|
+
npx vercel deploy --prebuilt
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Produces: `.vercel/output/config.json`, `.vercel/output/static/`, `.vercel/output/functions/render.func/server.js`
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Deployment Checklist
|
|
192
|
+
|
|
193
|
+
1. **Build**: Run `pracht build` and verify `dist/` output.
|
|
194
|
+
2. **Environment variables**: Ensure secrets/config needed by loaders are available at runtime.
|
|
195
|
+
3. **Static assets**: Verify `dist/client/` contains prerendered HTML for SSG routes (and ISG routes — except time-revalidated ISG routes on Cloudflare with Workers Caching enabled, which render on demand; webhook-only ISG routes keep their build-time snapshots).
|
|
196
|
+
4. **ISG routes**: Confirm the ISG manifest (`dist/server/isg-manifest.json`; on Cloudflare also `dist/client/_pracht/isg.json`) exists if using incremental static generation.
|
|
197
|
+
5. **API routes**: Test API endpoints work in the production runtime. For Node.js, run `pracht preview` (or `node dist/server/server.js`).
|
|
198
|
+
6. **Middleware**: Verify auth/redirect middleware behaves correctly in production.
|
|
199
|
+
|
|
200
|
+
## Rules
|
|
201
|
+
|
|
202
|
+
1. Read `vite.config.ts` and `package.json` before giving advice.
|
|
203
|
+
2. Run `pracht build` to verify the build succeeds before deploying.
|
|
204
|
+
3. Smoke-test the production runtime before pushing to production. For Node.js and Cloudflare, run `pracht preview`.
|
|
205
|
+
4. If the user needs an adapter that isn't installed, help them add it (`pnpm add @pracht/adapter-*`).
|
|
206
|
+
5. Don't push to production without the user's explicit confirmation.
|
|
207
|
+
|
|
208
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pracht-scaffold
|
|
3
|
+
version: 1.1.0
|
|
4
|
+
description: |
|
|
5
|
+
Pracht code scaffolding. Prefer the framework-native CLI generators
|
|
6
|
+
(`pracht generate route|shell|middleware|api`) and only fall back to manual
|
|
7
|
+
edits when the CLI flags cannot express the requested shape. Knows pracht
|
|
8
|
+
conventions (Preact idioms, render modes, route manifest).
|
|
9
|
+
Use when asked to "scaffold", "generate a route", "create a new page",
|
|
10
|
+
"add middleware", "add an API route", or "create a shell".
|
|
11
|
+
allowed-tools:
|
|
12
|
+
- Bash
|
|
13
|
+
- Read
|
|
14
|
+
- Write
|
|
15
|
+
- Edit
|
|
16
|
+
- Grep
|
|
17
|
+
- Glob
|
|
18
|
+
- AskUserQuestion
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Pracht Scaffold
|
|
22
|
+
|
|
23
|
+
Generate pracht framework modules with correct types, exports, and manifest wiring.
|
|
24
|
+
|
|
25
|
+
## First Choice
|
|
26
|
+
|
|
27
|
+
Use the CLI first:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pracht generate route --path /dashboard --render ssr
|
|
31
|
+
pracht generate shell --name app
|
|
32
|
+
pracht generate middleware --name auth
|
|
33
|
+
pracht generate api --path /health --methods GET,POST
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`pracht generate route` supports the full flag matrix below — do not fall back to manual edits for shapes it already covers:
|
|
37
|
+
|
|
38
|
+
| Flag | Meaning |
|
|
39
|
+
| ------------------ | ------------------------------------------------------------------------------------ |
|
|
40
|
+
| `--path` (required) | Route path, e.g. `/dashboard` or `/blog/:slug` |
|
|
41
|
+
| `--render` | Render mode: `ssr` (default), `spa`, `ssg`, or `isg` |
|
|
42
|
+
| `--shell` | Registered shell name (manifest apps only) |
|
|
43
|
+
| `--middleware` | Registered middleware names, comma-separated (manifest apps only) |
|
|
44
|
+
| `--loader` | Include a `loader` export |
|
|
45
|
+
| `--error-boundary` | Include an `ErrorBoundary` export |
|
|
46
|
+
| `--static-paths` | Include `getStaticPaths` (added automatically for dynamic `ssg`/`isg` paths) |
|
|
47
|
+
| `--title` | Page title used in the `head()` export |
|
|
48
|
+
| `--revalidate` | ISG revalidation window in seconds (`isg` only, default 3600) |
|
|
49
|
+
| `--json` | Machine-readable output |
|
|
50
|
+
|
|
51
|
+
`generate shell` and `generate middleware` take `--name`; `generate api` takes `--path` and `--methods` (comma-separated). All subcommands accept `--json`.
|
|
52
|
+
|
|
53
|
+
- `--shell`/`--middleware` names must already be registered in the app manifest — the CLI errors otherwise. Generate the shell/middleware first, then the route that references it.
|
|
54
|
+
- If the pracht MCP server is registered (docs/MCP.md), call the `generate_route`/`generate_shell`/`generate_middleware`/`generate_api` MCP tools instead of Bash — same behavior, structured results.
|
|
55
|
+
- Add `--json` when another agent/tool needs machine-readable output.
|
|
56
|
+
- `generate route` also emits a Playwright smoke test in `e2e/` when the app has a Playwright setup (`playwright.config.*` or an `e2e/` directory). Pass `--no-test` to skip it, `--test` to force it. Keep the generated test — it is the output-level proof the route works.
|
|
57
|
+
- Use `pracht inspect routes --json` or `pracht inspect api --json` to confirm current wiring before manual edits when the existing graph matters. `pracht inspect` requires the pracht plugin registered in the project's vite config.
|
|
58
|
+
- If the app has typed routes (`src/pracht-routes.ts` / `.d.ts`) or the user asks for typed links, run `pracht typegen` after adding or renaming routes.
|
|
59
|
+
- If the app commits `.pracht/app-graph.json`, run `pracht plan --write` after changing routes and include the refreshed snapshot — `pracht verify` fails when it is stale.
|
|
60
|
+
- If `src/routes.ts` declares `constraints:`, respect them (e.g. put new `/app/**` routes behind the required middleware). Never delete or weaken a constraint to make `pracht verify` pass — that is a policy change the user must approve.
|
|
61
|
+
- If the CLI can express the request, do not reimplement the scaffold by hand.
|
|
62
|
+
- Only edit files manually when the CLI cannot cover the requested shape.
|
|
63
|
+
|
|
64
|
+
The user will describe what they want to create. Parse their request and generate the appropriate module(s). Always ask if anything is ambiguous (e.g. render mode, shell assignment).
|
|
65
|
+
|
|
66
|
+
## What You Can Scaffold
|
|
67
|
+
|
|
68
|
+
| Kind | Directory | Key exports | Example |
|
|
69
|
+
| ---------- | ----------------- | -------------------------------------------------------------------- | ------------------------------ |
|
|
70
|
+
| Route | `src/routes/` | `loader`, `head`, `Component`, `ErrorBoundary`, `getStaticPaths` | `src/routes/blog.tsx` |
|
|
71
|
+
| Shell | `src/shells/` | `Shell`, `head` | `src/shells/marketing.tsx` |
|
|
72
|
+
| Middleware | `src/middleware/` | `middleware` | `src/middleware/rate-limit.ts` |
|
|
73
|
+
| API route | `src/api/` | Named HTTP method handlers (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) or one default method dispatcher | `src/api/users/[id].ts` |
|
|
74
|
+
|
|
75
|
+
## Templates (manual fallback)
|
|
76
|
+
|
|
77
|
+
Use these only when the CLI cannot express the requested shape.
|
|
78
|
+
|
|
79
|
+
### Route
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
export function head() {
|
|
83
|
+
return { title: "Page Title" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function Component() {
|
|
87
|
+
return <section>{/* route UI */}</section>;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- Include a `loader` only when the route needs server data (matches the CLI, which omits it unless `--loader` is passed):
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import type { LoaderArgs, RouteComponentProps } from "@pracht/core";
|
|
95
|
+
|
|
96
|
+
export async function loader(_args: LoaderArgs) {
|
|
97
|
+
return {
|
|
98
|
+
/* loader data */
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function Component({ data }: RouteComponentProps<typeof loader>) {
|
|
103
|
+
return <section>{/* route UI */}</section>;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- Include `ErrorBoundary` only if requested.
|
|
108
|
+
- Include `getStaticPaths` only for SSG/ISG routes with dynamic segments.
|
|
109
|
+
- Use `RouteComponentProps<typeof loader>` for typed `data` prop.
|
|
110
|
+
|
|
111
|
+
### Shell
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import type { ShellProps } from "@pracht/core";
|
|
115
|
+
|
|
116
|
+
export function Shell({ children }: ShellProps) {
|
|
117
|
+
return (
|
|
118
|
+
<div class="shell-name">
|
|
119
|
+
<nav>{/* navigation */}</nav>
|
|
120
|
+
<main>{children}</main>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function head() {
|
|
126
|
+
return { title: "Shell Title" };
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Middleware
|
|
131
|
+
|
|
132
|
+
Middleware wraps the rest of the request via `next()`:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { redirect, type MiddlewareFn } from "@pracht/core";
|
|
136
|
+
|
|
137
|
+
export const middleware: MiddlewareFn = async ({ context, request }, next) => {
|
|
138
|
+
// Mutate context, validate auth, etc.
|
|
139
|
+
// - Call `return next()` to continue
|
|
140
|
+
// - Return `redirect("/path", { request })` to short-circuit with a redirect
|
|
141
|
+
// - Return any `Response` to short-circuit
|
|
142
|
+
// - Wrap `await next()` in try/catch/finally for tracing/logging
|
|
143
|
+
return next();
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### API Route
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import type { ApiRouteArgs } from "@pracht/core";
|
|
151
|
+
|
|
152
|
+
export function GET({ params, url }: ApiRouteArgs) {
|
|
153
|
+
return Response.json({
|
|
154
|
+
/* response data */
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- Only include the HTTP methods the user needs.
|
|
160
|
+
- Use a default export only when the user wants to branch on `request.method` manually.
|
|
161
|
+
- Use `request.json()`, `request.formData()`, etc. for body parsing.
|
|
162
|
+
- Always return `Response` objects (typically `Response.json()`).
|
|
163
|
+
- Dynamic segments use bracket syntax in filenames: `[id].ts`, `[...slug].ts`.
|
|
164
|
+
|
|
165
|
+
## Wiring Into the Manifest (manual fallback only)
|
|
166
|
+
|
|
167
|
+
The CLI generators wire the manifest themselves: `pracht generate route` inserts the `route(...)` call into `src/routes.ts` (adding `route`/`timeRevalidate` imports as needed), and `generate shell`/`generate middleware` upsert their registry entries. **Do not re-edit the manifest after a successful `pracht generate` run.**
|
|
168
|
+
|
|
169
|
+
Only when you created module files by hand, update `src/routes.ts` to register the new module:
|
|
170
|
+
|
|
171
|
+
- **Routes**: Add a `route("/path", () => import("./routes/filename.tsx"), { id: "name", render: "ssr" })` call inside the appropriate group or at the top level. Plain strings like `"./routes/filename.tsx"` also work.
|
|
172
|
+
- **Shells**: Add to the `shells` record: `shellName: () => import("./shells/filename.tsx")` (or `"./shells/filename.tsx"`).
|
|
173
|
+
- **Middleware**: Add to the `middleware` record: `mwName: () => import("./middleware/filename.ts")` (or `"./middleware/filename.ts"`).
|
|
174
|
+
- **API routes**: No manifest change needed — auto-discovered from `src/api/` by the Vite plugin.
|
|
175
|
+
|
|
176
|
+
Available render modes: `"ssr"` (default), `"ssg"` (static at build), `"isg"` (incremental static with `revalidate: timeRevalidate(seconds)`), `"spa"` (client-only).
|
|
177
|
+
|
|
178
|
+
Import `timeRevalidate` from `"@pracht/core"` when using ISG.
|
|
179
|
+
|
|
180
|
+
## Rules
|
|
181
|
+
|
|
182
|
+
1. Prefer `pracht generate ...` over manual edits.
|
|
183
|
+
2. Read the project's existing `src/routes.ts` to determine current shells, middleware, and route structure before adding when the CLI cannot finish the job on its own.
|
|
184
|
+
3. Place files in the conventional directories (`src/routes/`, `src/shells/`, `src/middleware/`, `src/api/`).
|
|
185
|
+
4. Keep generated code minimal — only include exports the user actually needs.
|
|
186
|
+
5. Use Preact idioms: `class` not `className`, functional components, `import type` for type-only imports.
|
|
187
|
+
6. When route ids/paths change in a typed-routes app, run `pracht typegen` and include the generated route files.
|
|
188
|
+
7. Finish with `pracht verify` (and `pracht plan --write` when the app commits an app-graph snapshot).
|
|
189
|
+
8. After scaffolding, summarize what was created and how it was wired.
|
|
190
|
+
|
|
191
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pracht-test-api
|
|
3
|
+
version: 1.1.0
|
|
4
|
+
description: |
|
|
5
|
+
Auto-generate Vitest request/response tests for every handler in `src/api/`.
|
|
6
|
+
Each test instantiates a `Request`, calls the exported HTTP method handler
|
|
7
|
+
directly, and asserts on the returned `Response` — no server boot required.
|
|
8
|
+
Use when asked to "test my API routes", "scaffold API tests", "generate
|
|
9
|
+
tests for src/api", or "add tests for this endpoint".
|
|
10
|
+
allowed-tools:
|
|
11
|
+
- Bash
|
|
12
|
+
- Read
|
|
13
|
+
- Write
|
|
14
|
+
- Edit
|
|
15
|
+
- Grep
|
|
16
|
+
- Glob
|
|
17
|
+
- AskUserQuestion
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Pracht Test API
|
|
21
|
+
|
|
22
|
+
Pracht API handlers are plain functions: `(args: ApiRouteArgs) => Response |
|
|
23
|
+
Promise<Response>` (`ApiRouteArgs` is `BaseRouteArgs` with `route` narrowed
|
|
24
|
+
to `ResolvedApiRoute`). They test cleanly in Vitest without booting the
|
|
25
|
+
framework.
|
|
26
|
+
|
|
27
|
+
## Step 1: Confirm Vitest is installed
|
|
28
|
+
|
|
29
|
+
If the project has no `vitest` dependency, run `scaffold-tests` first (or
|
|
30
|
+
prompt the user to). This skill does not handle Vitest setup.
|
|
31
|
+
|
|
32
|
+
## Step 2: Enumerate API handlers
|
|
33
|
+
|
|
34
|
+
If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
|
|
35
|
+
(`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`,
|
|
36
|
+
`generate_*`) over shelling out. Prerequisite: `pracht inspect` needs a vite
|
|
37
|
+
config with the pracht plugin registered.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pracht inspect api --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For each entry, capture: `path` (URL), `file` (source path), and the exported
|
|
44
|
+
`methods` (e.g., `["GET", "POST"]`). Note: `methods` only lists named HTTP
|
|
45
|
+
method exports — a default-export dispatcher yields `methods: []`, never
|
|
46
|
+
`["default"]`. Current `@pracht/cli` versions also report a
|
|
47
|
+
`hasDefaultHandler` boolean; use it when present. If the field is absent
|
|
48
|
+
(older CLI) and `methods` is empty, fall back to the grep in Step 7 to detect
|
|
49
|
+
a default dispatcher.
|
|
50
|
+
|
|
51
|
+
Ask the user which subset to scaffold or accept paths via `$ARGUMENTS`.
|
|
52
|
+
|
|
53
|
+
## Step 3: Generate one test per handler file
|
|
54
|
+
|
|
55
|
+
Place tests next to the handler with `.test.ts` suffix
|
|
56
|
+
(`src/api/users/[id].test.ts`). Use a small helper to construct
|
|
57
|
+
`ApiRouteArgs`:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { describe, it, expect } from "vitest";
|
|
61
|
+
import { GET, POST /* import only what the handler exports */ } from "./<file>";
|
|
62
|
+
|
|
63
|
+
function args(url: string, init?: RequestInit, params: Record<string, string> = {}) {
|
|
64
|
+
const request = new Request(url, init);
|
|
65
|
+
return {
|
|
66
|
+
request,
|
|
67
|
+
params,
|
|
68
|
+
context: {} as never,
|
|
69
|
+
url: new URL(request.url),
|
|
70
|
+
signal: AbortSignal.timeout(5000),
|
|
71
|
+
route: {} as never,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe("<METHOD> <api-path>", () => {
|
|
76
|
+
it("returns 200 on a valid request", async () => {
|
|
77
|
+
const res = await GET(args("http://localhost<api-path>"));
|
|
78
|
+
expect(res).toBeInstanceOf(Response);
|
|
79
|
+
expect(res.status).toBe(200);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Step 4: Generate method-specific cases
|
|
85
|
+
|
|
86
|
+
For each exported method, emit the smallest realistic case:
|
|
87
|
+
|
|
88
|
+
| Method | Default case |
|
|
89
|
+
| ------- | --------------------------------------------------------------- |
|
|
90
|
+
| `GET` | Plain GET → `expect(res.status).toBeLessThan(400)` |
|
|
91
|
+
| `POST` | POST with empty `FormData` → assert validation behavior |
|
|
92
|
+
| `PUT` | PUT with JSON body → assert 200 or auth-required (401/403) |
|
|
93
|
+
| `PATCH` | PATCH with partial body → assert 200 or 422 |
|
|
94
|
+
| `DELETE`| DELETE on a real-shaped path → assert 200/204 or 401 |
|
|
95
|
+
|
|
96
|
+
For dynamic segments (`[id].ts` → `/api/users/:id`), pick a placeholder param
|
|
97
|
+
(e.g., `id: "test-1"`) and pass it via `params`. Surface in the report that
|
|
98
|
+
the user may need to provide a real fixture.
|
|
99
|
+
|
|
100
|
+
## Step 5: Detect auth-gated APIs
|
|
101
|
+
|
|
102
|
+
API routes do NOT appear in `pracht inspect routes --json` (that report
|
|
103
|
+
covers page routes only) and `pracht inspect api --json` has no middleware
|
|
104
|
+
field. API middleware is the single global list configured as
|
|
105
|
+
`defineApp({ api: { middleware: [...] } })` — read the manifest source
|
|
106
|
+
(`src/routes.ts` or wherever `defineApp` lives) to see whether an auth
|
|
107
|
+
middleware is in that list. If it is, scaffold an extra test:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
it("rejects unauthenticated requests", async () => {
|
|
111
|
+
const res = await POST(args("http://localhost<api-path>", { method: "POST" }));
|
|
112
|
+
expect([401, 403, 302]).toContain(res.status);
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Note: middleware does NOT run when calling the handler directly — this test
|
|
117
|
+
verifies the handler's own defense if it has one. If the only defense is
|
|
118
|
+
middleware, mention that in the report and recommend an integration test that
|
|
119
|
+
goes through the framework's request pipeline (out of scope for this skill).
|
|
120
|
+
The same applies to the framework's built-in CSRF check: `requireSameOrigin`
|
|
121
|
+
(on by default) rejects cross-origin state-changing API requests in the real
|
|
122
|
+
pipeline but never runs in direct-invocation tests.
|
|
123
|
+
|
|
124
|
+
## Step 6: Validate JSON shape
|
|
125
|
+
|
|
126
|
+
For handlers that return `Response.json(...)`, generate:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
it("returns JSON with the expected keys", async () => {
|
|
130
|
+
const res = await GET(args("http://localhost<api-path>"));
|
|
131
|
+
expect(res.headers.get("content-type")).toMatch(/application\/json/);
|
|
132
|
+
const body = await res.json();
|
|
133
|
+
expect(body).toEqual(expect.objectContaining({ /* fill in */ }));
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Step 7: Default-handler dispatchers
|
|
138
|
+
|
|
139
|
+
If the handler exports `default` (one function dispatching on
|
|
140
|
+
`request.method`), generate a test per HTTP method the handler appears to
|
|
141
|
+
support (grep for `request.method ===` patterns inside the file).
|
|
142
|
+
|
|
143
|
+
## Step 8: Run
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pnpm test
|
|
147
|
+
pracht verify --json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Report passes/failures. Mark generated assertions as TODO so the user knows
|
|
151
|
+
to tighten them.
|
|
152
|
+
|
|
153
|
+
## Rules
|
|
154
|
+
|
|
155
|
+
1. Use `pracht inspect api --json` as the inventory — do not glob.
|
|
156
|
+
2. Only import methods the handler actually exports; otherwise the test fails
|
|
157
|
+
to load.
|
|
158
|
+
3. Direct-handler invocation skips middleware AND the default-on
|
|
159
|
+
`requireSameOrigin` CSRF check. Be explicit in the report.
|
|
160
|
+
4. Test files live next to handlers with `.test.ts` suffix unless the
|
|
161
|
+
project already uses a `__tests__/` convention (detect and match).
|
|
162
|
+
5. Never overwrite existing test files; emit a `.next.test.ts` and tell the
|
|
163
|
+
user to merge.
|
|
164
|
+
|
|
165
|
+
$ARGUMENTS
|