create-pracht 0.3.0 → 0.4.1
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 +11 -1
- 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 +536 -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 +174 -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 +207 -0
- package/skills/upgrade-pracht/SKILL.md +152 -0
- package/src/index.js +148 -10
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrade-pracht
|
|
3
|
+
version: 1.0.1
|
|
4
|
+
description: |
|
|
5
|
+
Upgrade the @pracht/* packages in an app safely: inventory installed
|
|
6
|
+
versions, read the changelogs between installed and target, map breaking
|
|
7
|
+
changes to actual usage in the codebase, apply the upgrade, and walk the
|
|
8
|
+
verification ladder (doctor, typegen, verify, build, tests).
|
|
9
|
+
Use when asked to "upgrade pracht", "update @pracht packages", "bump the
|
|
10
|
+
framework", "what changed in the new pracht version", or "is this pracht
|
|
11
|
+
upgrade safe".
|
|
12
|
+
allowed-tools:
|
|
13
|
+
- Bash
|
|
14
|
+
- Read
|
|
15
|
+
- Edit
|
|
16
|
+
- Grep
|
|
17
|
+
- Glob
|
|
18
|
+
- AskUserQuestion
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Pracht Upgrade
|
|
22
|
+
|
|
23
|
+
Upgrade `@pracht/*` dependencies with the changelog read *before* the install,
|
|
24
|
+
not after the build breaks.
|
|
25
|
+
|
|
26
|
+
## Step 1: Inventory
|
|
27
|
+
|
|
28
|
+
List every installed pracht package and its resolved version:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm list --depth 1 --json | grep -A2 '@pracht/' # or read package.json + lockfile
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The family: `@pracht/core`, `@pracht/cli`, `@pracht/vite-plugin`,
|
|
35
|
+
`@pracht/adapter-node`, `@pracht/adapter-cloudflare`, `@pracht/adapter-vercel`,
|
|
36
|
+
`@pracht/preact-ssr-precompile`, `@pracht/image`. Get the latest published
|
|
37
|
+
versions with `npm view <pkg> version`.
|
|
38
|
+
|
|
39
|
+
## Step 2: Understand the versioning model
|
|
40
|
+
|
|
41
|
+
Pracht packages are **independently versioned** (the repo's changesets config
|
|
42
|
+
has empty `fixed`/`linked` groups) — `@pracht/core` can be at 0.9.x while
|
|
43
|
+
`@pracht/cli` is at 1.6.x. There is no "one framework version". Two
|
|
44
|
+
consequences:
|
|
45
|
+
|
|
46
|
+
1. **Internal dependencies are pinned exact.** Published packages depend on
|
|
47
|
+
their siblings at exact versions (e.g. `@pracht/vite-plugin@0.5.0` depends
|
|
48
|
+
on `@pracht/core@0.9.0`, not a range). Upgrade the whole family in one
|
|
49
|
+
move; upgrading only one package can drag in a second copy of
|
|
50
|
+
`@pracht/core` and split the runtime.
|
|
51
|
+
2. **Most packages are 0.x**, so under semver a *minor* bump may be breaking —
|
|
52
|
+
treat `### Minor Changes` entries on 0.x packages with the same care as
|
|
53
|
+
majors.
|
|
54
|
+
|
|
55
|
+
After any upgrade, confirm a single core resolution:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm why @pracht/core # exactly one version may appear
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Step 3: Read the changelogs between installed and target
|
|
62
|
+
|
|
63
|
+
Only `@pracht/cli` ships `CHANGELOG.md` in its npm tarball
|
|
64
|
+
(`node_modules/@pracht/cli/CHANGELOG.md`); the other packages publish `dist/`
|
|
65
|
+
only. Fetch their changelogs from the repo instead:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
https://raw.githubusercontent.com/JoviDeCroock/pracht/main/packages/<dir>/CHANGELOG.md
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Package | Repo directory |
|
|
72
|
+
| ------- | -------------- |
|
|
73
|
+
| `@pracht/core` | `packages/framework` |
|
|
74
|
+
| `@pracht/cli` | `packages/cli` |
|
|
75
|
+
| `@pracht/vite-plugin` | `packages/vite-plugin` |
|
|
76
|
+
| `@pracht/adapter-node` / `-cloudflare` / `-vercel` | `packages/adapter-*` |
|
|
77
|
+
| `@pracht/preact-ssr-precompile` | `packages/preact-ssr-precompile` |
|
|
78
|
+
| `@pracht/image` | `packages/image` |
|
|
79
|
+
|
|
80
|
+
Changelogs are changesets-generated: `## X.Y.Z` sections containing
|
|
81
|
+
`### Major Changes` / `### Minor Changes` / `### Patch Changes`. Read every
|
|
82
|
+
section between the installed and target version of every installed package.
|
|
83
|
+
|
|
84
|
+
## Step 4: Map changes onto this app
|
|
85
|
+
|
|
86
|
+
Classify each entry as **breaking** / **feature** / **fix**. For each breaking
|
|
87
|
+
(or 0.x minor) entry, grep the app for the APIs, exports, config options, and
|
|
88
|
+
generated-file shapes it names, and record: affected files, the migration the
|
|
89
|
+
changelog prescribes, and whether it can be applied mechanically. Also
|
|
90
|
+
re-check peer ranges after a major target bump — `@pracht/vite-plugin`
|
|
91
|
+
requires `vite` (^8), `@pracht/adapter-cloudflare` requires `vite` and
|
|
92
|
+
`wrangler` (^4.81), `@pracht/core` requires `preact` (^10) and
|
|
93
|
+
`preact-render-to-string` (^6).
|
|
94
|
+
|
|
95
|
+
Present the plan as a table:
|
|
96
|
+
|
|
97
|
+
| Package | Installed → Target | Breaking entries | App impact | Migration |
|
|
98
|
+
| ------- | ------------------ | ---------------- | ---------- | --------- |
|
|
99
|
+
|
|
100
|
+
## Step 5: Confirm, then apply
|
|
101
|
+
|
|
102
|
+
Use `AskUserQuestion` before touching anything when breaking migrations are
|
|
103
|
+
required: confirm the target versions and which migrations to apply. Then:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pnpm up '@pracht/core@<v>' '@pracht/cli@<v>' '@pracht/vite-plugin@<v>' <adapters...>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Upgrade every installed `@pracht/*` package in the same command. Apply the
|
|
110
|
+
agreed code migrations with minimal diffs, one changelog entry at a time.
|
|
111
|
+
|
|
112
|
+
## Step 6: Verification ladder
|
|
113
|
+
|
|
114
|
+
Run in order; stop and fix at the first failure:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pracht doctor --json # wiring still valid
|
|
118
|
+
pracht typegen --check # generated route types up to date?
|
|
119
|
+
pracht typegen # regenerate if --check failed or routes changed
|
|
120
|
+
pracht verify --json # framework-aware checks
|
|
121
|
+
pracht build # full production build (budgets included)
|
|
122
|
+
pnpm test # the app's own suite
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`pracht doctor`, `verify`, and `typegen --check` exit non-zero on failure, so
|
|
126
|
+
they gate CI cleanly.
|
|
127
|
+
|
|
128
|
+
## Step 7: Rollback note
|
|
129
|
+
|
|
130
|
+
If the ladder cannot be made green, roll back rather than shipping a
|
|
131
|
+
half-upgrade:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git restore package.json pnpm-lock.yaml && pnpm install
|
|
135
|
+
git checkout -- <migrated files> # or revert the upgrade commit
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Because internal deps are exact-pinned, a *partial* rollback (one package
|
|
139
|
+
back, the rest forward) recreates the duplicate-core problem from Step 2 —
|
|
140
|
+
roll the whole family back together.
|
|
141
|
+
|
|
142
|
+
## Rules
|
|
143
|
+
|
|
144
|
+
1. Never mix `@pracht/*` versions from different release waves — upgrade and
|
|
145
|
+
roll back the family as a unit, and verify with `pnpm why @pracht/core`.
|
|
146
|
+
2. Read changelogs before installing, not after something breaks.
|
|
147
|
+
3. Never apply a breaking-change migration without explicit user confirmation
|
|
148
|
+
via `AskUserQuestion`.
|
|
149
|
+
4. Treat 0.x minor bumps as potentially breaking.
|
|
150
|
+
5. Do not hand-edit lockfiles; let the package manager resolve.
|
|
151
|
+
|
|
152
|
+
$ARGUMENTS
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { copyFile, mkdir, readdir, stat, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { copyFile, mkdir, readFile, readdir, stat, symlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, resolve } from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
7
8
|
export class ValidationError extends Error {
|
|
8
9
|
constructor(message) {
|
|
@@ -20,7 +21,7 @@ const FALLBACK_VERSION_RANGES = {
|
|
|
20
21
|
"@pracht/vite-plugin": "^0.3.2",
|
|
21
22
|
"@tailwindcss/vite": "^4.1.0",
|
|
22
23
|
tailwindcss: "^4.1.0",
|
|
23
|
-
vercel: "
|
|
24
|
+
vercel: "^56.5.0",
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
async function fetchLatestVersion(packageName) {
|
|
@@ -58,6 +59,12 @@ const ADAPTERS = {
|
|
|
58
59
|
|
|
59
60
|
const DEFAULT_DIRECTORY = "pracht-app";
|
|
60
61
|
|
|
62
|
+
const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
63
|
+
|
|
64
|
+
// The published package bundles a copy of the repo skills (see
|
|
65
|
+
// scripts/sync-skills.js); inside the monorepo we fall back to the source.
|
|
66
|
+
const SKILL_DIRS = [resolve(PACKAGE_ROOT, "skills"), resolve(PACKAGE_ROOT, "../../skills")];
|
|
67
|
+
|
|
61
68
|
export async function run(argv = process.argv.slice(2)) {
|
|
62
69
|
const options = parseArgs(argv);
|
|
63
70
|
const packageManager = getPackageManager();
|
|
@@ -71,17 +78,20 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
71
78
|
const adapterId = options.adapter ?? (options.yes ? "node" : null);
|
|
72
79
|
const router = options.router ?? (options.yes ? "manifest" : null);
|
|
73
80
|
const tailwind = options.tailwind ?? (options.yes ? false : null);
|
|
81
|
+
const agentTools = options.agentTools ?? (options.yes ? true : null);
|
|
74
82
|
|
|
75
83
|
let resolvedDir = dir;
|
|
76
84
|
let resolvedAdapter = adapterId;
|
|
77
85
|
let resolvedRouter = router;
|
|
78
86
|
let resolvedTailwind = tailwind;
|
|
87
|
+
let resolvedAgentTools = agentTools;
|
|
79
88
|
|
|
80
89
|
if (
|
|
81
90
|
resolvedDir == null ||
|
|
82
91
|
resolvedAdapter == null ||
|
|
83
92
|
resolvedRouter == null ||
|
|
84
|
-
resolvedTailwind == null
|
|
93
|
+
resolvedTailwind == null ||
|
|
94
|
+
resolvedAgentTools == null
|
|
85
95
|
) {
|
|
86
96
|
const readline = createInterface({
|
|
87
97
|
input: process.stdin,
|
|
@@ -93,6 +103,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
93
103
|
resolvedAdapter = resolvedAdapter ?? (await promptForAdapter(readline));
|
|
94
104
|
resolvedRouter = resolvedRouter ?? (await promptForRouter(readline));
|
|
95
105
|
resolvedTailwind = resolvedTailwind ?? (await promptForTailwind(readline));
|
|
106
|
+
resolvedAgentTools = resolvedAgentTools ?? (await promptForAgentTools(readline));
|
|
96
107
|
} finally {
|
|
97
108
|
readline.close();
|
|
98
109
|
}
|
|
@@ -105,6 +116,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
105
116
|
if (options.dryRun) {
|
|
106
117
|
const files = await buildProjectFiles({
|
|
107
118
|
adapter: ADAPTERS[resolvedAdapter],
|
|
119
|
+
agentTools: resolvedAgentTools,
|
|
108
120
|
packageManager,
|
|
109
121
|
projectName: toPackageName(basename(targetDir)),
|
|
110
122
|
resolveRemoteVersions: false,
|
|
@@ -118,6 +130,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
118
130
|
console.log(
|
|
119
131
|
JSON.stringify({
|
|
120
132
|
adapter: resolvedAdapter,
|
|
133
|
+
agentTools: resolvedAgentTools,
|
|
121
134
|
directory: resolvedDir,
|
|
122
135
|
dryRun: true,
|
|
123
136
|
files: fileList,
|
|
@@ -138,6 +151,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
138
151
|
|
|
139
152
|
await scaffoldProject({
|
|
140
153
|
adapter: ADAPTERS[resolvedAdapter],
|
|
154
|
+
agentTools: resolvedAgentTools,
|
|
141
155
|
packageManager,
|
|
142
156
|
router: resolvedRouter,
|
|
143
157
|
tailwind: resolvedTailwind,
|
|
@@ -171,6 +185,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
171
185
|
if (options.json) {
|
|
172
186
|
const files = await buildProjectFiles({
|
|
173
187
|
adapter: ADAPTERS[resolvedAdapter],
|
|
188
|
+
agentTools: resolvedAgentTools,
|
|
174
189
|
packageManager,
|
|
175
190
|
projectName: toPackageName(basename(targetDir)),
|
|
176
191
|
resolveRemoteVersions: false,
|
|
@@ -181,6 +196,7 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
181
196
|
console.log(
|
|
182
197
|
JSON.stringify({
|
|
183
198
|
adapter: resolvedAdapter,
|
|
199
|
+
agentTools: resolvedAgentTools,
|
|
184
200
|
directory: resolvedDir,
|
|
185
201
|
files: Object.keys(files).sort(),
|
|
186
202
|
gitInitialized,
|
|
@@ -202,7 +218,9 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
202
218
|
|
|
203
219
|
export async function scaffoldProject({
|
|
204
220
|
adapter,
|
|
221
|
+
agentTools = true,
|
|
205
222
|
packageManager,
|
|
223
|
+
resolveRemoteVersions = true,
|
|
206
224
|
router = "manifest",
|
|
207
225
|
tailwind = false,
|
|
208
226
|
targetDir,
|
|
@@ -210,8 +228,10 @@ export async function scaffoldProject({
|
|
|
210
228
|
const packageName = toPackageName(basename(targetDir));
|
|
211
229
|
const files = await buildProjectFiles({
|
|
212
230
|
adapter,
|
|
231
|
+
agentTools,
|
|
213
232
|
packageManager,
|
|
214
233
|
projectName: packageName,
|
|
234
|
+
resolveRemoteVersions,
|
|
215
235
|
router,
|
|
216
236
|
tailwind,
|
|
217
237
|
});
|
|
@@ -245,6 +265,7 @@ export function getPackageManager(userAgent = process.env.npm_config_user_agent
|
|
|
245
265
|
export function parseArgs(argv) {
|
|
246
266
|
const options = {
|
|
247
267
|
adapter: undefined,
|
|
268
|
+
agentTools: undefined,
|
|
248
269
|
dir: undefined,
|
|
249
270
|
dryRun: false,
|
|
250
271
|
git: true,
|
|
@@ -276,6 +297,16 @@ export function parseArgs(argv) {
|
|
|
276
297
|
continue;
|
|
277
298
|
}
|
|
278
299
|
|
|
300
|
+
if (arg === "--agent-tools") {
|
|
301
|
+
options.agentTools = true;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (arg === "--no-agent-tools") {
|
|
306
|
+
options.agentTools = false;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
279
310
|
if (arg.startsWith("--template=")) {
|
|
280
311
|
const value = normalizeTemplate(arg.slice("--template=".length));
|
|
281
312
|
if (!value) {
|
|
@@ -403,6 +434,19 @@ async function promptForTailwind(readline) {
|
|
|
403
434
|
}
|
|
404
435
|
}
|
|
405
436
|
|
|
437
|
+
async function promptForAgentTools(readline) {
|
|
438
|
+
while (true) {
|
|
439
|
+
const answer = await readline.question("Set up Claude Code skills + MCP? (Y/n): ");
|
|
440
|
+
const normalized = normalizeYesNo(answer.trim() || "yes");
|
|
441
|
+
|
|
442
|
+
if (normalized != null) {
|
|
443
|
+
return normalized;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
console.log("Answer y/yes or n/no.");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
406
450
|
function normalizeYesNo(value) {
|
|
407
451
|
const normalized = value.toLowerCase();
|
|
408
452
|
|
|
@@ -511,6 +555,7 @@ async function resolveVersions(packageNames, { remote = true } = {}) {
|
|
|
511
555
|
|
|
512
556
|
async function buildProjectFiles({
|
|
513
557
|
adapter,
|
|
558
|
+
agentTools = true,
|
|
514
559
|
packageManager,
|
|
515
560
|
projectName,
|
|
516
561
|
resolveRemoteVersions = true,
|
|
@@ -533,13 +578,21 @@ async function buildProjectFiles({
|
|
|
533
578
|
const versions = await resolveVersions(packagesToResolve, { remote: resolveRemoteVersions });
|
|
534
579
|
|
|
535
580
|
const files = {
|
|
536
|
-
".gitignore":
|
|
537
|
-
|
|
581
|
+
".gitignore":
|
|
582
|
+
"dist\nnode_modules\n.wrangler\n.vercel\n.env*\n!.env.example\n.dev.vars\n# Keep .pracht/app-graph.json committed — it is the `pracht plan` snapshot.\n",
|
|
583
|
+
"README.md": createReadme({
|
|
584
|
+
adapter,
|
|
585
|
+
agentTools,
|
|
586
|
+
packageManager,
|
|
587
|
+
projectName,
|
|
588
|
+
router,
|
|
589
|
+
tailwind,
|
|
590
|
+
}),
|
|
538
591
|
"package.json": createPackageJson({ adapter, projectName, tailwind, versions }),
|
|
539
592
|
"src/api/health.ts": createHealthRoute(adapter),
|
|
540
593
|
"vite.config.ts": createViteConfig(adapter, router, tailwind),
|
|
541
594
|
"tsconfig.json": createBaseTSConfig(adapter),
|
|
542
|
-
"AGENTS.md": createAgentInstructions({ adapter, packageManager, router, tailwind }),
|
|
595
|
+
"AGENTS.md": createAgentInstructions({ adapter, agentTools, packageManager, router, tailwind }),
|
|
543
596
|
};
|
|
544
597
|
|
|
545
598
|
if (router === "pages") {
|
|
@@ -565,6 +618,45 @@ async function buildProjectFiles({
|
|
|
565
618
|
files[".dockerignore"] = createDockerignore();
|
|
566
619
|
}
|
|
567
620
|
|
|
621
|
+
if (agentTools) {
|
|
622
|
+
files[".mcp.json"] = createMcpConfig();
|
|
623
|
+
Object.assign(files, await readSkillFiles());
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return files;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function createMcpConfig() {
|
|
630
|
+
return `${JSON.stringify(
|
|
631
|
+
{
|
|
632
|
+
mcpServers: {
|
|
633
|
+
pracht: {
|
|
634
|
+
command: "npx",
|
|
635
|
+
args: ["pracht", "mcp"],
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
null,
|
|
640
|
+
2,
|
|
641
|
+
)}\n`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async function readSkillFiles() {
|
|
645
|
+
const skillsDir = SKILL_DIRS.find((dir) => existsSync(dir));
|
|
646
|
+
|
|
647
|
+
if (!skillsDir) {
|
|
648
|
+
return {};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const files = {};
|
|
652
|
+
for (const name of await readdir(skillsDir)) {
|
|
653
|
+
const skillFile = resolve(skillsDir, name, "SKILL.md");
|
|
654
|
+
if (!existsSync(skillFile)) {
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
files[`.claude/skills/${name}/SKILL.md`] = await readFile(skillFile, "utf-8");
|
|
658
|
+
}
|
|
659
|
+
|
|
568
660
|
return files;
|
|
569
661
|
}
|
|
570
662
|
|
|
@@ -632,8 +724,8 @@ function createViteConfig(adapter, router, tailwind) {
|
|
|
632
724
|
|
|
633
725
|
const prachtOptions =
|
|
634
726
|
router === "pages"
|
|
635
|
-
? `{ pagesDir: "/src/pages", adapter: ${info.fn}() }`
|
|
636
|
-
: `{ adapter: ${info.fn}() }`;
|
|
727
|
+
? `{ pagesDir: "/src/pages", adapter: ${info.fn}(), llmsTxt: {} }`
|
|
728
|
+
: `{ adapter: ${info.fn}(), llmsTxt: {} }`;
|
|
637
729
|
|
|
638
730
|
const plugins = tailwind
|
|
639
731
|
? `[pracht(${prachtOptions}), tailwindcss()]`
|
|
@@ -665,6 +757,13 @@ function createRoutesFile() {
|
|
|
665
757
|
" routes: [",
|
|
666
758
|
' route("/", "./routes/home.tsx", { id: "home", render: "ssg", shell: "public" }),',
|
|
667
759
|
" ],",
|
|
760
|
+
" // Custom 404 page — any module in ./routes, rendered when nothing matches:",
|
|
761
|
+
' // notFound: "./routes/not-found.tsx",',
|
|
762
|
+
" // Declarative invariants enforced by `pracht verify` — uncomment to use",
|
|
763
|
+
" // (add the helpers to the @pracht/core import):",
|
|
764
|
+
" // constraints: [",
|
|
765
|
+
' // requireHead("**"),',
|
|
766
|
+
" // ],",
|
|
668
767
|
"});",
|
|
669
768
|
"",
|
|
670
769
|
].join("\n");
|
|
@@ -920,7 +1019,7 @@ function createDockerignore() {
|
|
|
920
1019
|
].join("\n");
|
|
921
1020
|
}
|
|
922
1021
|
|
|
923
|
-
function createAgentInstructions({ adapter, packageManager, router, tailwind }) {
|
|
1022
|
+
function createAgentInstructions({ adapter, agentTools, packageManager, router, tailwind }) {
|
|
924
1023
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
|
925
1024
|
|
|
926
1025
|
const lines = [
|
|
@@ -954,6 +1053,12 @@ function createAgentInstructions({ adapter, packageManager, router, tailwind })
|
|
|
954
1053
|
lines.push("- `pracht generate middleware --name auth` — add middleware");
|
|
955
1054
|
lines.push("- `pracht generate api --path /health --methods GET` — add an API route");
|
|
956
1055
|
lines.push("- `pracht doctor` — check project health");
|
|
1056
|
+
lines.push("- `pracht verify` — enforce route and constraint invariants");
|
|
1057
|
+
lines.push(
|
|
1058
|
+
"- `pracht plan --write` — refresh the committed `.pracht/app-graph.json` snapshot after route changes",
|
|
1059
|
+
);
|
|
1060
|
+
lines.push("- `pracht report` — PR-ready markdown summary (plan diff, verify, budgets)");
|
|
1061
|
+
lines.push("- `pracht llms --write` — write an `llms.txt` authoring guide for coding agents");
|
|
957
1062
|
|
|
958
1063
|
lines.push("");
|
|
959
1064
|
lines.push("## Project structure");
|
|
@@ -988,12 +1093,24 @@ function createAgentInstructions({ adapter, packageManager, router, tailwind })
|
|
|
988
1093
|
lines.push("- `src/env.d.ts` — TypeScript types for Cloudflare bindings");
|
|
989
1094
|
}
|
|
990
1095
|
|
|
1096
|
+
if (agentTools) {
|
|
1097
|
+
lines.push("");
|
|
1098
|
+
lines.push("## Agent tooling");
|
|
1099
|
+
lines.push("");
|
|
1100
|
+
lines.push(
|
|
1101
|
+
"- `.claude/skills/` — pracht Claude Code skills (audits, scaffolds, testing, debugging); invoke with `/<skill-name>`",
|
|
1102
|
+
);
|
|
1103
|
+
lines.push(
|
|
1104
|
+
"- `.mcp.json` — registers the `pracht mcp` server so MCP clients can inspect the app graph, run doctor/verify, and scaffold natively",
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
991
1108
|
lines.push("");
|
|
992
1109
|
|
|
993
1110
|
return lines.join("\n");
|
|
994
1111
|
}
|
|
995
1112
|
|
|
996
|
-
function createReadme({ adapter, packageManager, projectName, router, tailwind }) {
|
|
1113
|
+
function createReadme({ adapter, agentTools, packageManager, projectName, router, tailwind }) {
|
|
997
1114
|
const installCommand = packageManager === "npm" ? "npm install" : `${packageManager} install`;
|
|
998
1115
|
const devCommand = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
|
|
999
1116
|
const previewCommand = packageManager === "npm" ? "npm run preview" : `${packageManager} preview`;
|
|
@@ -1050,6 +1167,25 @@ function createReadme({ adapter, packageManager, projectName, router, tailwind }
|
|
|
1050
1167
|
lines.push("- `src/styles/global.css` is the Tailwind CSS entry, imported by the shell.");
|
|
1051
1168
|
}
|
|
1052
1169
|
|
|
1170
|
+
if (agentTools) {
|
|
1171
|
+
lines.push(
|
|
1172
|
+
"- `.claude/skills/` and `.mcp.json` wire up the pracht Claude Code skills and MCP server.",
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
lines.push("");
|
|
1177
|
+
lines.push("## Checks");
|
|
1178
|
+
lines.push("");
|
|
1179
|
+
lines.push(
|
|
1180
|
+
router === "pages"
|
|
1181
|
+
? "- `pracht verify` validates routes."
|
|
1182
|
+
: "- `pracht verify` validates routes and constraints.",
|
|
1183
|
+
);
|
|
1184
|
+
lines.push(
|
|
1185
|
+
"- `pracht plan --write` commits an app-graph snapshot to `.pracht/`; `pracht plan` diffs against it.",
|
|
1186
|
+
);
|
|
1187
|
+
lines.push("- `pracht report` prints a PR-ready summary of both.");
|
|
1188
|
+
|
|
1053
1189
|
if (adapter.id === "node") {
|
|
1054
1190
|
lines.push("");
|
|
1055
1191
|
lines.push("## Docker");
|
|
@@ -1172,6 +1308,8 @@ Options:
|
|
|
1172
1308
|
--router=manifest|pages Choose routing system (default: manifest)
|
|
1173
1309
|
--template=minimal|tailwind Choose starter template (minimal, or minimal + Tailwind CSS)
|
|
1174
1310
|
--tailwind / --no-tailwind Enable or disable Tailwind CSS wiring (default: prompt)
|
|
1311
|
+
--agent-tools / --no-agent-tools
|
|
1312
|
+
Seed Claude Code skills and a pracht MCP config (default: prompt, yes)
|
|
1175
1313
|
--no-git Skip git init and the initial commit
|
|
1176
1314
|
--skip-install Skip dependency installation
|
|
1177
1315
|
--yes, -y Accept defaults, skip all prompts
|