create-pracht 0.6.0 → 0.6.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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/skills/add-auth/SKILL.md +63 -143
  3. package/skills/add-capabilities/SKILL.md +409 -0
  4. package/skills/add-content/SKILL.md +242 -0
  5. package/skills/add-db/SKILL.md +93 -202
  6. package/skills/add-i18n/SKILL.md +178 -217
  7. package/skills/add-images/SKILL.md +203 -0
  8. package/skills/add-observability/SKILL.md +118 -15
  9. package/skills/add-openapi/SKILL.md +209 -0
  10. package/skills/audit-a11y/SKILL.md +8 -9
  11. package/skills/audit-agent-surface/SKILL.md +335 -0
  12. package/skills/audit-auth/SKILL.md +16 -11
  13. package/skills/audit-bundles/SKILL.md +56 -12
  14. package/skills/audit-csrf/SKILL.md +9 -10
  15. package/skills/audit-deps/SKILL.md +8 -8
  16. package/skills/audit-headers/SKILL.md +9 -10
  17. package/skills/audit-islands/SKILL.md +9 -10
  18. package/skills/audit-loaders/SKILL.md +23 -8
  19. package/skills/audit-redirects/SKILL.md +9 -10
  20. package/skills/audit-secrets/SKILL.md +6 -6
  21. package/skills/audit-seo/SKILL.md +8 -8
  22. package/skills/audit-shells/SKILL.md +8 -9
  23. package/skills/configure-isg/SKILL.md +9 -10
  24. package/skills/migrate-nextjs/SKILL.md +200 -415
  25. package/skills/pracht-debug/SKILL.md +165 -120
  26. package/skills/pracht-deploy/SKILL.md +248 -329
  27. package/skills/pracht-scaffold/SKILL.md +123 -146
  28. package/skills/pracht-test-api/SKILL.md +10 -10
  29. package/skills/pre-deploy/SKILL.md +166 -195
  30. package/skills/scaffold-e2e/SKILL.md +11 -12
  31. package/skills/scaffold-tests/SKILL.md +10 -12
  32. package/skills/tune-render-mode/SKILL.md +7 -8
  33. package/skills/typed-routes/SKILL.md +15 -11
  34. package/skills/upgrade-pracht/SKILL.md +12 -10
  35. package/src/index.js +43 -0
@@ -1,14 +1,12 @@
1
1
  ---
2
2
  name: add-db
3
- version: 1.1.0
3
+ version: 1.2.0
4
4
  description: |
5
- Wire Drizzle ORM into a pracht app. Asks the user which database to target
6
- (Cloudflare D1, PlanetScale, Neon, Supabase, Turso, Postgres, MySQL, SQLite,
7
- ...) and generates the matching driver setup, schema scaffold, migration
8
- workflow, and a typed client accessible from loaders, middleware, and API
9
- routes.
10
- Use when asked to "add database", "set up Drizzle", "wire D1",
11
- "add Postgres", "set up an ORM", or "I need a DB".
5
+ Wire Drizzle ORM into a pracht app: pick the target (D1, PlanetScale, Neon,
6
+ Supabase, Turso, Postgres, MySQL, SQLite), then generate driver setup, schema,
7
+ migration workflow, and a typed client for loaders, middleware, and API routes.
8
+ Use for "add database", "set up Drizzle", "wire D1", "add Postgres", "set up an
9
+ ORM", "I need a DB".
12
10
  allowed-tools:
13
11
  - Bash
14
12
  - Read
@@ -21,60 +19,47 @@ allowed-tools:
21
19
 
22
20
  # Pracht Add Database (Drizzle)
23
21
 
24
- Drizzle works well in pracht because it is small, type-safe, and runs in
25
- both Node and edge runtimes (Cloudflare Workers, Vercel Edge). This skill
26
- sets up the driver, schema directory, migration tooling, and a client
27
- factory wired to the project's adapter.
22
+ Drizzle suits pracht because it is small, type-safe, and runs in both Node and
23
+ edge runtimes. This skill sets up the driver, schema, migration tooling, and a
24
+ client factory wired to the project's adapter. Never overwrite an existing
25
+ `drizzle.config.ts`, `wrangler.toml`, or `package.json` script — diff, merge,
26
+ and ask about collisions.
28
27
 
