create-pracht 0.0.2 → 0.2.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 CHANGED
@@ -43,7 +43,10 @@ Cloudflare scaffolds also include:
43
43
 
44
44
  - `dev` -> `pracht dev`
45
45
  - `build` -> `pracht build`
46
- - `preview` -> `pracht preview`
46
+
47
+ Node starters also include:
48
+
49
+ - `start` -> `node dist/server/server.js`
47
50
 
48
51
  Cloudflare starters also include:
49
52
 
@@ -52,9 +55,3 @@ Cloudflare starters also include:
52
55
  Vercel starters also include:
53
56
 
54
57
  - `deploy` -> `pracht build && vercel deploy --prebuilt`
55
-
56
- Node starters can be run in production with:
57
-
58
- ```bash
59
- node dist/server/server.js
60
- ```
@@ -3,7 +3,10 @@
3
3
  import { run } from "../src/index.js";
4
4
 
5
5
  run().catch((error) => {
6
- console.error("Failed to create a pracht app.");
7
- console.error(error instanceof Error ? error.message : error);
8
- process.exit(1);
6
+ const code = error && error.code === 2 ? 2 : 1;
7
+ console.error(code === 2 ? error.message : "Failed to create a pracht app.");
8
+ if (code !== 2) {
9
+ console.error(error instanceof Error ? error.message : error);
10
+ }
11
+ process.exit(code);
9
12
  });
package/package.json CHANGED
@@ -1,6 +1,15 @@
1
1
  {
2
2
  "name": "create-pracht",
3
- "version": "0.0.2",
3
+ "version": "0.2.0",
4
+ "homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/start",
5
+ "bugs": {
6
+ "url": "https://github.com/JoviDeCroock/pracht/issues"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/JoviDeCroock/pracht",
11
+ "directory": "packages/start"
12
+ },
4
13
  "bin": {
5
14
  "create-pracht": "./bin/create-pracht.js"
6
15
  },
@@ -10,6 +19,9 @@
10
19
  "README.md"
11
20
  ],
12
21
  "type": "module",
22
+ "publishConfig": {
23
+ "provenance": true
24
+ },
13
25
  "scripts": {
14
26
  "build": "node -e \"\""
15
27
  }
