create-pracht 0.6.2 → 0.7.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: migrate-nextjs
3
- version: 1.3.0
3
+ version: 1.7.0
4
4
  description: |
5
5
  Migrate a Next.js app to pracht: App or Pages Router pages, layouts, middleware,
6
6
  API routes, data fetching, and metadata — plus React→Preact, `className`→`class`,
@@ -25,11 +25,8 @@ before converting it; never infer from the filename. Prefer the simplest
25
25
  pracht equivalent, and when a Next.js feature has no equivalent, say so and
26
26
  propose an alternative instead of inventing one.
27
27
 
28
- MCP: when the pracht MCP server is registered (docs/MCP.md), use
29
- `generate_route`/`generate_shell`/`generate_middleware`/`generate_api` to
30
- scaffold and `inspect_routes`/`inspect_api`/`doctor`/`verify` to check
31
- progress, instead of Bash. `pracht inspect` needs the pracht plugin in the
32
- vite config; `inspect_build` needs a prior `pracht build`.
28
+ With the Pracht MCP server, prefer its generate/inspect/doctor/verify tools;
29
+ `inspect_build` needs a prior `pracht build`.
33
30
 
34
31
  ## Step 0: Assess the source
35
32
 
@@ -40,6 +37,9 @@ the tree: `app/` (App Router), `pages/` (Pages Router), `middleware.ts`,
40
37
  `"use server"` actions — and the third-party integrations (auth, CMS, DB,
41
38
  analytics). Confirm scope with the user if the app has more than ~20 routes.
42
39
 
40
+ Agent tools alone do not require a router migration; use the standalone host:
41
+ <https://pracht.resynapse.dev/docs/standalone-capabilities>.
42
+
43
43
  ## Fast path: Pages Router
44
44
 
45
45
  `pagesDir` makes a pages-router source near-drop-in — **Phase 7 is then
@@ -47,13 +47,25 @@ automatic**:
47
47
 
48
48
  1. `pracht({ pagesDir: "/src/pages" })` in `vite.config.ts`; copy `pages/` to
49
49
  `src/pages/`.
50
- 2. `_app.tsx` → pracht shell shape (`Shell` export taking `children`).
50
+ 2. `_app.tsx` → pracht shell shape (`Shell` export taking `children`). A
51
+ subdirectory `_app` scopes a shell to that subtree (`blog/_app.tsx` →
52
+ `"pages:blog"`), where App Router `layout.tsx` lands — but shells replace
53
+ rather than nest, so it repeats the chrome, `head()`, and `headers()` it
54
+ needs.
51
55
  3. `getServerSideProps`/`getStaticProps` → `loader` export.
52
56
  4. `export const RENDER_MODE = "ssg"` on static pages (`"ssr"` is the
53
57
  default). For time-revalidated pages export `RENDER_MODE = "isg"` plus a
54
58
  positive integer `REVALIDATE` in seconds; webhook policies require ejecting
55
59
  to a manifest.
56
- 5. Run the dev server, iterate, and optionally eject later with
60
+ 5. `middleware.ts` a root-level `src/pages/_middleware.ts` exporting a
61
+ `MiddlewareFn` (Phase 6). It runs on every page route; API routes are not
62
+ wrapped. Move `config.matcher` checks into the body and compare
63
+ `stripBase(url.pathname)`. A nested `_middleware`, a `_middleware/`
64
+ directory, and a missing export are build/doctor/verify errors.
65
+ 6. Other `_`-prefixed files and directories are reserved: pracht ignores the
66
+ subtree. Capabilities live in `src/capabilities/` (auto-discovered,
67
+ self-named); `agents` / `constraints` in `src/pages/_app.config.ts`.
68
+ 7. Run the dev server, iterate, and optionally eject later with
57
69
  `generateRoutesFile`.
58
70
 
59
71
  ## Concept mapping
@@ -62,11 +74,11 @@ automatic**:
62
74
  | ------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------- |
63
75
  | `pages/` directory | `pagesDir` plugin option | Auto-discovers routes from the file system |
64
76
  | `app/page.tsx` | `src/routes/*.tsx` + `route()` in manifest | File is a module; wiring is explicit |
65
- | `app/layout.tsx` | `src/shells/*.tsx` + `shells` in `defineApp` | Shells are named, not directory-nested |
77
+ | `app/layout.tsx` | `src/shells/*.tsx` + `shells` in `defineApp` (pages: `_app.tsx` per directory) | Named shells; a directory `_app` replaces its parent instead of nesting |
66
78
  | `app/loading.tsx` | `Loading` export on the shell | SSR placeholder for SPA routes until the client router takes over |
67
79
  | `app/error.tsx` | `ErrorBoundary` export in route module | Same concept, different wiring |
68
80
  | `app/not-found.tsx` | `notFound:` in `defineApp` (or `pages/404.tsx` in pagesDir mode) | Not a route — never matches a URL, so it cannot shadow static assets |