29
- ## Step 1: Pick the target
30
-
31
- Use `AskUserQuestion`:
32
-
33
- | Provider | Driver | Adapter notes |
34
- | ------------------ | ---------------------------------------- | ---------------------------- |
35
- | Cloudflare D1 | `drizzle-orm/d1` | Workers binding |
36
- | Cloudflare Hyperdrive (Postgres) | `drizzle-orm/postgres-js` or `node-postgres` | Workers binding |
37
- | PlanetScale | `drizzle-orm/planetscale-serverless` | Works on Node + edge |
38
- | Neon (Postgres) | `drizzle-orm/neon-serverless` or `neon-http` | Works on Node + edge |
39
- | Supabase Postgres | `drizzle-orm/postgres-js` | Node + edge (HTTP variant) |
40
- | Turso (libSQL) | `drizzle-orm/libsql` | Node + edge |
41
- | Vanilla Postgres | `drizzle-orm/node-postgres` | Node only |
42
- | Vanilla MySQL | `drizzle-orm/mysql2` | Node only |
43
- | SQLite (better-sqlite3) | `drizzle-orm/better-sqlite3` | Node only |
44
-
45
- If the pracht MCP server is registered (see docs/MCP.md), prefer its tools
46
- (`inspect_routes`, `inspect_api`, `inspect_build`, `doctor`, `verify`,
47
- `generate_*`) over shelling out. Prerequisites: `pracht inspect` needs a vite
48
- config with the pracht plugin; `pracht inspect build` reads artifacts from a
49
- prior `pracht build`.
28
+ MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its
29
+ `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify`/`generate_*`
30
+ tools over shelling out. `pracht inspect` needs the pracht plugin in the vite
31
+ config; `inspect build` needs a prior `pracht build`.
50
32
 
