create-pracht 0.4.2 → 0.5.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 +19 -4
- package/package.json +1 -1
- package/skills/audit-auth/SKILL.md +32 -2
- package/skills/configure-isg/SKILL.md +30 -15
- package/skills/migrate-nextjs/SKILL.md +11 -2
- package/skills/pracht-deploy/SKILL.md +124 -7
- package/skills/pracht-test-api/SKILL.md +13 -19
- package/skills/pre-deploy/SKILL.md +18 -6
- package/skills/scaffold-tests/SKILL.md +34 -50
- package/skills/tune-render-mode/SKILL.md +4 -1
- package/src/index.js +516 -34
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scaffold-tests
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.2.0
|
|
4
4
|
description: |
|
|
5
5
|
Scaffold Vitest unit/integration tests for pracht routes, loaders, and
|
|
6
6
|
middleware. Asks the user once whether to use vitest browser mode with
|
|
7
7
|
`vitest-browser-preact` (real DOM, real events) or classic JSDOM-based
|
|
8
|
-
tests with `@testing-library/preact`. Wires `vitest.config.ts`,
|
|
9
|
-
`LoaderArgs`, and emits ready-to-run files.
|
|
8
|
+
tests with `@testing-library/preact`. Wires `vitest.config.ts`, builds
|
|
9
|
+
`LoaderArgs` with `@pracht/test`, and emits ready-to-run files.
|
|
10
10
|
Use when asked to "scaffold tests", "set up Vitest", "add unit tests",
|
|
11
11
|
"test this loader", or "test this route".
|
|
12
12
|
allowed-tools:
|
|
@@ -48,9 +48,14 @@ Detect the package manager from the lockfile.
|
|
|
48
48
|
Common (both modes):
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
pnpm add -D vitest @types/node
|
|
51
|
+
pnpm add -D vitest @types/node @pracht/test
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
`@pracht/test` provides the typed args factories (`createLoaderArgs`,
|
|
55
|
+
`createApiArgs`, `createMiddlewareArgs`, `createApiMiddlewareArgs`), the
|
|
56
|
+
`runMiddleware()` chain runner, `submitForm()`, and the
|
|
57
|
+
`readJson()`/`readRedirect()` response readers used in the templates below.
|
|
58
|
+
|
|
54
59
|
Component-test extras — `@preact/preset-vite` must be an explicit dev
|
|
55
60
|
dependency: the configs in Step 3 import it, and a transitive-only copy
|
|
56
61
|
fails under pnpm's strict `node_modules`.
|
|
@@ -145,75 +150,54 @@ to scaffold, or pass paths via `$ARGUMENTS`.
|
|
|
145
150
|
|
|
146
151
|
```ts
|
|
147
152
|
import { describe, it, expect } from "vitest";
|
|
153
|
+
import { createLoaderArgs } from "@pracht/test";
|
|
148
154
|
import { loader } from "./<route-file>";
|
|
149
155
|
|
|
150
|
-
function args(url: string, init?: RequestInit) {
|
|
151
|
-
const request = new Request(url, init);
|
|
152
|
-
return {
|
|
153
|
-
request,
|
|
154
|
-
params: {} as Record<string, string>,
|
|
155
|
-
context: {} as never,
|
|
156
|
-
url: new URL(request.url),
|
|
157
|
-
signal: AbortSignal.timeout(5000),
|
|
158
|
-
route: {} as never,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
156
|
describe("<route> loader", () => {
|
|
163
157
|
it("returns the expected shape", async () => {
|
|
164
|
-
const data = await loader(
|
|
158
|
+
const data = await loader(createLoaderArgs({ url: "/<path>" }));
|
|
165
159
|
expect(data).toBeDefined();
|
|
166
160
|
});
|
|
167
161
|
});
|
|
168
162
|
```
|
|
169
163
|
|
|
164
|
+
Pass `params`, `headers`, a partial `context`, or a JSON-encoding `body` as
|
|
165
|
+
needed; the factory defaults everything else and exposes `controller` (the
|
|
166
|
+
`AbortController` behind `args.signal`) for cancellation tests.
|
|
167
|
+
|
|
170
168
|
### Middleware test template
|
|
171
169
|
|
|
172
170
|
```ts
|
|
173
171
|
import { describe, it, expect } from "vitest";
|
|
172
|
+
import { createMiddlewareArgs, readRedirect, runMiddleware } from "@pracht/test";
|
|
174
173
|
import { middleware } from "./<middleware-file>";
|
|
175
174
|
|
|
176
175
|
describe("<name> middleware", () => {
|
|
177
|
-
const ok = new Response("ok", { status: 200 });
|
|
178
|
-
const next = async () => ok;
|
|
179
|
-
|
|
180
176
|
it("redirects unauthenticated requests", async () => {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
{
|
|
184
|
-
request,
|
|
185
|
-
params: {},
|
|
186
|
-
context: {} as never,
|
|
187
|
-
url: new URL(request.url),
|
|
188
|
-
signal: AbortSignal.timeout(5000),
|
|
189
|
-
route: {} as never,
|
|
190
|
-
},
|
|
191
|
-
next,
|
|
192
|
-
);
|
|
193
|
-
expect(response.status).toBe(302);
|
|
194
|
-
expect(response.headers.get("location")).toMatch(/^\/login/);
|
|
177
|
+
const response = await runMiddleware(middleware, createMiddlewareArgs({ url: "/dashboard" }));
|
|
178
|
+
expect(readRedirect(response).location).toMatch(/^\/login/);
|
|
195
179
|
});
|
|
196
180
|
|
|
197
181
|
it("calls through when authenticated", async () => {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
request,
|
|
204
|
-
params: {},
|
|
205
|
-
context: {} as never,
|
|
206
|
-
url: new URL(request.url),
|
|
207
|
-
signal: AbortSignal.timeout(5000),
|
|
208
|
-
route: {} as never,
|
|
209
|
-
},
|
|
210
|
-
next,
|
|
182
|
+
const response = await runMiddleware(
|
|
183
|
+
middleware,
|
|
184
|
+
createMiddlewareArgs({ url: "/dashboard", headers: { cookie: "session=valid" } }),
|
|
185
|
+
async () => new Response("handler ran"),
|
|
211
186
|
);
|
|
212
|
-
expect(response).toBe(
|
|
187
|
+
expect(await response.text()).toBe("handler ran");
|
|
213
188
|
});
|
|
214
189
|
});
|
|
215
190
|
```
|
|
216
191
|
|
|
192
|
+
`runMiddleware()` accepts an array to test a chain in manifest order,
|
|
193
|
+
including `context` mutations flowing downstream and returned-response
|
|
194
|
+
short-circuits. A thrown `Response` resolves by default, matching page/API
|
|
195
|
+
outer normalization. For raw capability-chain behavior, pass
|
|
196
|
+
`undefined, { thrownResponse: "reject" }` after the args/final-handler slots;
|
|
197
|
+
prefer `createCapabilityTestHost()` so the test asserts the real typed
|
|
198
|
+
`internal_error` envelope. For middleware attached through `defineApp({ api })`,
|
|
199
|
+
use `createApiMiddlewareArgs()` so `route` has the API metadata shape.
|
|
200
|
+
|
|
217
201
|
### Component test template (browser mode)
|
|
218
202
|
|
|
219
203
|
```tsx
|
|
@@ -274,8 +258,8 @@ broken scaffolding.
|
|
|
274
258
|
1. Ask the rendering-strategy question once per project; persist by
|
|
275
259
|
inspecting `vitest.config.ts` on subsequent runs.
|
|
276
260
|
2. Only test exports that exist — read the route file before generating.
|
|
277
|
-
3. Use
|
|
278
|
-
construction.
|
|
261
|
+
3. Use `@pracht/test`'s factories for `BaseRouteArgs`/`LoaderArgs`
|
|
262
|
+
construction instead of hand-building args objects.
|
|
279
263
|
4. For routes with `getStaticPaths`, scaffold a separate test that calls it.
|
|
280
264
|
5. Generated tests should pass on first run with a placeholder assertion;
|
|
281
265
|
the user fills in real expectations.
|
|
@@ -141,6 +141,9 @@ on the router `mode` from Step 1:
|
|
|
141
141
|
`export const RENDER_MODE = "ssg"` in the page module (valid values
|
|
142
142
|
`"ssr" | "ssg" | "isg" | "spa"`; the default is `"ssr"`, overridable
|
|
143
143
|
globally via `pracht({ pagesDefaultRender: "..." })` in vite config).
|
|
144
|
+
For ISG, also add a positive integer time policy such as
|
|
145
|
+
`export const REVALIDATE = 3600`; pages mode requires it and supports
|
|
146
|
+
time-based revalidation only. Eject for webhook or combined policies.
|
|
144
147
|
Hydration is `export const HYDRATION = "..."` in the same file. If most
|
|
145
148
|
pages want the same mode, prefer changing `pagesDefaultRender` over adding
|
|
146
149
|
a constant to every file.
|
|
@@ -159,7 +162,7 @@ Apply the edits only after the user confirms.
|
|
|
159
162
|
| ---------- | -------------------------------------------------------------- | ----- |
|
|
160
163
|
| Node | Filesystem: `isg-manifest.json` + file-mtime revalidation | Serves stale immediately, refreshes in place. |
|
|
161
164
|
| Cloudflare | Worker-managed Workers Cache API, **per colo** — works without any extra config | `cloudflareAdapter({ cache: true })` + `"cache": { "enabled": true }` in wrangler config is an **optional upgrade** that moves time-revalidated routes to an edge-tier cache in front of the Worker; webhook-only routes stay worker-managed. Webhook invalidation on the default path is per-colo, not a global purge. |
|
|
162
|
-
| Vercel | Native ISR: Build Output API prerender functions with `expiration` from the time policy; `PRACHT_REVALIDATE_TOKEN` becomes the `bypassToken`
|
|
165
|
+
| Vercel | Native ISR: Build Output API prerender functions with `expiration` from the time policy; for webhook revalidation, `PRACHT_REVALIDATE_TOKEN` becomes the `bypassToken` and must be set at build time | ISG routes deploy as Node Serverless Functions (Vercel rejects ISR on an Edge Function) while SSR stays on the edge. Time-only ISR does not require the token. See docs/ADAPTERS.md. |
|
|
163
166
|
|
|
164
167
|
4. For dynamic SSG/ISG routes, ensure `getStaticPaths` exists. Flag if missing.
|
|
165
168
|
5. Use `pracht inspect routes --json` rather than reading `src/routes.ts`
|