@pracht/cli 1.1.5 → 1.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/CHANGELOG.md +11 -0
- package/bin/pracht.js +1 -40
- package/dist/build-BzQAQjuy.mjs +212 -0
- package/dist/build-metadata-IABPFFKk.mjs +49 -0
- package/dist/dev-BCFe3g38.mjs +25 -0
- package/dist/doctor-BcoqyBk-.mjs +25 -0
- package/dist/generate-BXkePCIx.mjs +404 -0
- package/dist/index.mjs +41 -0
- package/dist/inspect-DIjjCfs3.mjs +128 -0
- package/dist/manifest-DGq1n5LT.mjs +99 -0
- package/dist/project-DydjqBoS.mjs +155 -0
- package/dist/verification-Dfl3X4Zo.mjs +372 -0
- package/dist/verify-CObGxWtW.mjs +31 -0
- package/package.json +6 -2
- package/{lib/build-metadata.js → src/build-metadata.ts} +22 -9
- package/{lib/build-shared.js → src/build-shared.ts} +39 -13
- package/src/commands/build.ts +132 -0
- package/src/commands/dev.ts +27 -0
- package/src/commands/doctor.ts +33 -0
- package/{lib/commands/generate.js → src/commands/generate.ts} +178 -72
- package/{lib/commands/inspect.js → src/commands/inspect.ts} +81 -30
- package/src/commands/verify.ts +37 -0
- package/{lib/constants.js → src/constants.ts} +12 -2
- package/src/index.ts +21 -0
- package/{lib/manifest.js → src/manifest.ts} +32 -13
- package/{lib/project.js → src/project.ts} +48 -20
- package/src/utils.ts +72 -0
- package/{lib/verification.js → src/verification.ts} +103 -34
- package/tsdown.config.ts +8 -0
- package/lib/cli.js +0 -164
- package/lib/commands/build.js +0 -120
- package/lib/commands/dev.js +0 -16
- package/lib/commands/doctor.js +0 -21
- package/lib/commands/verify.js +0 -21
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
|
|
3
5
|
import {
|
|
4
6
|
ensureTrailingNewline,
|
|
5
7
|
parseApiMethods,
|
|
6
8
|
parseCommaList,
|
|
7
|
-
parseFlags,
|
|
8
|
-
printGenerateHelp,
|
|
9
9
|
quote,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
requireStringOption,
|
|
14
|
-
} from "../cli.js";
|
|
10
|
+
requireEnum,
|
|
11
|
+
requirePositiveInteger,
|
|
12
|
+
} from "../utils.js";
|
|
15
13
|
import {
|
|
16
14
|
extractRegistryEntries,
|
|
17
15
|
insertArrayItem,
|
|
@@ -29,20 +27,103 @@ import {
|
|
|
29
27
|
resolveRouteModulePath,
|
|
30
28
|
resolveScopedFile,
|
|
31
29
|
writeGeneratedFile,
|
|
30
|
+
type ProjectConfig,
|
|
32
31
|
} from "../project.js";
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const options = parseFlags(rest);
|
|
42
|
-
const project = readProjectConfig(process.cwd());
|
|
43
|
-
const result = runGenerate(kind, options, project);
|
|
33
|
+
interface GenerateResult {
|
|
34
|
+
created: string[];
|
|
35
|
+
kind: string;
|
|
36
|
+
updated: string[];
|
|
37
|
+
}
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
const routeCommand = defineCommand({
|
|
40
|
+
meta: {
|
|
41
|
+
name: "route",
|
|
42
|
+
description: "Scaffold a route module",
|
|
43
|
+
},
|
|
44
|
+
args: {
|
|
45
|
+
path: { type: "string", required: true, description: "Route path (e.g. /dashboard)" },
|
|
46
|
+
render: { type: "string", description: "Render mode: ssr, spa, ssg, or isg" },
|
|
47
|
+
shell: { type: "string", description: "Shell name" },
|
|
48
|
+
middleware: { type: "string", description: "Middleware names (comma-separated)" },
|
|
49
|
+
loader: { type: "boolean", description: "Include loader" },
|
|
50
|
+
"error-boundary": { type: "boolean", description: "Include error boundary" },
|
|
51
|
+
"static-paths": { type: "boolean", description: "Include static paths" },
|
|
52
|
+
title: { type: "string", description: "Page title" },
|
|
53
|
+
revalidate: { type: "string", description: "ISG revalidation seconds" },
|
|
54
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
55
|
+
},
|
|
56
|
+
async run({ args }) {
|
|
57
|
+
const project = readProjectConfig(process.cwd());
|
|
58
|
+
const result = generateRoute(args, project);
|
|
59
|
+
outputResult(result, Boolean(args.json));
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const shellCommand = defineCommand({
|
|
64
|
+
meta: {
|
|
65
|
+
name: "shell",
|
|
66
|
+
description: "Scaffold a shell component",
|
|
67
|
+
},
|
|
68
|
+
args: {
|
|
69
|
+
name: { type: "string", required: true, description: "Shell name" },
|
|
70
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
71
|
+
},
|
|
72
|
+
async run({ args }) {
|
|
73
|
+
const project = readProjectConfig(process.cwd());
|
|
74
|
+
const result = generateShell(args.name, project);
|
|
75
|
+
outputResult(result, Boolean(args.json));
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const middlewareCommand = defineCommand({
|
|
80
|
+
meta: {
|
|
81
|
+
name: "middleware",
|
|
82
|
+
description: "Scaffold a middleware function",
|
|
83
|
+
},
|
|
84
|
+
args: {
|
|
85
|
+
name: { type: "string", required: true, description: "Middleware name" },
|
|
86
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
87
|
+
},
|
|
88
|
+
async run({ args }) {
|
|
89
|
+
const project = readProjectConfig(process.cwd());
|
|
90
|
+
const result = generateMiddleware(args.name, project);
|
|
91
|
+
outputResult(result, Boolean(args.json));
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const apiCommand = defineCommand({
|
|
96
|
+
meta: {
|
|
97
|
+
name: "api",
|
|
98
|
+
description: "Scaffold an API route",
|
|
99
|
+
},
|
|
100
|
+
args: {
|
|
101
|
+
path: { type: "string", required: true, description: "API endpoint path" },
|
|
102
|
+
methods: { type: "string", description: "HTTP methods (comma-separated, e.g. GET,POST)" },
|
|
103
|
+
json: { type: "boolean", description: "Output as JSON" },
|
|
104
|
+
},
|
|
105
|
+
async run({ args }) {
|
|
106
|
+
const project = readProjectConfig(process.cwd());
|
|
107
|
+
const result = generateApi(args, project);
|
|
108
|
+
outputResult(result, Boolean(args.json));
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export default defineCommand({
|
|
113
|
+
meta: {
|
|
114
|
+
name: "generate",
|
|
115
|
+
description: "Scaffold framework files",
|
|
116
|
+
},
|
|
117
|
+
subCommands: {
|
|
118
|
+
route: routeCommand,
|
|
119
|
+
shell: shellCommand,
|
|
120
|
+
middleware: middlewareCommand,
|
|
121
|
+
api: apiCommand,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
function outputResult(result: GenerateResult, json: boolean): void {
|
|
126
|
+
if (json) {
|
|
46
127
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
47
128
|
return;
|
|
48
129
|
}
|
|
@@ -56,36 +137,31 @@ export async function generateCommand(args) {
|
|
|
56
137
|
}
|
|
57
138
|
}
|
|
58
139
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (kind === "api") {
|
|
70
|
-
return generateApi(options, project);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
throw new Error(`Unknown generate kind: ${kind}`);
|
|
140
|
+
interface RouteArgs {
|
|
141
|
+
"error-boundary"?: boolean;
|
|
142
|
+
loader?: boolean;
|
|
143
|
+
middleware?: string;
|
|
144
|
+
path: string;
|
|
145
|
+
render?: string;
|
|
146
|
+
revalidate?: string;
|
|
147
|
+
shell?: string;
|
|
148
|
+
"static-paths"?: boolean;
|
|
149
|
+
title?: string;
|
|
74
150
|
}
|
|
75
151
|
|
|
76
|
-
function generateRoute(
|
|
77
|
-
const routePath = normalizeRoutePathString(
|
|
78
|
-
const render =
|
|
79
|
-
const includeLoader = Boolean(
|
|
80
|
-
const includeErrorBoundary = Boolean(
|
|
81
|
-
const middleware = parseCommaList(
|
|
152
|
+
function generateRoute(args: RouteArgs, project: ProjectConfig): GenerateResult {
|
|
153
|
+
const routePath = normalizeRoutePathString(args.path);
|
|
154
|
+
const render = requireEnum(args.render, "render", ["spa", "ssr", "ssg", "isg"], "ssr");
|
|
155
|
+
const includeLoader = Boolean(args.loader);
|
|
156
|
+
const includeErrorBoundary = Boolean(args["error-boundary"]);
|
|
157
|
+
const middleware = parseCommaList(args.middleware);
|
|
82
158
|
const includeStaticPaths =
|
|
83
|
-
Boolean(
|
|
159
|
+
Boolean(args["static-paths"]) ||
|
|
84
160
|
(hasDynamicSegments(routePath) && (render === "ssg" || render === "isg"));
|
|
85
|
-
const title =
|
|
161
|
+
const title = args.title ?? titleFromPath(routePath);
|
|
86
162
|
|
|
87
163
|
if (project.mode === "pages") {
|
|
88
|
-
if (
|
|
164
|
+
if (args.shell) {
|
|
89
165
|
throw new Error("`pracht generate route --shell` is only available for manifest apps.");
|
|
90
166
|
}
|
|
91
167
|
if (middleware.length > 0) {
|
|
@@ -113,7 +189,7 @@ function generateRoute(options, project) {
|
|
|
113
189
|
extractRegistryEntries(manifestSource, "middleware").map((entry) => entry.name),
|
|
114
190
|
);
|
|
115
191
|
|
|
116
|
-
const shellName =
|
|
192
|
+
const shellName = args.shell;
|
|
117
193
|
if (shellName && !registeredShells.has(shellName)) {
|
|
118
194
|
throw new Error(`Shell "${shellName}" is not registered in ${project.appFile}.`);
|
|
119
195
|
}
|
|
@@ -152,7 +228,7 @@ function generateRoute(options, project) {
|
|
|
152
228
|
meta.push(`middleware: [${middleware.map((item) => quote(item)).join(", ")}]`);
|
|
153
229
|
}
|
|
154
230
|
if (render === "isg") {
|
|
155
|
-
const seconds =
|
|
231
|
+
const seconds = requirePositiveInteger(args.revalidate, "revalidate", 3600);
|
|
156
232
|
meta.push(`revalidate: timeRevalidate(${seconds})`);
|
|
157
233
|
}
|
|
158
234
|
|
|
@@ -182,7 +258,15 @@ function generatePagesRoute({
|
|
|
182
258
|
render,
|
|
183
259
|
routePath,
|
|
184
260
|
title,
|
|
185
|
-
}
|
|
261
|
+
}: {
|
|
262
|
+
includeErrorBoundary: boolean;
|
|
263
|
+
includeLoader: boolean;
|
|
264
|
+
includeStaticPaths: boolean;
|
|
265
|
+
project: ProjectConfig;
|
|
266
|
+
render: string;
|
|
267
|
+
routePath: string;
|
|
268
|
+
title: string;
|
|
269
|
+
}): GenerateResult {
|
|
186
270
|
const routeFile = resolvePagesRouteModulePath(project, routePath, ".tsx");
|
|
187
271
|
writeGeneratedFile(
|
|
188
272
|
routeFile.absolutePath,
|
|
@@ -203,14 +287,13 @@ function generatePagesRoute({
|
|
|
203
287
|
};
|
|
204
288
|
}
|
|
205
289
|
|
|
206
|
-
function generateShell(
|
|
290
|
+
function generateShell(name: string, project: ProjectConfig): GenerateResult {
|
|
207
291
|
if (project.mode === "pages") {
|
|
208
292
|
throw new Error(
|
|
209
293
|
"Pages router apps use a single `_app` shell. `pracht generate shell` is only available for manifest apps.",
|
|
210
294
|
);
|
|
211
295
|
}
|
|
212
296
|
|
|
213
|
-
const name = requireStringOption(options, "name");
|
|
214
297
|
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
215
298
|
assertFileExists(manifestPath, `App manifest not found at ${project.appFile}.`);
|
|
216
299
|
|
|
@@ -232,14 +315,13 @@ function generateShell(options, project) {
|
|
|
232
315
|
};
|
|
233
316
|
}
|
|
234
317
|
|
|
235
|
-
function generateMiddleware(
|
|
318
|
+
function generateMiddleware(name: string, project: ProjectConfig): GenerateResult {
|
|
236
319
|
if (project.mode === "pages") {
|
|
237
320
|
throw new Error(
|
|
238
321
|
"Pages router apps do not use manifest middleware registration. `pracht generate middleware` is only available for manifest apps.",
|
|
239
322
|
);
|
|
240
323
|
}
|
|
241
324
|
|
|
242
|
-
const name = requireStringOption(options, "name");
|
|
243
325
|
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
244
326
|
assertFileExists(manifestPath, `App manifest not found at ${project.appFile}.`);
|
|
245
327
|
|
|
@@ -261,9 +343,14 @@ function generateMiddleware(options, project) {
|
|
|
261
343
|
};
|
|
262
344
|
}
|
|
263
345
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
346
|
+
interface ApiArgs {
|
|
347
|
+
methods?: string;
|
|
348
|
+
path: string;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function generateApi(args: ApiArgs, project: ProjectConfig): GenerateResult {
|
|
352
|
+
const endpointPath = normalizeApiPath(args.path);
|
|
353
|
+
const methods = parseApiMethods(args.methods);
|
|
267
354
|
const apiFile = resolveApiModulePath(project, endpointPath);
|
|
268
355
|
writeGeneratedFile(apiFile.absolutePath, buildApiRouteSource({ endpointPath, methods }));
|
|
269
356
|
|
|
@@ -280,10 +367,16 @@ function buildManifestRouteModuleSource({
|
|
|
280
367
|
includeStaticPaths,
|
|
281
368
|
routePath,
|
|
282
369
|
title,
|
|
283
|
-
}
|
|
370
|
+
}: {
|
|
371
|
+
includeErrorBoundary: boolean;
|
|
372
|
+
includeLoader: boolean;
|
|
373
|
+
includeStaticPaths: boolean;
|
|
374
|
+
routePath: string;
|
|
375
|
+
title: string;
|
|
376
|
+
}): string {
|
|
284
377
|
const params = dynamicParamNames(routePath);
|
|
285
|
-
const imports = [];
|
|
286
|
-
const sections = [];
|
|
378
|
+
const imports: string[] = [];
|
|
379
|
+
const sections: string[] = [];
|
|
287
380
|
|
|
288
381
|
if (includeLoader) {
|
|
289
382
|
imports.push("LoaderArgs", "RouteComponentProps");
|
|
@@ -359,10 +452,17 @@ function buildPagesRouteModuleSource({
|
|
|
359
452
|
render,
|
|
360
453
|
routePath,
|
|
361
454
|
title,
|
|
362
|
-
}
|
|
455
|
+
}: {
|
|
456
|
+
includeErrorBoundary: boolean;
|
|
457
|
+
includeLoader: boolean;
|
|
458
|
+
includeStaticPaths: boolean;
|
|
459
|
+
render: string;
|
|
460
|
+
routePath: string;
|
|
461
|
+
title: string;
|
|
462
|
+
}): string {
|
|
363
463
|
const params = dynamicParamNames(routePath);
|
|
364
|
-
const imports = [];
|
|
365
|
-
const sections = [];
|
|
464
|
+
const imports: string[] = [];
|
|
465
|
+
const sections: string[] = [];
|
|
366
466
|
|
|
367
467
|
if (includeLoader) {
|
|
368
468
|
imports.push("LoaderArgs", "RouteComponentProps");
|
|
@@ -431,7 +531,7 @@ function buildPagesRouteModuleSource({
|
|
|
431
531
|
return `${sections.join("\n")}\n`;
|
|
432
532
|
}
|
|
433
533
|
|
|
434
|
-
function buildShellModuleSource(name) {
|
|
534
|
+
function buildShellModuleSource(name: string): string {
|
|
435
535
|
const title = titleCase(name);
|
|
436
536
|
return [
|
|
437
537
|
'import type { ShellProps } from "@pracht/core";',
|
|
@@ -451,7 +551,7 @@ function buildShellModuleSource(name) {
|
|
|
451
551
|
].join("\n");
|
|
452
552
|
}
|
|
453
553
|
|
|
454
|
-
function buildMiddlewareModuleSource() {
|
|
554
|
+
function buildMiddlewareModuleSource(): string {
|
|
455
555
|
return [
|
|
456
556
|
'import type { MiddlewareFn } from "@pracht/core";',
|
|
457
557
|
"",
|
|
@@ -462,7 +562,13 @@ function buildMiddlewareModuleSource() {
|
|
|
462
562
|
].join("\n");
|
|
463
563
|
}
|
|
464
564
|
|
|
465
|
-
function buildApiRouteSource({
|
|
565
|
+
function buildApiRouteSource({
|
|
566
|
+
endpointPath,
|
|
567
|
+
methods,
|
|
568
|
+
}: {
|
|
569
|
+
endpointPath: string;
|
|
570
|
+
methods: string[];
|
|
571
|
+
}): string {
|
|
466
572
|
const methodLines = methods.flatMap((method, index) => {
|
|
467
573
|
const lines = buildApiMethodSource(method, methods, endpointPath);
|
|
468
574
|
if (index === methods.length - 1) return lines;
|
|
@@ -472,7 +578,7 @@ function buildApiRouteSource({ endpointPath, methods }) {
|
|
|
472
578
|
return ['import type { BaseRouteArgs } from "@pracht/core";', "", ...methodLines, ""].join("\n");
|
|
473
579
|
}
|
|
474
580
|
|
|
475
|
-
function buildApiMethodSource(method, methods, endpointPath) {
|
|
581
|
+
function buildApiMethodSource(method: string, methods: string[], endpointPath: string): string[] {
|
|
476
582
|
if (method === "DELETE" || method === "HEAD") {
|
|
477
583
|
return [
|
|
478
584
|
`export function ${method}(_args: BaseRouteArgs) {`,
|
|
@@ -509,22 +615,22 @@ function buildApiMethodSource(method, methods, endpointPath) {
|
|
|
509
615
|
];
|
|
510
616
|
}
|
|
511
617
|
|
|
512
|
-
function normalizeRoutePathString(value) {
|
|
618
|
+
function normalizeRoutePathString(value: string): string {
|
|
513
619
|
if (!value || value === "/") return "/";
|
|
514
620
|
const normalized = `/${value}`.replace(/\/+/g, "/");
|
|
515
621
|
return normalized !== "/" && normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
516
622
|
}
|
|
517
623
|
|
|
518
|
-
function normalizeApiPath(value) {
|
|
624
|
+
function normalizeApiPath(value: string): string {
|
|
519
625
|
const normalized = normalizeRoutePathString(value).replace(/^\/api(?=\/|$)/, "");
|
|
520
626
|
return normalized || "/";
|
|
521
627
|
}
|
|
522
628
|
|
|
523
|
-
function hasDynamicSegments(routePath) {
|
|
629
|
+
function hasDynamicSegments(routePath: string): boolean {
|
|
524
630
|
return routePath.split("/").some((segment) => segment.startsWith(":") || segment === "*");
|
|
525
631
|
}
|
|
526
632
|
|
|
527
|
-
function dynamicParamNames(routePath) {
|
|
633
|
+
function dynamicParamNames(routePath: string): string[] {
|
|
528
634
|
return routePath
|
|
529
635
|
.split("/")
|
|
530
636
|
.filter(Boolean)
|
|
@@ -533,10 +639,10 @@ function dynamicParamNames(routePath) {
|
|
|
533
639
|
if (segment === "*") return "slug";
|
|
534
640
|
return null;
|
|
535
641
|
})
|
|
536
|
-
.filter(
|
|
642
|
+
.filter((s): s is string => s !== null);
|
|
537
643
|
}
|
|
538
644
|
|
|
539
|
-
function routeIdFromPath(routePath) {
|
|
645
|
+
function routeIdFromPath(routePath: string): string {
|
|
540
646
|
if (routePath === "/") return "index";
|
|
541
647
|
return routePath
|
|
542
648
|
.split("/")
|
|
@@ -545,13 +651,13 @@ function routeIdFromPath(routePath) {
|
|
|
545
651
|
.join("-");
|
|
546
652
|
}
|
|
547
653
|
|
|
548
|
-
function titleFromPath(routePath) {
|
|
654
|
+
function titleFromPath(routePath: string): string {
|
|
549
655
|
if (routePath === "/") return "Home";
|
|
550
656
|
const lastSegment = routePath.split("/").filter(Boolean).at(-1) ?? "Page";
|
|
551
657
|
return titleCase(lastSegment.replace(/^:/, "").replace(/\*/g, "slug"));
|
|
552
658
|
}
|
|
553
659
|
|
|
554
|
-
function titleCase(value) {
|
|
660
|
+
function titleCase(value: string): string {
|
|
555
661
|
return value
|
|
556
662
|
.split(/[-_/]/)
|
|
557
663
|
.filter(Boolean)
|
|
@@ -559,7 +665,7 @@ function titleCase(value) {
|
|
|
559
665
|
.join(" ");
|
|
560
666
|
}
|
|
561
667
|
|
|
562
|
-
function buildStaticPathsStub(params) {
|
|
668
|
+
function buildStaticPathsStub(params: string[]): string {
|
|
563
669
|
if (params.length === 0) {
|
|
564
670
|
return "{}";
|
|
565
671
|
}
|
|
@@ -567,6 +673,6 @@ function buildStaticPathsStub(params) {
|
|
|
567
673
|
return `{ ${params.map((name) => `${name}: ${quote(`example-${name}`)}`).join(", ")} }`;
|
|
568
674
|
}
|
|
569
675
|
|
|
570
|
-
function escapeJsxText(value) {
|
|
676
|
+
function escapeJsxText(value: string): string {
|
|
571
677
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
572
678
|
}
|
|
@@ -1,40 +1,76 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
import { createServer, type ViteDevServer } from "vite";
|
|
5
6
|
|
|
6
|
-
import { handleCliError
|
|
7
|
+
import { handleCliError } from "../utils.js";
|
|
7
8
|
import { readClientBuildAssets } from "../build-metadata.js";
|
|
8
|
-
import { HTTP_METHODS } from "../constants.js";
|
|
9
|
+
import { HTTP_METHODS, type HttpMethod } from "../constants.js";
|
|
9
10
|
import { readProjectConfig, resolveProjectPath } from "../project.js";
|
|
10
11
|
|
|
11
12
|
const INSPECT_TARGETS = new Set(["routes", "api", "build", "all"]);
|
|
12
|
-
const METHOD_ORDER = [...HTTP_METHODS];
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
const METHOD_ORDER: HttpMethod[] = [...HTTP_METHODS];
|
|
14
|
+
|
|
15
|
+
export default defineCommand({
|
|
16
|
+
meta: {
|
|
17
|
+
name: "inspect",
|
|
18
|
+
description: "Inspect resolved app graph",
|
|
19
|
+
},
|
|
20
|
+
args: {
|
|
21
|
+
target: {
|
|
22
|
+
type: "positional",
|
|
23
|
+
description: "Inspect target: routes, api, build, or all",
|
|
24
|
+
required: false,
|
|
25
|
+
},
|
|
26
|
+
json: {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "Output as JSON",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
async run({ args }) {
|
|
32
|
+
const target = args.target || "all";
|
|
33
|
+
|
|
34
|
+
if (!INSPECT_TARGETS.has(target)) {
|
|
35
|
+
handleCliError(new Error(`Unknown inspect target: ${target}`), {
|
|
36
|
+
json: Boolean(args.json),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
const report = await runInspect(process.cwd(), { target });
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
if (args.json) {
|
|
43
|
+
console.log(JSON.stringify(report, null, 2));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
33
46
|
|
|
34
|
-
|
|
47
|
+
printInspectReport(report);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
interface InspectReport {
|
|
52
|
+
api?: { file: string; methods: string[]; path: string }[];
|
|
53
|
+
build?: {
|
|
54
|
+
adapterTarget: string;
|
|
55
|
+
clientEntryUrl: string | null;
|
|
56
|
+
cssManifest: Record<string, string[]>;
|
|
57
|
+
jsManifest: Record<string, string[]>;
|
|
58
|
+
};
|
|
59
|
+
mode: string;
|
|
60
|
+
routes?: {
|
|
61
|
+
file: string;
|
|
62
|
+
id: string;
|
|
63
|
+
loaderFile: string | null;
|
|
64
|
+
middleware: string[];
|
|
65
|
+
path: string;
|
|
66
|
+
render: string | null;
|
|
67
|
+
revalidate: unknown;
|
|
68
|
+
shell: string | null;
|
|
69
|
+
shellFile: string | null;
|
|
70
|
+
}[];
|
|
35
71
|
}
|
|
36
72
|
|
|
37
|
-
|
|
73
|
+
async function runInspect(root: string, { target = "all" } = {}): Promise<InspectReport> {
|
|
38
74
|
const project = readProjectConfig(root);
|
|
39
75
|
|
|
40
76
|
if (!project.configFile) {
|
|
@@ -66,7 +102,7 @@ export async function runInspect(root, { target = "all" } = {}) {
|
|
|
66
102
|
|
|
67
103
|
try {
|
|
68
104
|
const serverModule = await server.ssrLoadModule("virtual:pracht/server");
|
|
69
|
-
const report = {
|
|
105
|
+
const report: InspectReport = {
|
|
70
106
|
mode: project.mode,
|
|
71
107
|
};
|
|
72
108
|
|
|
@@ -76,7 +112,7 @@ export async function runInspect(root, { target = "all" } = {}) {
|
|
|
76
112
|
|
|
77
113
|
if (target === "api" || target === "all") {
|
|
78
114
|
report.api = await Promise.all(
|
|
79
|
-
serverModule.apiRoutes.map(async (route) => ({
|
|
115
|
+
serverModule.apiRoutes.map(async (route: { file: string; path: string }) => ({
|
|
80
116
|
file: route.file,
|
|
81
117
|
methods: await detectApiMethods(server, root, route.file),
|
|
82
118
|
path: route.path,
|
|
@@ -100,7 +136,19 @@ export async function runInspect(root, { target = "all" } = {}) {
|
|
|
100
136
|
}
|
|
101
137
|
}
|
|
102
138
|
|
|
103
|
-
|
|
139
|
+
interface RouteEntry {
|
|
140
|
+
file: string;
|
|
141
|
+
id: string;
|
|
142
|
+
loaderFile?: string;
|
|
143
|
+
middleware: string[];
|
|
144
|
+
path: string;
|
|
145
|
+
render?: string;
|
|
146
|
+
revalidate?: unknown;
|
|
147
|
+
shell?: string;
|
|
148
|
+
shellFile?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function serializeRoutes(routes: RouteEntry[]) {
|
|
104
152
|
return routes.map((route) => ({
|
|
105
153
|
file: route.file,
|
|
106
154
|
id: route.id,
|
|
@@ -114,11 +162,14 @@ function serializeRoutes(routes) {
|
|
|
114
162
|
}));
|
|
115
163
|
}
|
|
116
164
|
|
|
117
|
-
async function detectApiMethods(
|
|
165
|
+
async function detectApiMethods(
|
|
166
|
+
server: ViteDevServer,
|
|
167
|
+
root: string,
|
|
168
|
+
file: string,
|
|
169
|
+
): Promise<string[]> {
|
|
118
170
|
const resolvedFile = resolve(root, `.${file}`);
|
|
119
171
|
const source = readFileSync(resolvedFile, "utf-8");
|
|
120
172
|
|
|
121
|
-
// Use module evaluation first so re-exported handlers are reflected too.
|
|
122
173
|
try {
|
|
123
174
|
const module = await server.ssrLoadModule(file);
|
|
124
175
|
return METHOD_ORDER.filter((method) => typeof module[method] === "function");
|
|
@@ -129,7 +180,7 @@ async function detectApiMethods(server, root, file) {
|
|
|
129
180
|
}
|
|
130
181
|
}
|
|
131
182
|
|
|
132
|
-
function printInspectReport(report) {
|
|
183
|
+
function printInspectReport(report: InspectReport): void {
|
|
133
184
|
console.log(`Pracht inspect (${report.mode} mode)`);
|
|
134
185
|
|
|
135
186
|
if (report.routes) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
|
|
3
|
+
import { runVerification } from "../verification.js";
|
|
4
|
+
|
|
5
|
+
export default defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "verify",
|
|
8
|
+
description: "Fast framework-aware verification",
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
changed: {
|
|
12
|
+
type: "boolean",
|
|
13
|
+
description: "Only check changed files",
|
|
14
|
+
},
|
|
15
|
+
json: {
|
|
16
|
+
type: "boolean",
|
|
17
|
+
description: "Output as JSON",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
async run({ args }) {
|
|
21
|
+
const report = runVerification(process.cwd(), { changed: Boolean(args.changed) });
|
|
22
|
+
|
|
23
|
+
if (args.json) {
|
|
24
|
+
console.log(JSON.stringify(report, null, 2));
|
|
25
|
+
} else {
|
|
26
|
+
console.log(`Pracht verify (${report.mode} mode, ${report.scope} scope)`);
|
|
27
|
+
for (const check of report.checks) {
|
|
28
|
+
console.log(`${check.status.toUpperCase().padEnd(7)} ${check.message}`);
|
|
29
|
+
}
|
|
30
|
+
console.log(report.ok ? "\nNo blocking issues found." : "\nBlocking issues found.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!report.ok) {
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const DEFAULT_SECURITY_HEADERS = {
|
|
1
|
+
export const DEFAULT_SECURITY_HEADERS: Record<string, string> = {
|
|
2
2
|
"permissions-policy":
|
|
3
3
|
"accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()",
|
|
4
4
|
"referrer-policy": "strict-origin-when-cross-origin",
|
|
@@ -19,4 +19,14 @@ export const PROJECT_DEFAULTS = {
|
|
|
19
19
|
shellsDir: "/src/shells",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export
|
|
22
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
23
|
+
|
|
24
|
+
export const HTTP_METHODS = new Set<HttpMethod>([
|
|
25
|
+
"GET",
|
|
26
|
+
"POST",
|
|
27
|
+
"PUT",
|
|
28
|
+
"PATCH",
|
|
29
|
+
"DELETE",
|
|
30
|
+
"HEAD",
|
|
31
|
+
"OPTIONS",
|
|
32
|
+
]);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineCommand, runMain } from "citty";
|
|
2
|
+
|
|
3
|
+
import { VERSION } from "./constants.js";
|
|
4
|
+
|
|
5
|
+
const main = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "pracht",
|
|
8
|
+
version: VERSION,
|
|
9
|
+
description: "The pracht CLI",
|
|
10
|
+
},
|
|
11
|
+
subCommands: {
|
|
12
|
+
build: () => import("./commands/build.js").then((m) => m.default),
|
|
13
|
+
dev: () => import("./commands/dev.js").then((m) => m.default),
|
|
14
|
+
doctor: () => import("./commands/doctor.js").then((m) => m.default),
|
|
15
|
+
generate: () => import("./commands/generate.js").then((m) => m.default),
|
|
16
|
+
inspect: () => import("./commands/inspect.js").then((m) => m.default),
|
|
17
|
+
verify: () => import("./commands/verify.js").then((m) => m.default),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
runMain(main);
|