create-pracht 0.3.0 → 0.4.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 +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 +519 -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 +164 -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 +203 -0
- package/skills/upgrade-pracht/SKILL.md +151 -0
- package/src/index.js +146 -8
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
|
|
|
@@ -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
|