create-pracht 0.4.0 → 0.4.2
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 +2 -1
- package/package.json +1 -1
- package/skills/audit-auth/SKILL.md +5 -3
- package/skills/audit-csrf/SKILL.md +2 -1
- package/skills/audit-secrets/SKILL.md +6 -0
- package/skills/migrate-nextjs/SKILL.md +21 -4
- package/skills/pre-deploy/SKILL.md +12 -1
- package/skills/typed-routes/SKILL.md +4 -0
- package/skills/upgrade-pracht/SKILL.md +4 -3
- package/src/index.js +9 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm run dev
|
|
|
17
17
|
- Detects the active package manager from the current environment.
|
|
18
18
|
- Lets the user choose between the Node.js, Cloudflare, and Vercel adapters.
|
|
19
19
|
- Optionally wires up Tailwind CSS (`tailwindcss` + `@tailwindcss/vite`, a global stylesheet, and the shell import).
|
|
20
|
-
- Scaffolds a minimal app with a route manifest or pages router, shell, home route, sample API route, runnable project README, and agent instructions.
|
|
20
|
+
- Scaffolds a minimal app with a route manifest or pages router, shell, home route, sample API route, runnable project README, TypeScript typecheck script, and agent instructions.
|
|
21
21
|
- Manifest scaffolds include a commented-out `constraints` example in `src/routes.ts`, ready for `pracht verify`.
|
|
22
22
|
- The generated `.gitignore` keeps `.pracht/app-graph.json` committable, and the README and agent instructions cover the `pracht verify` / `pracht plan` / `pracht report` loop.
|
|
23
23
|
- Seeds the pracht Claude Code skills into `.claude/skills/` and writes a `.mcp.json` registering the `pracht mcp` server (yes-default prompt; skipped with `--no-agent-tools`).
|
|
@@ -80,6 +80,7 @@ Cloudflare scaffolds also include:
|
|
|
80
80
|
|
|
81
81
|
- `dev` -> `pracht dev`
|
|
82
82
|
- `build` -> `pracht build`
|
|
83
|
+
- `typecheck` -> `tsc --noEmit`
|
|
83
84
|
|
|
84
85
|
Node starters also include:
|
|
85
86
|
|
package/package.json
CHANGED
|
@@ -91,9 +91,11 @@ target. From `pracht inspect api --json`:
|
|
|
91
91
|
serves ALL methods but reports `methods: []` — treat
|
|
92
92
|
`hasDefaultHandler: true` as "every method exposed". On older CLIs where
|
|
93
93
|
the field is missing, grep the handler file for `export default` instead.
|
|
94
|
-
- For each mutation handler (named method export or default handler)
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
- For each mutation handler (named method export or default handler) and each
|
|
95
|
+
HTTP-exposed capability, check whether
|
|
96
|
+
`defineApp({ api: { middleware } })` applies a Gate, OR the handler/capability
|
|
97
|
+
reads and validates a session itself. App-level API middleware wraps generated
|
|
98
|
+
capability endpoints before capability-specific middleware.
|
|
97
99
|
- Common bug: dashboard route is protected by middleware, but
|
|
98
100
|
`POST /api/items` is not — attacker bypasses the UI entirely.
|
|
99
101
|
|
|
@@ -128,7 +128,8 @@ The canonical shape is in `recipes-auth.md` (the `origin-check.ts` example).
|
|
|
128
128
|
|
|
129
129
|
Verify the wiring: the middleware name must appear in
|
|
130
130
|
`defineApp({ api: { middleware: [...] } })` — that single global list applies
|
|
131
|
-
to every API route. There is no
|
|
131
|
+
to every API route and generated capability HTTP endpoint. There is no
|
|
132
|
+
per-group API middleware, and
|
|
132
133
|
`pracht inspect api --json` output has no middleware field, so the manifest is
|
|
133
134
|
the only place to check.
|
|
134
135
|
|
|
@@ -115,6 +115,12 @@ Check for accidental exposure outside loaders:
|
|
|
115
115
|
intentionally public, so flag client-side `VITE_*` references unless they are
|
|
116
116
|
explicitly allowlisted and reviewed. Warn loudly if a public env name has a
|
|
117
117
|
secret-shaped name.
|
|
118
|
+
- Flag any client-side read of `import.meta.env` that is not a single-key
|
|
119
|
+
access — a bare reference, destructuring, a spread, or bracket access such as
|
|
120
|
+
`const env = import.meta.env` or `import.meta.env["MODE"]`. Vite replaces
|
|
121
|
+
those with an object literal holding **every** exposed variable, so the
|
|
122
|
+
`VITE_*` values land in the bundle with no accessor text left for a
|
|
123
|
+
name-based grep to find. Use `publicEnv` to enumerate public values.
|
|
118
124
|
- Confirm server-side env access uses `serverEnv` (from
|
|
119
125
|
`@pracht/core/env/server`) or `context.env` rather than ad-hoc globals.
|
|
120
126
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: migrate-nextjs
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.2.0
|
|
4
4
|
description: |
|
|
5
5
|
Migrate a Next.js application to Pracht. Converts App Router pages, layouts,
|
|
6
6
|
middleware, API routes, data fetching, and metadata to pracht equivalents.
|
|
@@ -83,7 +83,7 @@ For pages router projects, you can **skip manual manifest wiring entirely** (Pha
|
|
|
83
83
|
| `next/link` `<Link>` | `<Link route="...">` or plain `<a>` | Prefer typed `<Link>` for known app routes after `pracht typegen`; plain anchors still work |
|
|
84
84
|
| `next/link` `prefetch={false}` | `<Link prefetch="none">` | Pracht prefetches on hover/focus by default; also `"viewport"`, `"render"` |
|
|
85
85
|
| `useLinkStatus()` / pending UI | `useNavigation()` | `{ state, location, formData }` — powers progress bars and optimistic UI |
|
|
86
|
-
| `next/image` |
|
|
86
|
+
| `next/image` | `<Image>` from `@pracht/image` | Responsive srcsets plus Node, Cloudflare, Vercel, or passthrough loaders |
|
|
87
87
|
| `next/head` or Metadata API | `head()` export on route/shell | Per-route and per-shell head merging |
|
|
88
88
|
| `className` | `class` | Preact uses `class` attribute |
|
|
89
89
|
| `React.useState` etc. | `import { useState } from "preact/hooks"` | Preact hooks API is compatible |
|
|
@@ -117,6 +117,7 @@ For pages router projects, you can **skip manual manifest wiring entirely** (Pha
|
|
|
117
117
|
3. Update `package.json`:
|
|
118
118
|
- Replace `react`, `react-dom` → `preact`
|
|
119
119
|
- Replace `next` → `@pracht/core` (framework runtime), `@pracht/cli` (provides the `pracht` bin), `@pracht/vite-plugin`, and `@pracht/adapter-node` (or target adapter). There is no package named `pracht`.
|
|
120
|
+
- If the app imports `next/image`, add `@pracht/image`; add `sharp` only for the built-in Node optimization endpoint.
|
|
120
121
|
- Update scripts: `dev` → `pracht dev`, `build` → `pracht build`, `start` → `node dist/server/server.js` (Node.js) or a platform-specific deploy command; add `preview` → `pracht preview` to serve the production build locally
|
|
121
122
|
4. Remove Next.js config files: `next.config.*`, `next-env.d.ts`, `.next/`
|
|
122
123
|
5. If `tsconfig.json` has `"jsx": "preserve"`, change to `"jsx": "react-jsx"` and add `"jsxImportSource": "preact"`.
|
|
@@ -388,7 +389,7 @@ Plain anchors still work for simple, external, or user-provided URLs because the
|
|
|
388
389
|
|
|
389
390
|
`<Link>` also accepts navigation-behavior props: `prefetch` (`"none" | "intent" | "viewport" | "render"`, default `"intent"` on hover/focus — the equivalent of `next/link`'s `prefetch` tuning), `preserveScroll` (skip the scroll-to-top reset), and `viewTransition` (wrap the navigation in `document.startViewTransition()` where supported). Scroll restoration on back/forward works out of the box, like Next.js.
|
|
390
391
|
|
|
391
|
-
#### `next/image` →
|
|
392
|
+
#### `next/image` → `@pracht/image`
|
|
392
393
|
|
|
393
394
|
```tsx
|
|
394
395
|
// Next.js
|
|
@@ -396,9 +397,24 @@ import Image from "next/image";
|
|
|
396
397
|
<Image src="/photo.jpg" width={500} height={300} alt="Photo" />
|
|
397
398
|
|
|
398
399
|
// Pracht
|
|
399
|
-
|
|
400
|
+
import { Image } from "@pracht/image";
|
|
401
|
+
<Image src="/photo.jpg" width={500} height={300} alt="Photo" />
|
|
400
402
|
```
|
|
401
403
|
|
|
404
|
+
Choose the loader for the deployment target:
|
|
405
|
+
|
|
406
|
+
- Node: mount `createImageHandler()` from `@pracht/image/node`, install
|
|
407
|
+
`sharp`, and set its `localOrigin` to the same trusted value as
|
|
408
|
+
`nodeAdapter({ canonicalOrigin })`.
|
|
409
|
+
- Cloudflare Workers: configure `cloudflareLoader`; do not bundle the Node
|
|
410
|
+
handler because `sharp` does not run in Workers.
|
|
411
|
+
- Vercel Edge: configure `vercelLoader` and keep Vercel's allowed image sizes
|
|
412
|
+
aligned with the Pracht breakpoints.
|
|
413
|
+
- Static hosts: configure `passthroughLoader`.
|
|
414
|
+
|
|
415
|
+
Preserve the original `width`, `height`, `fill`, `sizes`, `quality`, and
|
|
416
|
+
priority intent. See `docs/IMAGES.md` for the endpoint and loader wiring.
|
|
417
|
+
|
|
402
418
|
#### `useRouter` → navigation
|
|
403
419
|
|
|
404
420
|
```tsx
|
|
@@ -483,6 +499,7 @@ export async function loader({ request }: LoaderArgs) {
|
|
|
483
499
|
| Next.js package | Pracht equivalent |
|
|
484
500
|
| --------------- | ---------------------------------------------------------------------------- |
|
|
485
501
|
| `next` | `@pracht/core` + `@pracht/cli` + `@pracht/vite-plugin` + `@pracht/adapter-node` (or target adapter) |
|
|
502
|
+
| `next/image` | `@pracht/image` |
|
|
486
503
|
| `react` | `preact` |
|
|
487
504
|
| `react-dom` | `preact` |
|
|
488
505
|
| `@next/font` | CSS `@font-face` or `fontsource` packages |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pre-deploy
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.2.0
|
|
4
4
|
description: |
|
|
5
5
|
Adapter-aware pre-deployment checklist for pracht apps targeting Node,
|
|
6
6
|
Cloudflare Workers, or Vercel. Catches the issues that only surface in the
|
|
@@ -75,6 +75,11 @@ a markdown summary (graph diff + verify + budgets) worth attaching to it.
|
|
|
75
75
|
- Smoke test: `pracht preview --skip-build` (or `node dist/server/server.js`) boots and `curl localhost:3000` returns 200.
|
|
76
76
|
- Required env vars (grep `process.env.*` across `src/`) are set in the
|
|
77
77
|
deployment environment. List them for the user.
|
|
78
|
+
- If the app mounts `createImageHandler()` from `@pracht/image/node`, confirm
|
|
79
|
+
`sharp` is installed and `localOrigin` is the same trusted public origin as
|
|
80
|
+
`nodeAdapter({ canonicalOrigin })`. A relative image endpoint without both
|
|
81
|
+
values is an error in every environment; loopback-looking request origins
|
|
82
|
+
are intentionally not trusted.
|
|
78
83
|
- Reverse-proxy / TLS termination configured (out of scope for this skill —
|
|
79
84
|
flag for confirmation).
|
|
80
85
|
|
|
@@ -101,6 +106,9 @@ a markdown summary (graph diff + verify + budgets) worth attaching to it.
|
|
|
101
106
|
- Dev already runs inside workerd via `@cloudflare/vite-plugin`, so most
|
|
102
107
|
incompatibilities surface in dev; this check is the backstop for code
|
|
103
108
|
paths dev never hit.
|
|
109
|
+
- An API route importing `@pracht/image/node` is an error on Workers because
|
|
110
|
+
its optimizer requires `sharp`. Require `cloudflareLoader` (or
|
|
111
|
+
`passthroughLoader`) instead.
|
|
104
112
|
- ISG: worker-managed ISG via the per-colo Workers Cache API works out of the
|
|
105
113
|
box. If time-revalidated routes should use the edge-tier Workers Caching
|
|
106
114
|
upgrade instead, confirm both sides — `cloudflareAdapter({ cache: true })`
|
|
@@ -131,6 +139,9 @@ a markdown summary (graph diff + verify + budgets) worth attaching to it.
|
|
|
131
139
|
variant, so run the same Node-only API check as Cloudflare
|
|
132
140
|
**unconditionally** for Vercel builds. Do not skip it based on a runtime
|
|
133
141
|
probe.
|
|
142
|
+
- An API route importing `@pracht/image/node` is an error for the Vercel Edge
|
|
143
|
+
function. Require `vercelLoader` (with aligned allowed sizes) or
|
|
144
|
+
`passthroughLoader` instead.
|
|
134
145
|
- Build Output API v3 sanity: `config.json` has `version: 3`.
|
|
135
146
|
|
|
136
147
|
## Step 4: Cross-cutting checks
|
|
@@ -60,6 +60,10 @@ This writes:
|
|
|
60
60
|
- `src/pracht.d.ts` — module augmentation for route ids, params, loader data
|
|
61
61
|
types, and API route request/response types (consumed by `apiFetch()`).
|
|
62
62
|
- `src/pracht-routes.ts` — runtime `href()` helper backed by the same route map.
|
|
63
|
+
- `src/pracht-capabilities.d.ts` — only when the app registers capabilities:
|
|
64
|
+
input/output types from the capability schemas, so `invokeCapability()`,
|
|
65
|
+
`callCapability()`, and `<Form capability>`'s `onCapabilityResult` infer
|
|
66
|
+
types from the capability name.
|
|
63
67
|
|
|
64
68
|
Earlier versions wrote the declaration to `src/pracht-routes.d.ts`; typegen
|
|
65
69
|
removes that stale file automatically (TypeScript silently ignored it next to
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: upgrade-pracht
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: |
|
|
5
5
|
Upgrade the @pracht/* packages in an app safely: inventory installed
|
|
6
6
|
versions, read the changelogs between installed and target, map breaking
|
|
@@ -33,8 +33,8 @@ pnpm list --depth 1 --json | grep -A2 '@pracht/' # or read package.json + lock
|
|
|
33
33
|
|
|
34
34
|
The family: `@pracht/core`, `@pracht/cli`, `@pracht/vite-plugin`,
|
|
35
35
|
`@pracht/adapter-node`, `@pracht/adapter-cloudflare`, `@pracht/adapter-vercel`,
|
|
36
|
-
`@pracht/preact-ssr-precompile`. Get the latest published
|
|
37
|
-
`npm view <pkg> version`.
|
|
36
|
+
`@pracht/preact-ssr-precompile`, `@pracht/image`. Get the latest published
|
|
37
|
+
versions with `npm view <pkg> version`.
|
|
38
38
|
|
|
39
39
|
## Step 2: Understand the versioning model
|
|
40
40
|
|
|
@@ -75,6 +75,7 @@ https://raw.githubusercontent.com/JoviDeCroock/pracht/main/packages/<dir>/CHANGE
|
|
|
75
75
|
| `@pracht/vite-plugin` | `packages/vite-plugin` |
|
|
76
76
|
| `@pracht/adapter-node` / `-cloudflare` / `-vercel` | `packages/adapter-*` |
|
|
77
77
|
| `@pracht/preact-ssr-precompile` | `packages/preact-ssr-precompile` |
|
|
78
|
+
| `@pracht/image` | `packages/image` |
|
|
78
79
|
|
|
79
80
|
Changelogs are changesets-generated: `## X.Y.Z` sections containing
|
|
80
81
|
`### Major Changes` / `### Minor Changes` / `### Patch Changes`. Read every
|
package/src/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const FALLBACK_VERSION_RANGES = {
|
|
|
21
21
|
"@pracht/vite-plugin": "^0.3.2",
|
|
22
22
|
"@tailwindcss/vite": "^4.1.0",
|
|
23
23
|
tailwindcss: "^4.1.0",
|
|
24
|
+
typescript: "^6.0.0",
|
|
24
25
|
vercel: "^56.5.0",
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -567,6 +568,7 @@ async function buildProjectFiles({
|
|
|
567
568
|
"@pracht/vite-plugin",
|
|
568
569
|
"@pracht/core",
|
|
569
570
|
adapter.packageName,
|
|
571
|
+
"typescript",
|
|
570
572
|
];
|
|
571
573
|
if (adapter.id === "vercel") {
|
|
572
574
|
packagesToResolve.push("vercel");
|
|
@@ -664,6 +666,7 @@ function createPackageJson({ adapter, projectName, tailwind, versions }) {
|
|
|
664
666
|
const scripts = {
|
|
665
667
|
build: "pracht build",
|
|
666
668
|
dev: "pracht dev",
|
|
669
|
+
typecheck: "tsc --noEmit",
|
|
667
670
|
};
|
|
668
671
|
|
|
669
672
|
if (adapter.id === "node") {
|
|
@@ -676,6 +679,7 @@ function createPackageJson({ adapter, projectName, tailwind, versions }) {
|
|
|
676
679
|
"@pracht/vite-plugin": versions["@pracht/vite-plugin"],
|
|
677
680
|
preact: "^10.26.9",
|
|
678
681
|
"preact-render-to-string": "^6.5.13",
|
|
682
|
+
typescript: versions["typescript"],
|
|
679
683
|
vite: "^8.0.0",
|
|
680
684
|
};
|
|
681
685
|
|
|
@@ -724,8 +728,8 @@ function createViteConfig(adapter, router, tailwind) {
|
|
|
724
728
|
|
|
725
729
|
const prachtOptions =
|
|
726
730
|
router === "pages"
|
|
727
|
-
? `{ pagesDir: "/src/pages", adapter: ${info.fn}() }`
|
|
728
|
-
: `{ adapter: ${info.fn}() }`;
|
|
731
|
+
? `{ pagesDir: "/src/pages", adapter: ${info.fn}(), llmsTxt: {} }`
|
|
732
|
+
: `{ adapter: ${info.fn}(), llmsTxt: {} }`;
|
|
729
733
|
|
|
730
734
|
const plugins = tailwind
|
|
731
735
|
? `[pracht(${prachtOptions}), tailwindcss()]`
|
|
@@ -1116,6 +1120,8 @@ function createReadme({ adapter, agentTools, packageManager, projectName, router
|
|
|
1116
1120
|
const previewCommand = packageManager === "npm" ? "npm run preview" : `${packageManager} preview`;
|
|
1117
1121
|
const startCommand = packageManager === "npm" ? "npm run start" : `${packageManager} start`;
|
|
1118
1122
|
const deployCommand = packageManager === "npm" ? "npm run deploy" : `${packageManager} deploy`;
|
|
1123
|
+
const typecheckCommand =
|
|
1124
|
+
packageManager === "npm" ? "npm run typecheck" : `${packageManager} typecheck`;
|
|
1119
1125
|
|
|
1120
1126
|
const lines = [
|
|
1121
1127
|
`# ${projectName}`,
|
|
@@ -1126,6 +1132,7 @@ function createReadme({ adapter, agentTools, packageManager, projectName, router
|
|
|
1126
1132
|
"",
|
|
1127
1133
|
`- \`${installCommand}\``,
|
|
1128
1134
|
`- \`${devCommand}\``,
|
|
1135
|
+
`- \`${typecheckCommand}\``,
|
|
1129
1136
|
];
|
|
1130
1137
|
|
|
1131
1138
|
if (adapter.id === "node") {
|