51
- Cross-check with the project's pracht adapter (`pracht inspect build --json`):
52
- flag mismatches (e.g., `node-postgres` on Cloudflare Workers — won't work).
33
+ ## Step 1: Pick the target
53
34
 
54
- ## Step 2: Install
35
+ Ask with `AskUserQuestion`, then **cross-check against the project's adapter**
36
+ (`pracht inspect build --json`) and flag mismatches — `node-postgres` on
37
+ Cloudflare Workers will not work.
38
+
39
+ | Provider | Driver import | Extra package | Runtimes |
40
+ | -------------------------------- | -------------------------------------------- | ------------- | -------- |
41
+ | Cloudflare D1 | `drizzle-orm/d1` | — (Workers binding) | Edge |
42
+ | Cloudflare Hyperdrive (Postgres) | `drizzle-orm/postgres-js` or `node-postgres` | `postgres` / `pg` | Edge (binding) |
43
+ | PlanetScale | `drizzle-orm/planetscale-serverless` | `@planetscale/database` | Node + edge |
44
+ | Neon | `drizzle-orm/neon-serverless` or `neon-http` | `@neondatabase/serverless` | Node + edge |
45
+ | Supabase Postgres | `drizzle-orm/postgres-js` | `postgres` | Node + edge (HTTP) |
46
+ | Turso (libSQL) | `drizzle-orm/libsql` | `@libsql/client` | Node + edge |
47
+ | Vanilla Postgres | `drizzle-orm/node-postgres` | `pg` + `-D @types/pg` | Node only |
48
+ | Vanilla MySQL | `drizzle-orm/mysql2` | `mysql2` | Node only |
49
+ | SQLite | `drizzle-orm/better-sqlite3` | `better-sqlite3` + `-D @types/better-sqlite3` | Node only |
55
50
 
56
51
  ```bash
57
- pnpm add drizzle-orm <driver>
52
+ pnpm add drizzle-orm <driver-package>
58
53
  pnpm add -D drizzle-kit
59
54
  ```
60
55
 
61
- Specific drivers:
62
-
63
- - D1: no additional package; uses the Workers binding.
64
- - PlanetScale: `pnpm add @planetscale/database`.
65
- - Neon: `pnpm add @neondatabase/serverless`.
66
- - Postgres / Supabase: `pnpm add postgres` (postgres-js).
67
- - Turso: `pnpm add @libsql/client`.
68
- - node-postgres: `pnpm add pg && pnpm add -D @types/pg`.
69
- - mysql2: `pnpm add mysql2`.
70
- - better-sqlite3: `pnpm add better-sqlite3 && pnpm add -D @types/better-sqlite3`.
56
+ ## Step 2: Schema
71
57
 
72
- ## Step 3: Schema directory
73
-
74
- `src/db/schema.ts`:
58
+ `src/db/schema.ts` `pgTable` from `drizzle-orm/pg-core`, `sqliteTable` from
59
+ `drizzle-orm/sqlite-core` (D1 and SQLite), or `mysqlTable` from
60
+ `drizzle-orm/mysql-core`:
75
61
 
76
62
  ```ts
77
- // Postgres example — substitute sqliteTable / mysqlTable for other dialects.
78
63
  import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
79
64
 
80
65
  export const users = pgTable("users", {
@@ -84,15 +69,20 @@ export const users = pgTable("users", {
84
69
  });
85
70
  ```
86
71
 
87
- For D1/SQLite, use `sqliteTable` from `drizzle-orm/sqlite-core`. For MySQL,
88
- use `mysqlTable` from `drizzle-orm/mysql-core`.
72
+ ## Step 3: Client factory
89
73
 
90
- ## Step 4: Client factory
74
+ Read connection strings via `serverEnv` from `@pracht/core/env/server`, never
75
+ `process.env` — it keeps the secret out of the client bundle and resolves per
76
+ adapter (docs/ENV.md). The shape depends on the runtime:
91
77
 
92
- `src/db/client.ts`:
78
+ - **Node, persistent process** — a module-level singleton is fine, because
79
+ `serverEnv` works at module top level there.
80
+ - **Edge with per-request context (Cloudflare, Vercel Edge)** — read
81
+ `serverEnv` or the binding *inside* a factory. Workers env bindings only
82
+ exist per request, so a module-level read bricks the worker at import time.
93
83
 
94
84
  ```ts
95
- // Example for Postgres on Node:
85
+ // src/db/client.ts Postgres on Node
96
86
  import { serverEnv } from "@pracht/core/env/server";
97
87
  import { drizzle } from "drizzle-orm/node-postgres";
98
88
  import { Pool } from "pg";
@@ -102,15 +92,8 @@ const pool = new Pool({ connectionString: serverEnv.DATABASE_URL });
102
92
  export const db = drizzle(pool, { schema });
103
93
  ```
104
94
 
105
- Read the connection string via `serverEnv` (from `@pracht/core/env/server`),
106
- never `process.env` — it keeps the secret out of the client bundle and
107
- resolves per adapter (see docs/ENV.md). The module-level singleton above is
108
- fine on the Node adapter, where `serverEnv` works at module top level; on
109
- Cloudflare/Vercel Edge, read `serverEnv` inside a factory function instead —
110
- Workers env bindings only exist per request.
111
-
112
- For Cloudflare D1, first register the Cloudflare context type once via the
113
- `Register` augmentation (the pattern the docs recommend — see
95
+ For Cloudflare D1, register the Cloudflare context type once via the `Register`
96
+ augmentation (the pattern in
114
97
  `examples/docs/src/routes/docs/recipes-fullstack-cloudflare.md`):
115
98
 
116
99
  ```ts
@@ -125,37 +108,26 @@ declare module "@pracht/core" {
125
108
  }
126
109
  ```
127
110
 
128
- Then the factory needs no per-file generics:
111
+ The factory then needs no per-file generics:
129
112
 
130
113
  ```ts
114
+ import type { LoaderArgs } from "@pracht/core";
131
115
  import { drizzle } from "drizzle-orm/d1";
132
116
  import * as schema from "./schema";
133
- import type { LoaderArgs } from "@pracht/core";
134
117
 
135
118
  export function getDb({ context }: Pick<LoaderArgs, "context">) {
136
119
  return drizzle(context.env.DB, { schema });
137
120
  }
138
121
  ```
139
122
 
140
- (Without the `Register` augmentation, the inline generic must describe the
141
- full Cloudflare context shape —
123
+ Without that augmentation, the inline generic must describe the full context —
142
124
  `LoaderArgs<{ env: { DB: D1Database }; executionContext: ExecutionContext }>` —
143
- the context is `{ env, executionContext }`, not the bindings object itself.)
144
-
145
- For PlanetScale / Neon / Turso, follow the matching driver pattern. The
146
- pattern is:
147
-
148
- - **Node + persistent process**: module-level singleton.
149
- - **Edge + per-request context (Cloudflare/Vercel Edge)**: factory called
150
- with `context` inside the loader.
125
+ because the context is `{ env, executionContext }`, not the bindings object.
151
126
 
152
- ## Step 5: `drizzle.config.ts`
127
+ ## Step 4: drizzle.config.ts and scripts
153
128
 
154
- If `drizzle.config.ts` already exists, diff and merge — never overwrite.
155
- (`process.env` is fine here: this file runs under the drizzle-kit CLI on
156
- Node, never inside the worker.)
157
-
158
- ### Non-D1 providers (Postgres, MySQL, Turso, PlanetScale, Neon, local SQLite)
129
+ `process.env` *is* fine in `drizzle.config.ts`: it runs under the drizzle-kit
130
+ CLI on Node, never inside the worker.
159
131
 
160
132
  ```ts
161
133
  import { defineConfig } from "drizzle-kit";
@@ -164,74 +136,44 @@ export default defineConfig({
164
136
  schema: "./src/db/schema.ts",
165
137
  out: "./drizzle/migrations",
166
138
  dialect: "postgresql", // or "sqlite" / "mysql"
167
- dbCredentials: {
168
- url: process.env.DATABASE_URL!,
169
- },
170
- });
171
- ```
172
-
173
- ### Cloudflare D1
174
-
175
- D1 has no TCP endpoint, so drizzle-kit can only *generate* migrations. It
176
- cannot apply them — applying goes through `wrangler` (Step 6):
177
-
178
- ```ts
179
- import { defineConfig } from "drizzle-kit";
180
-
181
- export default defineConfig({
182
- schema: "./src/db/schema.ts",
183
- out: "./drizzle/migrations",
184
- dialect: "sqlite",
139
+ dbCredentials: { url: process.env.DATABASE_URL! },
185
140
  });
186
141
  ```
187
142
 
188
- If you want `drizzle-kit studio` against D1, add a `driver: "d1-http"` block
189
- with Cloudflare account/database/API-token credentials (see Drizzle's D1
190
- docs). Otherwise omit `dbCredentials` entirely `drizzle-kit generate`
191
- doesn't need them.
143
+ **D1 is the exception.** It has no TCP endpoint, so drizzle-kit can only
144
+ *generate* migrations applying goes through `wrangler`. Omit `dbCredentials`
145
+ entirely (add a `driver: "d1-http"` block with Cloudflare account/database/API
146
+ token only if you want `drizzle-kit studio`), and omit `db:push`: the
147
+ migrations-apply flow is the only supported path. Split local from remote so
148
+ the miniflare D1 can be iterated without touching production.
192
149
 
193
- ## Step 6: Scripts
150
+ | Script | Non-D1 | Cloudflare D1 |
151
+ | ------ | ------ | ------------- |
152
+ | `db:generate` | `drizzle-kit generate` | `drizzle-kit generate` |
153
+ | `db:migrate` | `drizzle-kit migrate` | `wrangler d1 migrations apply <db-name> --local` / `--remote` as `db:migrate:local` / `db:migrate:remote` |
154
+ | `db:push` | `drizzle-kit push` (local dev only — prefer migrations beyond that) | *omit* |
155
+ | `db:studio` | `drizzle-kit studio` | `drizzle-kit studio` |
194
156
 
195
- Merge these into the existing `package.json` `scripts` block — never
196
- overwrite scripts that already exist; diff and ask if one collides.
157
+ `<db-name>` is the `database_name` from `wrangler.toml`/`.jsonc`.
197
158
 
198
- ### Non-D1 providers
159
+ ## Step 5: Bindings and env
199
160
 
200
- ```json
201
- {
202
- "scripts": {
203
- "db:generate": "drizzle-kit generate",
204
- "db:migrate": "drizzle-kit migrate",
205
- "db:push": "drizzle-kit push",
206
- "db:studio": "drizzle-kit studio"
207
- }
208
- }
209
- ```
161
+ - **Cloudflare D1** — merge the binding into `wrangler.toml`/`.jsonc`.
162
+ `migrations_dir` must match `out` in `drizzle.config.ts` or wrangler will not
163
+ find the SQL drizzle-kit emits:
210
164
 
211
- ### Cloudflare D1
212
-
213
- `drizzle-kit migrate` does not work against D1 (no TCP). Apply migrations
214
- via `wrangler d1 migrations apply <db-name>`, split into local vs remote so
215
- you can iterate safely against the miniflare D1 before touching production:
216
-
217
- ```json
218
- {
219
- "scripts": {
220
- "db:generate": "drizzle-kit generate",
221
- "db:migrate:local": "wrangler d1 migrations apply <db-name> --local",
222
- "db:migrate:remote": "wrangler d1 migrations apply <db-name> --remote",
223
- "db:studio": "drizzle-kit studio"
224
- }
225
- }
226
- ```
227
-
228
- Replace `<db-name>` with the `database_name` from `wrangler.toml`/`.jsonc`.
229
- Omit `db:push` for D1 — the migrations-apply flow is the only supported
230
- path.
165
+ ```toml
166
+ [[d1_databases]]
167
+ binding = "DB"
168
+ database_name = "my-app"
169
+ database_id = "<id>"
170
+ migrations_dir = "drizzle/migrations"
171
+ ```
231
172
 
232
- ## Step 7: Use in a loader
173
+ - **Node / Vercel** — document `DATABASE_URL` in `.env.example`, and add
174
+ `.env*` to `.gitignore` if it is missing.
233
175
 
234
- Demonstrate the wired-up usage:
176
+ ## Step 6: Use it in a loader
235
177
 
236
178
  ```ts
237
179
  import type { LoaderArgs } from "@pracht/core";
@@ -240,77 +182,26 @@ import { users } from "../db/schema";
240
182
 
241
183
  export async function loader(_args: LoaderArgs) {
242
184
  const rows = await db.select().from(users).limit(20);
243
- return { users: rows.map(u => ({ id: u.id, email: u.email })) };
185
+ return { users: rows.map((u) => ({ id: u.id, email: u.email })) };
244
186
  }
245
187
  ```
246
188
 
247
- Note: explicit projection — never spread DB rows into loader return values
248
- (see `audit-secrets`).
189
+ Project explicitly — never spread DB rows into loader return values, since
190
+ everything returned crosses the wire (see `/audit-secrets`).
249
191
 
250
- ## Step 8: Bindings & env vars
251
-
252
- - For Cloudflare adapters with D1: add the binding to `wrangler.toml` (or
253
- `wrangler.jsonc`). If the file already exists, diff and merge the binding
254
- in — never overwrite the existing config. `migrations_dir` must match the
255
- `out` in `drizzle.config.ts` so wrangler finds the SQL drizzle-kit emits:
256
- ```toml
257
- [[d1_databases]]
258
- binding = "DB"
259
- database_name = "my-app"
260
- database_id = "<id>"
261
- migrations_dir = "drizzle/migrations"
262
- ```
263
- - For Node/Vercel: document `DATABASE_URL` in `.env.example`. Add `.env*` to
264
- `.gitignore` if missing.
265
-
266
- ## Step 9: Verify
267
-
268
- Non-D1:
269
-
270
- ```bash
271
- pnpm db:generate
272
- pnpm db:push # or db:migrate after creating one
273
- ```
274
-
275
- D1:
192
+ ## Step 7: Verify
276
193
 
277
194
  ```bash
278
195
  pnpm db:generate
279
- pnpm db:migrate:local # apply to miniflare D1
280
- # when happy:
281
- pnpm db:migrate:remote # apply to production D1
282
- ```
283
-
284
- Then:
285
-
286
- ```bash
196
+ pnpm db:push # non-D1; or db:migrate once a migration exists
197
+ pnpm db:migrate:local # D1 — then db:migrate:remote when happy
287
198
  pracht verify --json
288
199
  pnpm test
289
200
  ```
290
201
 
291
- Note: on a fresh project `pnpm test` is a no-op (no tests exist yet) it
292
- proves nothing about the DB wiring. Suggest a loader smoke test that calls
293
- the Step 7 loader with a real (local) DB and asserts on the returned shape,
294
- or run `scaffold-tests` to set that up.
295
-
296
- ## Rules
297
-
298
- 1. Always confirm the adapter ↔ driver compatibility before installing.
299
- 2. Never spread DB rows into loader return values — project explicitly.
300
- 3. For edge runtimes, do not module-cache a connection — use a factory keyed
301
- by `context.env`.
302
- 4. In app code, read connection strings via `serverEnv` from
303
- `@pracht/core/env/server`, not `process.env`; on Cloudflare, read it
304
- inside functions only. (Exception: `drizzle.config.ts` runs under the
305
- drizzle-kit CLI on Node, where `process.env` is fine.)
306
- 5. Add `.env*` to `.gitignore` if a connection string is involved.
307
- 6. Recommend a migration workflow (`db:migrate`) over `db:push` for
308
- anything beyond local dev.
309
- 7. For D1, apply migrations with `wrangler d1 migrations apply`, not
310
- `drizzle-kit migrate` — D1 exposes no TCP endpoint and drizzle-kit will
311
- silently fail to connect. Split into `db:migrate:local` and
312
- `db:migrate:remote` so the local miniflare DB can be iterated without
313
- touching production. Ensure `migrations_dir` in `wrangler.toml` matches
314
- `out` in `drizzle.config.ts`.
202
+ On a fresh project `pnpm test` is a no-op and proves nothing about the DB
203
+ wiring. Suggest a loader smoke test that calls the Step 6 loader against a real
204
+ local DB and asserts the returned shape, or run `/scaffold-tests` to set that
205
+ up.
315
206
 
316
207
  $ARGUMENTS