package/src/index.js CHANGED
@@ -1,12 +1,28 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
3
+ import { mkdir, 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
6
 
7
+ export class ValidationError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.code = 2;
11
+ }
12
+ }
13
+
14
+ async function fetchLatestVersion(packageName) {
15
+ const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
16
+ if (!res.ok) {
17
+ throw new Error(`Failed to fetch version for ${packageName}: ${res.statusText}`);
18
+ }
19
+ const data = await res.json();
20
+ return data.version;
21
+ }
22
+
7
23
  const ADAPTERS = {
8
24
  node: {
9
- description: "Node.js server with pracht preview",
25
+ description: "Node.js server with a generated server entry",
10
26
  id: "node",
11
27
  label: "Node.js",
12
28
  packageName: "@pracht/adapter-node",
@@ -33,54 +49,119 @@ const DEFAULT_DIRECTORY = "pracht-app";
33
49
  export async function run(argv = process.argv.slice(2)) {
34
50
  const options = parseArgs(argv);
35
51
  const packageManager = getPackageManager();
52
+ const log = options.json ? () => {} : console.log.bind(console);
36
53
 
37
- console.log("create-pracht");
38
- console.log(`Using ${packageManager} for this scaffold.`);
39
- console.log("");
54
+ log("create-pracht");
55
+ log(`Using ${packageManager} for this scaffold.`);
56
+ log("");
40
57
 
41
- const readline = createInterface({
42
- input: process.stdin,
43
- output: process.stdout,
44
- });
58
+ const dir = options.dir ?? (options.yes ? DEFAULT_DIRECTORY : null);
59
+ const adapterId = options.adapter ?? (options.yes ? "node" : null);
60
+ const router = options.router ?? (options.yes ? "manifest" : null);
45
61
 
46
- try {
47
- const dir = options.dir ?? (await promptForDirectory(readline));
48
- const adapterId = options.adapter ?? (await promptForAdapter(readline));
49
- const targetDir = resolve(process.cwd(), dir);
62
+ let resolvedDir = dir;
63
+ let resolvedAdapter = adapterId;
64
+ let resolvedRouter = router;
50
65
 
51
- await ensureTargetDirectory(targetDir);
66
+ if (resolvedDir == null || resolvedAdapter == null || resolvedRouter == null) {
67
+ const readline = createInterface({
68
+ input: process.stdin,
69
+ output: process.stdout,
70
+ });
52
71
 
53
- await scaffoldProject({
54
- adapter: ADAPTERS[adapterId],
72
+ try {
73
+ resolvedDir = resolvedDir ?? (await promptForDirectory(readline));
74
+ resolvedAdapter = resolvedAdapter ?? (await promptForAdapter(readline));
75
+ resolvedRouter = resolvedRouter ?? (await promptForRouter(readline));
76
+ } finally {
77
+ readline.close();
78
+ }
79
+ }
80
+
81
+ const targetDir = resolve(process.cwd(), resolvedDir);
82
+
83
+ await ensureTargetDirectory(targetDir);
84
+
85
+ if (options.dryRun) {
86
+ const files = await buildProjectFiles({
87
+ adapter: ADAPTERS[resolvedAdapter],
55
88
  packageManager,
56
- targetDir,
89
+ projectName: toPackageName(basename(targetDir)),
90
+ router: resolvedRouter,
57
91
  });
58
92
 
59
- let installSucceeded = false;
60
- if (!options.skipInstall) {
61
- console.log("");
62
- console.log(`Installing dependencies with ${packageManager}...`);
63
- installSucceeded = await installDependencies(targetDir, packageManager);
93
+ const fileList = Object.keys(files).sort();
94
+
95
+ if (options.json) {
96
+ console.log(
97
+ JSON.stringify({
98
+ adapter: resolvedAdapter,
99
+ directory: resolvedDir,
100
+ dryRun: true,
101
+ files: fileList,
102
+ router: resolvedRouter,
103
+ }),
104
+ );
105
+ } else {
106
+ log("Dry run — the following files would be created:");
107
+ log("");
108
+ for (const file of fileList) {
109
+ log(` ${file}`);
110
+ }
64
111
  }
65
112
 
113
+ return;
114
+ }
115
+
116
+ await scaffoldProject({
117
+ adapter: ADAPTERS[resolvedAdapter],
118
+ packageManager,
119
+ router: resolvedRouter,
120
+ targetDir,
121
+ });
122
+
123
+ let installSucceeded = false;
124
+ if (!options.skipInstall) {
125
+ log("");
126
+ log(`Installing dependencies with ${packageManager}...`);
127
+ installSucceeded = await installDependencies(targetDir, packageManager);
128
+ }
129
+
130
+ if (options.json) {
131
+ const files = await buildProjectFiles({
132
+ adapter: ADAPTERS[resolvedAdapter],
133
+ packageManager,
134
+ projectName: toPackageName(basename(targetDir)),
135
+ router: resolvedRouter,
136
+ });
137
+
138
+ console.log(
139
+ JSON.stringify({
140
+ adapter: resolvedAdapter,
141
+ directory: resolvedDir,
142
+ files: Object.keys(files).sort(),
143
+ installed: options.skipInstall ? false : installSucceeded,
144
+ router: resolvedRouter,
145
+ }),
146
+ );
147
+ } else {
66
148
  printNextSteps({
67
- adapter: ADAPTERS[adapterId],
68
- dir,
149
+ adapter: ADAPTERS[resolvedAdapter],
150
+ dir: resolvedDir,
69
151
  installSucceeded,
70
152
  packageManager,
71
153
  skipInstall: options.skipInstall,
72
154
  });
73
- } finally {
74
- readline.close();
75
155
  }
76
156
  }
77
157
 
78
- export async function scaffoldProject({ adapter, packageManager, targetDir }) {
158
+ export async function scaffoldProject({ adapter, packageManager, router = "manifest", targetDir }) {
79
159
  const packageName = toPackageName(basename(targetDir));
80
- const files = buildProjectFiles({
160
+ const files = await buildProjectFiles({
81
161
  adapter,
82
162
  packageManager,
83
163
  projectName: packageName,
164
+ router,
84
165
  });
85
166
 
86
167
  await mkdir(targetDir, { recursive: true });
@@ -90,6 +171,8 @@ export async function scaffoldProject({ adapter, packageManager, targetDir }) {
90
171
  await mkdir(dirname(filePath), { recursive: true });
91
172
  await writeFile(filePath, content, "utf-8");
92
173
  }
174
+
175
+ await symlink("AGENTS.md", resolve(targetDir, "CLAUDE.md"));
93
176
  }
94
177
 
95
178
  export function getPackageManager(userAgent = process.env.npm_config_user_agent ?? "") {
@@ -103,7 +186,11 @@ export function parseArgs(argv) {
103
186
  const options = {
104
187
  adapter: undefined,
105
188
  dir: undefined,
189
+ dryRun: false,
190
+ json: false,
191
+ router: undefined,
106
192
  skipInstall: false,
193
+ yes: false,
107
194
  };
108
195
 
109
196
  for (const arg of argv) {
@@ -112,8 +199,40 @@ export function parseArgs(argv) {
112
199
  continue;
113
200
  }
114
201
 
202
+ if (arg === "--yes" || arg === "-y") {
203
+ options.yes = true;
204
+ continue;
205
+ }
206
+
207
+ if (arg === "--json") {
208
+ options.json = true;
209
+ continue;
210
+ }
211
+
212
+ if (arg === "--dry-run") {
213
+ options.dryRun = true;
214
+ continue;
215
+ }
216
+
115
217
  if (arg.startsWith("--adapter=")) {
116
- options.adapter = normalizeAdapter(arg.slice("--adapter=".length));
218
+ const value = normalizeAdapter(arg.slice("--adapter=".length));
219
+ if (!value) {
220
+ throw new ValidationError(
221
+ `Invalid adapter: ${arg.slice("--adapter=".length)}. Use node, cf, or vercel.`,
222
+ );
223
+ }
224
+ options.adapter = value;
225
+ continue;
226
+ }
227
+
228
+ if (arg.startsWith("--router=")) {
229
+ const value = normalizeRouter(arg.slice("--router=".length));
230
+ if (!value) {
231
+ throw new ValidationError(
232
+ `Invalid router: ${arg.slice("--router=".length)}. Use manifest or pages.`,
233
+ );
234
+ }
235
+ options.router = value;
117
236
  continue;
118
237
  }
119
238
 
@@ -166,11 +285,28 @@ async function promptForAdapter(readline) {
166
285
  }
167
286
  }
168
287
 
288
+ async function promptForRouter(readline) {
289
+ console.log("Router:");
290
+ console.log(" 1. Manifest (explicit routes.ts)");
291
+ console.log(" 2. Pages (file-system routing)");
292
+
293
+ while (true) {
294
+ const answer = await readline.question("Router (1): ");
295
+ const normalized = normalizeRouter(answer.trim() || "1");
296
+
297
+ if (normalized) {
298
+ return normalized;
299
+ }
300
+
301
+ console.log("Choose 1/2 or manifest/pages.");
302
+ }
303
+ }
304
+
169
305
  async function ensureTargetDirectory(targetDir) {
170
306
  const error = await validateTargetDirectory(targetDir);
171
307
 
172
308
  if (error) {
173
- throw new Error(error);
309
+ throw new ValidationError(error);
174
310
  }
175
311
  }
176
312
 
@@ -192,6 +328,20 @@ async function validateTargetDirectory(targetDir) {
192
328
  return null;
193
329
  }
194
330
 
331
+ function normalizeRouter(value) {
332
+ const normalized = value.toLowerCase();
333
+
334
+ if (normalized === "1" || normalized === "manifest") {
335
+ return "manifest";
336
+ }
337
+
338
+ if (normalized === "2" || normalized === "pages") {
339
+ return "pages";
340
+ }
341
+
342
+ return null;
343
+ }
344
+
195
345
  function normalizeAdapter(value) {
196
346
  const normalized = value.toLowerCase();
197
347
 
@@ -215,18 +365,45 @@ function normalizeAdapter(value) {
215
365
  return null;
216
366
  }
217
367
 
218
- function buildProjectFiles({ adapter, packageManager, projectName }) {
368
+ async function resolveVersions(packageNames) {
369
+ const entries = await Promise.all(
370
+ packageNames.map(async (name) => [name, `^${await fetchLatestVersion(name)}`]),
371
+ );
372
+ return Object.fromEntries(entries);
373
+ }
374
+
375
+ async function buildProjectFiles({ adapter, packageManager, projectName, router }) {
376
+ const packagesToResolve = [
377
+ "@pracht/cli",
378
+ "@pracht/vite-plugin",
379
+ "@pracht/core",
380
+ adapter.packageName,
381
+ ];
382
+ if (adapter.id === "vercel") {
383
+ packagesToResolve.push("vercel");
384
+ }
385
+
386
+ const versions = await resolveVersions(packagesToResolve);
387
+
219
388
  const files = {
220
389
  ".gitignore": "dist\nnode_modules\n.wrangler\n.vercel\n",
221
- "README.md": createReadme({ adapter, packageManager, projectName }),
222
- "package.json": createPackageJson({ adapter, projectName }),
390
+ "README.md": createReadme({ adapter, packageManager, projectName, router }),
391
+ "package.json": createPackageJson({ adapter, projectName, versions }),
223
392
  "src/api/health.ts": createHealthRoute(adapter),
224
- "src/routes.ts": createRoutesFile(),
225
- "src/routes/home.tsx": createHomeRoute(adapter),
226
- "src/shells/public.tsx": createShellFile(projectName),
227
- "vite.config.ts": createViteConfig(adapter),
393
+ "vite.config.ts": createViteConfig(adapter, router),
394
+ "tsconfig.json": createBaseTSConfig(adapter),
395
+ "AGENTS.md": createAgentInstructions({ adapter, packageManager, router }),
228
396
  };
229
397
 
398
+ if (router === "pages") {
399
+ files["src/pages/_app.tsx"] = createShellFile(projectName);
400
+ files["src/pages/index.tsx"] = createPagesHomeRoute(adapter);
401
+ } else {
402
+ files["src/routes.ts"] = createRoutesFile();
403
+ files["src/routes/home.tsx"] = createHomeRoute(adapter);
404
+ files["src/shells/public.tsx"] = createShellFile(projectName);
405
+ }
406
+
230
407
  if (adapter.id === "cloudflare") {
231
408
  files["wrangler.jsonc"] = createWranglerConfig(projectName);
232
409
  files["src/env.d.ts"] = createCloudflareEnvDeclaration();
@@ -235,16 +412,19 @@ function buildProjectFiles({ adapter, packageManager, projectName }) {
235
412
  return files;
236
413
  }
237
414
 
238
- function createPackageJson({ adapter, projectName }) {
415
+ function createPackageJson({ adapter, projectName, versions }) {
239
416
  const scripts = {
240
417
  build: "pracht build",
241
418
  dev: "pracht dev",
242
- preview: "pracht preview",
243
419
  };
244
420
 
421
+ if (adapter.id === "node") {
422
+ scripts.start = "node dist/server/server.js";
423
+ }
424
+
245
425
  const devDependencies = {
246
- "@pracht/cli": "latest",
247
- "@pracht/vite-plugin": "latest",
426
+ "@pracht/cli": versions["@pracht/cli"],
427
+ "@pracht/vite-plugin": versions["@pracht/vite-plugin"],
248
428
  preact: "^10.26.9",
249
429
  "preact-render-to-string": "^6.5.13",
250
430
  vite: "^8.0.0",
@@ -257,14 +437,14 @@ function createPackageJson({ adapter, projectName }) {
257
437
 
258
438
  if (adapter.id === "vercel") {
259
439
  scripts.deploy = "pracht build && vercel deploy --prebuilt";
260
- devDependencies.vercel = "latest";
440
+ devDependencies.vercel = versions["vercel"];
261
441
  }
262
442
 
263
443
  return `${JSON.stringify(
264
444
  {
265
445
  dependencies: {
266
- [adapter.packageName]: "latest",
267
- "@pracht/core": "latest",
446
+ [adapter.packageName]: versions[adapter.packageName],
447
+ "@pracht/core": versions["@pracht/core"],
268
448
  },
269
449
  devDependencies,
270
450
  name: projectName,
@@ -278,7 +458,7 @@ function createPackageJson({ adapter, projectName }) {
278
458
  )}\n`;
279
459
  }
280
460
 
281
- function createViteConfig(adapter) {
461
+ function createViteConfig(adapter, router) {
282
462
  const ADAPTER_IMPORTS = {
283
463
  node: { fn: "nodeAdapter", pkg: "@pracht/adapter-node" },
284
464
  cloudflare: { fn: "cloudflareAdapter", pkg: "@pracht/adapter-cloudflare" },
@@ -287,13 +467,18 @@ function createViteConfig(adapter) {
287
467
 
288
468
  const info = ADAPTER_IMPORTS[adapter.id] ?? ADAPTER_IMPORTS.node;
289
469
 
470
+ const prachtOptions =
471
+ router === "pages"
472
+ ? `{ pagesDir: "/src/pages", adapter: ${info.fn}() }`
473
+ : `{ adapter: ${info.fn}() }`;
474
+
290
475
  return [
291
476
  'import { defineConfig } from "vite";',
292
477
  'import { pracht } from "@pracht/vite-plugin";',
293
478
  `import { ${info.fn} } from "${info.pkg}";`,
294
479
  "",
295
480
  "export default defineConfig({",
296
- ` plugins: [pracht({ adapter: ${info.fn}() })],`,
481
+ ` plugins: [pracht(${prachtOptions})],`,
297
482
  "});",
298
483
  "",
299
484
  ].join("\n");
@@ -379,6 +564,57 @@ function createHomeRoute(adapter) {
379
564
  ].join("\n");
380
565
  }
381
566
 
567
+ function createPagesHomeRoute(adapter) {
568
+ return [
569
+ 'import type { LoaderArgs, RouteComponentProps } from "@pracht/core";',
570
+ "",
571
+ 'export const RENDER_MODE = "ssg";',
572
+ "",
573
+ "export async function loader(_args: LoaderArgs) {",
574
+ " return {",
575
+ ` adapter: ${JSON.stringify(adapter.label)},`,
576
+ " steps: [",
577
+ ' "Edit src/pages/index.tsx to change this page.",',
578
+ ' "Add more pages in src/pages/.",',
579
+ ' "Add API handlers in src/api/*.ts.",',
580
+ " ],",
581
+ " };",
582
+ "}",
583
+ "",
584
+ "export function Component({ data }: RouteComponentProps<typeof loader>) {",
585
+ " return (",
586
+ " <section>",
587
+ ' <p style={{ color: "#555", marginBottom: "8px" }}>Starter ready.</p>',
588
+ ' <h1 style={{ fontSize: "2.5rem", lineHeight: 1.1, margin: "0 0 16px" }}>Your pracht app is up and running.</h1>',
589
+ ' <p style={{ fontSize: "1.1rem", lineHeight: 1.6, marginBottom: "24px" }}>',
590
+ " This starter is configured for <strong>{data.adapter}</strong>.",
591
+ " </p>",
592
+ ' <ul style={{ lineHeight: 1.8, paddingLeft: "20px" }}>',
593
+ " {data.steps.map((step) => (",
594
+ " <li key={step}>{step}</li>",
595
+ " ))}",
596
+ " </ul>",
597
+ ' <p style={{ marginTop: "24px" }}>',
598
+ " Check <code>/api/health</code> for a simple API route.",
599
+ " </p>",
600
+ " </section>",
601
+ " );",
602
+ "}",
603
+ "",
604
+ ].join("\n");
605
+ }
606
+
607
+ function createBaseTSConfig(_adapter) {
608
+ const config = {
609
+ compilerOptions: {
610
+ jsx: "react-jsx",
611
+ jsxImportSource: "preact",
612
+ lib: ["ES2022"],
613
+ },
614
+ };
615
+ return JSON.stringify(config, null, 4);
616
+ }
617
+
382
618
  function createHealthRoute(adapter) {
383
619
  return [
384
620
  "export function GET() {",
@@ -426,10 +662,71 @@ function createCloudflareEnvDeclaration() {
426
662
  ].join("\n");
427
663
  }
428
664
 
429
- function createReadme({ adapter, packageManager, projectName }) {
665
+ function createAgentInstructions({ adapter, packageManager, router }) {
666
+ const runCmd = packageManager === "npm" ? "npm run" : packageManager;
667
+
668
+ const lines = [
669
+ "# Pracht App",
670
+ "",
671
+ "## Commands",
672
+ "",
673
+ `- \`${runCmd} dev\` — start the dev server`,
674
+ `- \`${runCmd} build\` — production build`,
675
+ ];
676
+
677
+ if (adapter.id === "node") {
678
+ lines.push(`- \`${runCmd} start\` — run the built server`);
679
+ }
680
+
681
+ if (adapter.id === "cloudflare" || adapter.id === "vercel") {
682
+ lines.push(`- \`${runCmd} deploy\` — build and deploy`);
683
+ }
684
+
685
+ lines.push("");
686
+ lines.push("## Scaffolding");
687
+ lines.push("");
688
+ lines.push("Use the CLI to generate new files:");
689
+ lines.push("");
690
+ lines.push("- `pracht generate route <name>` — add a route");
691
+ lines.push("- `pracht generate shell <name>` — add a shell");
692
+ lines.push("- `pracht generate middleware <name>` — add middleware");
693
+ lines.push("- `pracht generate api <name>` — add an API route");
694
+ lines.push("- `pracht doctor` — check project health");
695
+
696
+ lines.push("");
697
+ lines.push("## Project structure");
698
+ lines.push("");
699
+
700
+ if (router === "pages") {
701
+ lines.push("This app uses **pages routing** (file-system based).");
702
+ lines.push("");
703
+ lines.push("- `src/pages/` — file-system routes (each file becomes a route)");
704
+ lines.push("- `src/pages/_app.tsx` — app shell (layout and head)");
705
+ } else {
706
+ lines.push("This app uses **manifest routing**.");
707
+ lines.push("");
708
+ lines.push("- `src/routes.ts` — route manifest (defines all routes and shells)");
709
+ lines.push("- `src/routes/` — route components and loaders");
710
+ lines.push("- `src/shells/` — shell components (layouts)");
711
+ }
712
+
713
+ lines.push("- `src/api/` — API route handlers");
714
+ lines.push(`- \`vite.config.ts\` — Vite config with the ${adapter.label} adapter`);
715
+
716
+ if (adapter.id === "cloudflare") {
717
+ lines.push("- `wrangler.jsonc` — Cloudflare Workers configuration");
718
+ lines.push("- `src/env.d.ts` — TypeScript types for Cloudflare bindings");
719
+ }
720
+
721
+ lines.push("");
722
+
723
+ return lines.join("\n");
724
+ }
725
+
726
+ function createReadme({ adapter, packageManager, projectName, router }) {
430
727
  const installCommand = packageManager === "npm" ? "npm install" : `${packageManager} install`;
431
728
  const devCommand = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
432
- const previewCommand = packageManager === "npm" ? "npm run preview" : `${packageManager} preview`;
729
+ const startCommand = packageManager === "npm" ? "npm run start" : `${packageManager} start`;
433
730
  const deployCommand = packageManager === "npm" ? "npm run deploy" : `${packageManager} deploy`;
434
731
 
435
732
  const lines = [
@@ -441,9 +738,12 @@ function createReadme({ adapter, packageManager, projectName }) {
441
738
  "",
442
739
  `- \`${installCommand}\``,
443
740
  `- \`${devCommand}\``,
444
- `- \`${previewCommand}\``,
445
741
  ];
446
742
 
743
+ if (adapter.id === "node") {
744
+ lines.push(`- \`${startCommand}\``);
745
+ }
746
+
447
747
  if (adapter.id === "cloudflare") {
448
748
  lines.push(`- \`${deployCommand}\``);
449
749
  lines.push("");
@@ -461,8 +761,16 @@ function createReadme({ adapter, packageManager, projectName }) {
461
761
  lines.push("");
462
762
  lines.push("## Files");
463
763
  lines.push("");
464
- lines.push("- `src/routes.ts` defines your app manifest.");
465
- lines.push("- `src/routes/home.tsx` is the first page.");
764
+
765
+ if (router === "pages") {
766
+ lines.push("- `src/pages/` contains your file-system routes.");
767
+ lines.push("- `src/pages/_app.tsx` is the app shell.");
768
+ lines.push("- `src/pages/index.tsx` is the home page.");
769
+ } else {
770
+ lines.push("- `src/routes.ts` defines your app manifest.");
771
+ lines.push("- `src/routes/home.tsx` is the first page.");
772
+ }
773
+
466
774
  lines.push("- `src/api/health.ts` is a sample API route.");
467
775
 
468
776
  return `${lines.join("\n")}\n`;
@@ -514,7 +822,16 @@ function printHelp() {
514
822
  console.log(`create-pracht
515
823
 
516
824
  Usage:
517
- create-pracht [directory] [--adapter=node|cf|vercel] [--skip-install]
825
+ create-pracht [directory] [options]
826
+
827
+ Options:
828
+ --adapter=node|cf|vercel Choose hosting adapter (default: node)
829
+ --router=manifest|pages Choose routing system (default: manifest)
830
+ --skip-install Skip dependency installation
831
+ --yes, -y Accept defaults, skip all prompts
832
+ --json Output JSON summary instead of prose
833
+ --dry-run Show which files would be created without writing
834
+ -h, --help Show this help message
518
835
  `);
519
836
  }
520
837