69
- | `middleware.ts` | `src/middleware/*.ts` + `middleware` in `defineApp` | Named, applied per route/group |
81
+ | `middleware.ts` | `src/middleware/*.ts` + `middleware` in `defineApp` (or `src/pages/_middleware.ts` in pagesDir mode) | Named, applied per route/group; the pages-mode file runs on every page route |
70
82
  | `app/api/*/route.ts` | `src/api/*.ts` with `GET`/`POST` exports | Auto-discovered, no manifest entry |
71
83
  | `generateStaticParams` | `getStaticPaths()` export | Returns `RouteParams[]` of param objects |
72
84
  | `generateMetadata` | `head()` export | Returns `{ title, meta }` |
@@ -204,6 +216,14 @@ export const middleware: MiddlewareFn = async ({ request }, next) => {
204
216
  is wrap-around (Hono/Koa/Astro shape), so you can `await next()` and observe
205
217
  the response — useful for tracing.
206
218
 
219
+ In `pagesDir` mode the same `MiddlewareFn` goes in a root-level
220
+ `src/pages/_middleware.ts`, applied to every page route (API routes stay
221
+ unwrapped). Do not migrate a per-request auth matcher onto an `ssg`/`isg` page
222
+ and call it protected: that document runs middleware only at
223
+ build/revalidation, with a sanitized request. Keep such pages `ssr`/`spa`, or
224
+ keep a separately verified edge gate. Pure static exports have no request
225
+ runtime.
226
+
207
227
  ## Phase 7: Route manifest
208
228
 
209
229
  Skip this phase for `pagesDir` projects. Prefer
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pracht-scaffold
3
- version: 1.2.0
3
+ version: 1.4.0
4
4
  description: |
5
5
  Scaffold pracht code with the native generators (`pracht generate
6
6
  route|shell|middleware|api`), falling back to manual edits only when the CLI
@@ -57,6 +57,16 @@ agent or tool consumes the output. When the pracht MCP server is registered
57
57
 
58
58
  - `--shell`/`--middleware` names must already be registered or the CLI errors.
59
59
  Generate the shell or middleware first, then the route referencing it.
60
+ - Pages-router apps have one middleware seam: `pracht generate middleware
61
+ --name _middleware` scaffolds the root `src/pages/_middleware.ts`, which runs
62
+ on every page route (API routes are not wrapped). Other names error in pages
63
+ mode, pure static exports cannot use request middleware, and `generate shell`
64
+ stays manifest-only — pages apps add an `_app.tsx` to the directory they want
65
+ it to wrap (`src/pages/blog/_app.tsx` registers `pages:blog`).
66
+ - `generate capability` works in both modes. Manifest apps get a `capabilities`
67
+ registry entry; pages apps auto-discover `src/capabilities/`, so the module
68
+ declares its own `name` and no manifest is written. Pages `agents` and
69
+ `constraints` are named exports in `src/pages/_app.config.ts`.
60
70
  - `generate route` also emits a Playwright smoke test in `e2e/` when the app
61
71
  has a Playwright setup (`playwright.config.*` or an `e2e/` directory);
62
72
  `--no-test` skips it, `--test` forces it. The test imports
@@ -127,6 +127,42 @@ field (absent from the JSON), grep the manifest for `hydration:` (pages apps:
127
127
  router), island props must be JSON-serializable, and `render: "spa"` cannot
128
128
  combine with `"islands"`/`"none"`.
129
129
 
130
+ ## Step 3c: Consider streaming for slow SSR routes
131
+
132
+ An `ssr` route whose loader has one slow call and several fast ones is a
133
+ candidate for `defer()` plus `streaming: true`. Flag it when the loader awaits
134
+ more than one independent source and at least one is materially slower — the
135
+ whole document currently waits on the slowest.
136
+
137
+ ```ts
138
+ // before: TTFB waits on getReviews()
139
+ return { product: await getProduct(id), reviews: await getReviews(id) };
140
+
141
+ // after: shell flushes without reviews, which stream in
142
+ return { product: await getProduct(id), reviews: defer(getReviews(id)) };
143
+ ```
144
+
145
+ Pages routes use `export const STREAMING = true` with SSR and full hydration.
146
+
147
+ Recommend `streaming: true` only alongside a `<Suspense>` boundary reading the
148
+ deferred value with `use()`; without a boundary there is nothing to flush
149
+ early. Do not recommend it for:
150
+
151
+ - `ssg` / `isg` routes — they write files and the manifest rejects the
152
+ combination
153
+ - routes with `hydration` other than `"full"` — no client runtime resumes a
154
+ boundary, and the manifest rejects it
155
+ - routes whose loader is a single slow call with nothing else to show — the
156
+ shell would be empty and the user sees a skeleton rather than content
157
+
158
+ Also mention the tradeoffs when proposing it: a deferred rejection surfaces at
159
+ the read site rather than as an error document (the response is already
160
+ committed at `200`), and streaming needs a `script-src` that allows the
161
+ renderer's inline bootstrap script. Route `head()` and `headers()` hooks run
162
+ before deferred work settles, so any data they need must stay awaited. Confirm
163
+ the app uses
164
+ `preact-render-to-string` 6.7 or newer before enabling streaming.
165
+
130
166
  ## Step 4: Propose diffs, then apply on confirmation
131
167
 
132
168
  Present the exact edits and wait for approval. Where the edit lands depends
@@ -145,8 +145,9 @@ export function Component() {
145
145
 
146
146
  Prefer this over `useRouteData<typeof loader>()` when typegen runs; keep the
147
147
  generic form for projects that do not generate route types. Routes without a
148
- loader type their data as `undefined`. The id must be the active route — dev
149
- mode warns on mismatches.
148
+ loader type their data as `undefined`. The id must be the active route: a
149
+ mismatch throws (with a descriptive message in dev), so a shell or island that
150
+ needs another route's data must receive it as props instead.
150
151
 
151
152
  ### API routes
152
153
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: upgrade-pracht
3
- version: 1.0.2
3
+ version: 1.1.0
4
4
  description: |
5
5
  Upgrade the `@pracht/*` packages safely: inventory installed versions, read the
6
6
  changelogs between installed and target, map breaking changes to real usage,
@@ -30,11 +30,15 @@ List every installed pracht package and its resolved version:
30
30
  pnpm list --depth 1 --json | grep -A2 '@pracht/' # or read package.json + lockfile
31
31
  ```
32
32
 
33
- The family: `@pracht/core`, `@pracht/cli`, `@pracht/vite-plugin`,
34
- `@pracht/adapter-node`, `@pracht/adapter-cloudflare`, `@pracht/adapter-vercel`,
35
- `@pracht/preact-ssr-precompile`, `@pracht/content`, `@pracht/markdown`,
36
- `@pracht/image`. Get the latest published versions with
37
- `npm view <pkg> version`.
33
+ The family, in full: `@pracht/core`, `@pracht/cli`, `@pracht/vite-plugin`,
34
+ `@pracht/adapter-node`, `@pracht/adapter-cloudflare`,
35
+ `@pracht/adapter-netlify`, `@pracht/adapter-vercel`,
36
+ `@pracht/adapter-static`, `@pracht/preact-ssr-precompile`,
37
+ `@pracht/capabilities`, `@pracht/content`, `@pracht/markdown`,
38
+ `@pracht/openapi`, `@pracht/image`, `@pracht/i18n`, `@pracht/session`,
39
+ `@pracht/test`, plus the `create-pracht` scaffolder (which is not a runtime
40
+ dependency and does not need to move with the rest). Get the latest published
41
+ versions with `npm view <pkg> version`.
38
42
 
39
43
  ## Step 2: Understand the versioning model
40
44
 
@@ -73,11 +77,17 @@ https://raw.githubusercontent.com/JoviDeCroock/pracht/main/packages/<dir>/CHANGE
73
77
  | `@pracht/core` | `packages/framework` |
74
78
  | `@pracht/cli` | `packages/cli` |
75
79
  | `@pracht/vite-plugin` | `packages/vite-plugin` |
76
- | `@pracht/adapter-node` / `-cloudflare` / `-vercel` | `packages/adapter-*` |
80
+ | `@pracht/adapter-node` / `-cloudflare` / `-netlify` / `-vercel` / `-static` | `packages/adapter-*` |
77
81
  | `@pracht/preact-ssr-precompile` | `packages/preact-ssr-precompile` |
82
+ | `@pracht/capabilities` | `packages/capabilities` |
78
83
  | `@pracht/content` | `packages/content` |
79
84
  | `@pracht/markdown` | `packages/markdown` |
85
+ | `@pracht/openapi` | `packages/openapi` |
80
86
  | `@pracht/image` | `packages/image` |
87
+ | `@pracht/i18n` | `packages/i18n` |
88
+ | `@pracht/session` | `packages/session` |
89
+ | `@pracht/test` | `packages/test` |
90
+ | `create-pracht` | `packages/start` |
81
91
 
82
92
  Changelogs are changesets-generated: `## X.Y.Z` sections containing
83
93
  `### Major Changes` / `### Minor Changes` / `### Patch Changes`. Read every
@@ -92,7 +102,7 @@ changelog prescribes, and whether it can be applied mechanically. Also
92
102
  re-check peer ranges after a major target bump — `@pracht/vite-plugin`
93
103
  requires `vite` (^8), `@pracht/adapter-cloudflare` requires `vite` and
94
104
  `wrangler` (^4.81), `@pracht/core` requires `preact` (^10) and
95
- `preact-render-to-string` (^6).
105
+ `preact-render-to-string` (^6.7).
96
106
 
97
107
  Present the plan as a table:
98
108