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 +4 -7
- package/bin/create-pracht.js +6 -3
- package/package.json +13 -1
- package/src/index.js +369 -52
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
|
-
|
|
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
|
-
```
|
package/bin/create-pracht.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import { run } from "../src/index.js";
|
|
4
4
|
|
|
5
5
|
run().catch((error) => {
|
|
6
|
-
|
|
7
|
-
console.error(
|
|
8
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
log("create-pracht");
|
|
55
|
+
log(`Using ${packageManager} for this scaffold.`);
|
|
56
|
+
log("");
|
|
40
57
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const targetDir = resolve(process.cwd(), dir);
|
|
62
|
+
let resolvedDir = dir;
|
|
63
|
+
let resolvedAdapter = adapterId;
|
|
64
|
+
let resolvedRouter = router;
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
if (resolvedDir == null || resolvedAdapter == null || resolvedRouter == null) {
|
|
67
|
+
const readline = createInterface({
|
|
68
|
+
input: process.stdin,
|
|
69
|
+
output: process.stdout,
|
|
70
|
+
});
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
console.log(
|
|
63
|
-
|
|
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[
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"
|
|
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": "
|
|
247
|
-
"@pracht/vite-plugin": "
|
|
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 = "
|
|
440
|
+
devDependencies.vercel = versions["vercel"];
|
|
261
441
|
}
|
|
262
442
|
|
|
263
443
|
return `${JSON.stringify(
|
|
264
444
|
{
|
|
265
445
|
dependencies: {
|
|
266
|
-
[adapter.packageName]:
|
|
267
|
-
"@pracht/core": "
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
465
|
-
|
|
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] [
|
|
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
|
|