@pracht/cli 1.1.5 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/bin/pracht.js +1 -40
- package/dist/build-DlABFPbG.mjs +214 -0
- package/dist/build-metadata-BOChHO8g.mjs +58 -0
- package/dist/dev-BCFe3g38.mjs +25 -0
- package/dist/doctor-CQq64tfW.mjs +25 -0
- package/dist/generate-sHtRHRAH.mjs +400 -0
- package/dist/index.mjs +41 -0
- package/dist/inspect-DP-h3vLw.mjs +123 -0
- package/dist/manifest-DGq1n5LT.mjs +99 -0
- package/dist/project-DUyH_7eJ.mjs +148 -0
- package/dist/verification-CId4crSF.mjs +372 -0
- package/dist/verify-D7Bg1Su6.mjs +31 -0
- package/package.json +6 -2
- package/src/build-metadata.ts +101 -0
- package/{lib/build-shared.js → src/build-shared.ts} +30 -20
- package/src/commands/build.ts +137 -0
- package/src/commands/dev.ts +27 -0
- package/src/commands/doctor.ts +33 -0
- package/{lib/commands/generate.js → src/commands/generate.ts} +193 -149
- package/{lib/commands/inspect.js → src/commands/inspect.ts} +83 -34
- package/src/commands/verify.ts +37 -0
- package/src/constants.ts +26 -0
- package/src/index.ts +21 -0
- package/{lib/manifest.js → src/manifest.ts} +32 -13
- package/{lib/project.js → src/project.ts} +51 -34
- package/src/utils.ts +72 -0
- package/{lib/verification.js → src/verification.ts} +103 -34
- package/tsdown.config.ts +8 -0
- package/lib/build-metadata.js +0 -73
- 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
- package/lib/constants.js +0 -22
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
listFilesRecursively,
|
|
10
10
|
readProjectConfig,
|
|
11
11
|
resolveProjectPath,
|
|
12
|
+
type ProjectConfig,
|
|
12
13
|
} from "./project.js";
|
|
13
14
|
|
|
14
15
|
const CONFIG_FILE_NAMES = new Set([
|
|
@@ -23,7 +24,30 @@ const CONFIG_FILE_NAMES = new Set([
|
|
|
23
24
|
const MODULE_SOURCE_RE = /\.(ts|tsx|js|jsx)$/;
|
|
24
25
|
const PAGE_SOURCE_RE = /\.(ts|tsx|js|jsx|md|mdx)$/;
|
|
25
26
|
|
|
26
|
-
export
|
|
27
|
+
export interface Check {
|
|
28
|
+
message: string;
|
|
29
|
+
status: "ok" | "warning" | "error";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DoctorReport {
|
|
33
|
+
checks: Check[];
|
|
34
|
+
configFile: string | null;
|
|
35
|
+
mode: "manifest" | "pages";
|
|
36
|
+
ok: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface VerificationReport {
|
|
40
|
+
changedFiles: string[];
|
|
41
|
+
checks: Check[];
|
|
42
|
+
configFile: string | null;
|
|
43
|
+
frameworkFiles: string[];
|
|
44
|
+
mode: "manifest" | "pages";
|
|
45
|
+
ok: boolean;
|
|
46
|
+
requestedScope: string;
|
|
47
|
+
scope: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runDoctor(root: string): DoctorReport {
|
|
27
51
|
const report = runVerification(root);
|
|
28
52
|
|
|
29
53
|
return {
|
|
@@ -34,9 +58,12 @@ export function runDoctor(root) {
|
|
|
34
58
|
};
|
|
35
59
|
}
|
|
36
60
|
|
|
37
|
-
export function runVerification(
|
|
61
|
+
export function runVerification(
|
|
62
|
+
root: string,
|
|
63
|
+
options: { changed?: boolean } = {},
|
|
64
|
+
): VerificationReport {
|
|
38
65
|
const project = readProjectConfig(root);
|
|
39
|
-
const checks = [];
|
|
66
|
+
const checks: Check[] = [];
|
|
40
67
|
const packageJsonPath = resolve(project.root, "package.json");
|
|
41
68
|
const configDisplayPath = project.configFile
|
|
42
69
|
? displayPath(root, project.configFile)
|
|
@@ -45,7 +72,7 @@ export function runVerification(root, options = {}) {
|
|
|
45
72
|
|
|
46
73
|
collectConfigChecks(project, checks, configDisplayPath);
|
|
47
74
|
|
|
48
|
-
let changedInfo = {
|
|
75
|
+
let changedInfo: { files: string[]; warning: string | null } = {
|
|
49
76
|
files: [],
|
|
50
77
|
warning: null,
|
|
51
78
|
};
|
|
@@ -92,7 +119,11 @@ export function runVerification(root, options = {}) {
|
|
|
92
119
|
};
|
|
93
120
|
}
|
|
94
121
|
|
|
95
|
-
function collectConfigChecks(
|
|
122
|
+
function collectConfigChecks(
|
|
123
|
+
project: ProjectConfig,
|
|
124
|
+
checks: Check[],
|
|
125
|
+
configDisplayPath: string,
|
|
126
|
+
): void {
|
|
96
127
|
if (!project.configFile) {
|
|
97
128
|
checks.push(createCheck("error", "Missing vite config."));
|
|
98
129
|
} else {
|
|
@@ -106,7 +137,11 @@ function collectConfigChecks(project, checks, configDisplayPath) {
|
|
|
106
137
|
}
|
|
107
138
|
}
|
|
108
139
|
|
|
109
|
-
function collectManifestVerification(
|
|
140
|
+
function collectManifestVerification(
|
|
141
|
+
project: ProjectConfig,
|
|
142
|
+
checks: Check[],
|
|
143
|
+
{ changedFiles, scope }: { changedFiles: string[]; scope: string },
|
|
144
|
+
): void {
|
|
110
145
|
const manifestPath = resolveProjectPath(project.root, project.appFile);
|
|
111
146
|
if (!existsSync(manifestPath)) {
|
|
112
147
|
checks.push(createCheck("error", `App manifest is missing at ${project.appFile}.`));
|
|
@@ -187,12 +222,12 @@ function collectManifestVerification(project, checks, { changedFiles, scope }) {
|
|
|
187
222
|
}
|
|
188
223
|
|
|
189
224
|
function collectChangedManifestModuleChecks(
|
|
190
|
-
project,
|
|
191
|
-
checks,
|
|
192
|
-
manifestPath,
|
|
193
|
-
relativeModules,
|
|
194
|
-
changedFiles,
|
|
195
|
-
) {
|
|
225
|
+
project: ProjectConfig,
|
|
226
|
+
checks: Check[],
|
|
227
|
+
manifestPath: string,
|
|
228
|
+
relativeModules: string[],
|
|
229
|
+
changedFiles: string[],
|
|
230
|
+
): void {
|
|
196
231
|
const manifestDir = dirname(manifestPath);
|
|
197
232
|
const referencedModules = new Set(relativeModules.map(normalizePath));
|
|
198
233
|
const moduleDirectories = [
|
|
@@ -241,7 +276,11 @@ function collectChangedManifestModuleChecks(
|
|
|
241
276
|
}
|
|
242
277
|
}
|
|
243
278
|
|
|
244
|
-
function collectPagesVerification(
|
|
279
|
+
function collectPagesVerification(
|
|
280
|
+
project: ProjectConfig,
|
|
281
|
+
checks: Check[],
|
|
282
|
+
{ changedFiles, scope }: { changedFiles: string[]; scope: string },
|
|
283
|
+
): void {
|
|
245
284
|
const pagesDir = resolveProjectPath(project.root, project.pagesDir);
|
|
246
285
|
if (!existsSync(pagesDir)) {
|
|
247
286
|
checks.push(createCheck("error", `Pages directory is missing at ${project.pagesDir}.`));
|
|
@@ -250,7 +289,7 @@ function collectPagesVerification(project, checks, { changedFiles, scope }) {
|
|
|
250
289
|
|
|
251
290
|
const pages = scanPagesDirectory(pagesDir);
|
|
252
291
|
const routes = pages.filter((page) => page.kind === "route");
|
|
253
|
-
const duplicates = collectDuplicateRoutePaths(routes).map((entry) => ({
|
|
292
|
+
const duplicates = collectDuplicateRoutePaths(routes as PagesRoute[]).map((entry) => ({
|
|
254
293
|
...entry,
|
|
255
294
|
files: entry.files.map((file) => displayPath(project.root, file)),
|
|
256
295
|
}));
|
|
@@ -298,7 +337,12 @@ function collectPagesVerification(project, checks, { changedFiles, scope }) {
|
|
|
298
337
|
}
|
|
299
338
|
}
|
|
300
339
|
|
|
301
|
-
function collectChangedPagesChecks(
|
|
340
|
+
function collectChangedPagesChecks(
|
|
341
|
+
project: ProjectConfig,
|
|
342
|
+
checks: Check[],
|
|
343
|
+
pagesDir: string,
|
|
344
|
+
changedFiles: string[],
|
|
345
|
+
): void {
|
|
302
346
|
for (const file of changedFiles) {
|
|
303
347
|
if (!isWithinDirectory(file, pagesDir)) continue;
|
|
304
348
|
if (!PAGE_SOURCE_RE.test(file)) continue;
|
|
@@ -344,7 +388,11 @@ function collectChangedPagesChecks(project, checks, pagesDir, changedFiles) {
|
|
|
344
388
|
}
|
|
345
389
|
}
|
|
346
390
|
|
|
347
|
-
function collectApiVerification(
|
|
391
|
+
function collectApiVerification(
|
|
392
|
+
project: ProjectConfig,
|
|
393
|
+
checks: Check[],
|
|
394
|
+
{ changedFiles, scope }: { changedFiles: string[]; scope: string },
|
|
395
|
+
): void {
|
|
348
396
|
const apiDir = resolveProjectPath(project.root, project.apiDir);
|
|
349
397
|
const changedApiFiles = changedFiles.filter((file) => isWithinDirectory(file, apiDir));
|
|
350
398
|
if (scope === "changed" && changedApiFiles.length === 0) {
|
|
@@ -364,7 +412,7 @@ function collectApiVerification(project, checks, { changedFiles, scope }) {
|
|
|
364
412
|
}
|
|
365
413
|
|
|
366
414
|
const apiFiles = listFilesRecursively(apiDir).filter((file) => MODULE_SOURCE_RE.test(file));
|
|
367
|
-
const routeMap = new Map();
|
|
415
|
+
const routeMap = new Map<string, string[]>();
|
|
368
416
|
|
|
369
417
|
for (const file of apiFiles) {
|
|
370
418
|
const routePath = resolveApiRoutePath(apiDir, file);
|
|
@@ -422,14 +470,18 @@ function collectApiVerification(project, checks, { changedFiles, scope }) {
|
|
|
422
470
|
}
|
|
423
471
|
}
|
|
424
472
|
|
|
425
|
-
function collectPackageChecks(
|
|
473
|
+
function collectPackageChecks(
|
|
474
|
+
project: ProjectConfig,
|
|
475
|
+
checks: Check[],
|
|
476
|
+
packageJsonPath: string,
|
|
477
|
+
): void {
|
|
426
478
|
if (!existsSync(packageJsonPath)) {
|
|
427
479
|
checks.push(createCheck("warning", "No package.json found in the current app root."));
|
|
428
480
|
return;
|
|
429
481
|
}
|
|
430
482
|
|
|
431
483
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
432
|
-
const deps = {
|
|
484
|
+
const deps: Record<string, string> = {
|
|
433
485
|
...packageJson.dependencies,
|
|
434
486
|
...packageJson.devDependencies,
|
|
435
487
|
};
|
|
@@ -455,7 +507,7 @@ function collectPackageChecks(project, checks, packageJsonPath) {
|
|
|
455
507
|
}
|
|
456
508
|
}
|
|
457
509
|
|
|
458
|
-
function collectChangedFiles(root) {
|
|
510
|
+
function collectChangedFiles(root: string): { files: string[]; warning: string | null } {
|
|
459
511
|
try {
|
|
460
512
|
const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
461
513
|
cwd: root,
|
|
@@ -469,7 +521,7 @@ function collectChangedFiles(root) {
|
|
|
469
521
|
cwd: repoRoot,
|
|
470
522
|
encoding: "utf-8",
|
|
471
523
|
});
|
|
472
|
-
const files = new Set();
|
|
524
|
+
const files = new Set<string>();
|
|
473
525
|
|
|
474
526
|
for (const line of output.split(/\r?\n/).filter(Boolean)) {
|
|
475
527
|
const record = line.slice(3);
|
|
@@ -496,7 +548,12 @@ function collectChangedFiles(root) {
|
|
|
496
548
|
}
|
|
497
549
|
}
|
|
498
550
|
|
|
499
|
-
function addChangedFile(
|
|
551
|
+
function addChangedFile(
|
|
552
|
+
files: Set<string>,
|
|
553
|
+
repoRoot: string,
|
|
554
|
+
prefix: string,
|
|
555
|
+
repoRelativePath: string,
|
|
556
|
+
): void {
|
|
500
557
|
if (prefix && !repoRelativePath.startsWith(prefix)) {
|
|
501
558
|
return;
|
|
502
559
|
}
|
|
@@ -509,7 +566,11 @@ function addChangedFile(files, repoRoot, prefix, repoRelativePath) {
|
|
|
509
566
|
files.add(resolve(repoRoot, projectRelativePath));
|
|
510
567
|
}
|
|
511
568
|
|
|
512
|
-
function filterFrameworkFiles(
|
|
569
|
+
function filterFrameworkFiles(
|
|
570
|
+
project: ProjectConfig,
|
|
571
|
+
files: string[],
|
|
572
|
+
packageJsonPath: string,
|
|
573
|
+
): string[] {
|
|
513
574
|
const appFile = resolveProjectPath(project.root, project.appFile);
|
|
514
575
|
const routesDir = resolveProjectPath(project.root, project.routesDir);
|
|
515
576
|
const shellsDir = resolveProjectPath(project.root, project.shellsDir);
|
|
@@ -532,7 +593,7 @@ function filterFrameworkFiles(project, files, packageJsonPath) {
|
|
|
532
593
|
});
|
|
533
594
|
}
|
|
534
595
|
|
|
535
|
-
function requiresFullVerification(project, changedFiles) {
|
|
596
|
+
function requiresFullVerification(project: ProjectConfig, changedFiles: string[]): boolean {
|
|
536
597
|
const packageJsonPath = resolve(project.root, "package.json");
|
|
537
598
|
const appFile = resolveProjectPath(project.root, project.appFile);
|
|
538
599
|
|
|
@@ -545,13 +606,21 @@ function requiresFullVerification(project, changedFiles) {
|
|
|
545
606
|
});
|
|
546
607
|
}
|
|
547
608
|
|
|
548
|
-
|
|
609
|
+
type PagesFile = { file: string; kind: "shell" } | { file: string; kind: "ignored" } | PagesRoute;
|
|
610
|
+
|
|
611
|
+
interface PagesRoute {
|
|
612
|
+
file: string;
|
|
613
|
+
kind: "route";
|
|
614
|
+
routePath: string;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function scanPagesDirectory(pagesDir: string): PagesFile[] {
|
|
549
618
|
return listFilesRecursively(pagesDir)
|
|
550
619
|
.filter((file) => PAGE_SOURCE_RE.test(file))
|
|
551
620
|
.map((file) => describePagesFile(pagesDir, file));
|
|
552
621
|
}
|
|
553
622
|
|
|
554
|
-
function describePagesFile(pagesDir, file) {
|
|
623
|
+
function describePagesFile(pagesDir: string, file: string): PagesFile {
|
|
555
624
|
const relativePath = relative(pagesDir, file).replace(/\\/g, "/");
|
|
556
625
|
const routePath = relativePath.replace(/\.(tsx?|jsx?|mdx?)$/, "");
|
|
557
626
|
const name = basename(routePath);
|
|
@@ -580,8 +649,8 @@ function describePagesFile(pagesDir, file) {
|
|
|
580
649
|
};
|
|
581
650
|
}
|
|
582
651
|
|
|
583
|
-
function collectDuplicateRoutePaths(routes) {
|
|
584
|
-
const routeMap = new Map();
|
|
652
|
+
function collectDuplicateRoutePaths(routes: PagesRoute[]): { files: string[]; path: string }[] {
|
|
653
|
+
const routeMap = new Map<string, string[]>();
|
|
585
654
|
|
|
586
655
|
for (const route of routes) {
|
|
587
656
|
const files = routeMap.get(route.routePath) ?? [];
|
|
@@ -594,7 +663,7 @@ function collectDuplicateRoutePaths(routes) {
|
|
|
594
663
|
.map(([path, files]) => ({ files, path }));
|
|
595
664
|
}
|
|
596
665
|
|
|
597
|
-
function resolveApiRoutePath(apiDir, file) {
|
|
666
|
+
function resolveApiRoutePath(apiDir: string, file: string): string {
|
|
598
667
|
let relativePath = relative(apiDir, file).replace(/\\/g, "/");
|
|
599
668
|
relativePath = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
600
669
|
|
|
@@ -609,7 +678,7 @@ function resolveApiRoutePath(apiDir, file) {
|
|
|
609
678
|
return normalizeRoutePath(relativePath ? `/api/${relativePath}` : "/api");
|
|
610
679
|
}
|
|
611
680
|
|
|
612
|
-
function toModuleSpecifier(fromDir, filePath) {
|
|
681
|
+
function toModuleSpecifier(fromDir: string, filePath: string): string {
|
|
613
682
|
const relativePath = relative(fromDir, filePath).replace(/\\/g, "/");
|
|
614
683
|
if (relativePath.startsWith(".")) {
|
|
615
684
|
return relativePath;
|
|
@@ -617,7 +686,7 @@ function toModuleSpecifier(fromDir, filePath) {
|
|
|
617
686
|
return `./${relativePath}`;
|
|
618
687
|
}
|
|
619
688
|
|
|
620
|
-
function normalizeRoutePath(path) {
|
|
689
|
+
function normalizeRoutePath(path: string): string {
|
|
621
690
|
if (!path || path === "/") {
|
|
622
691
|
return "/";
|
|
623
692
|
}
|
|
@@ -627,15 +696,15 @@ function normalizeRoutePath(path) {
|
|
|
627
696
|
return collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
|
|
628
697
|
}
|
|
629
698
|
|
|
630
|
-
function isWithinDirectory(filePath, directoryPath) {
|
|
699
|
+
function isWithinDirectory(filePath: string, directoryPath: string): boolean {
|
|
631
700
|
const relativePath = relative(directoryPath, filePath);
|
|
632
701
|
return relativePath === "" || (!relativePath.startsWith("..") && !relativePath.startsWith("../"));
|
|
633
702
|
}
|
|
634
703
|
|
|
635
|
-
function normalizePath(value) {
|
|
704
|
+
function normalizePath(value: string): string {
|
|
636
705
|
return value.replace(/\\/g, "/");
|
|
637
706
|
}
|
|
638
707
|
|
|
639
|
-
function createCheck(status, message) {
|
|
708
|
+
function createCheck(status: Check["status"], message: string): Check {
|
|
640
709
|
return { message, status };
|
|
641
710
|
}
|
package/tsdown.config.ts
ADDED
package/lib/build-metadata.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
const MANIFEST_PATHS = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"];
|
|
5
|
-
|
|
6
|
-
export function readClientBuildAssets(root = process.cwd()) {
|
|
7
|
-
const manifestPath = MANIFEST_PATHS.map((candidate) => resolve(root, candidate)).find((path) =>
|
|
8
|
-
existsSync(path),
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
if (!manifestPath) {
|
|
12
|
-
return {
|
|
13
|
-
clientEntryUrl: null,
|
|
14
|
-
cssManifest: {},
|
|
15
|
-
jsManifest: {},
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const rawManifest = readFileSync(manifestPath, "utf-8");
|
|
20
|
-
const manifest = JSON.parse(rawManifest);
|
|
21
|
-
const clientEntry = manifest["virtual:pracht/client"];
|
|
22
|
-
|
|
23
|
-
function collectTransitiveDeps(key) {
|
|
24
|
-
const css = new Set();
|
|
25
|
-
const js = new Set();
|
|
26
|
-
const visited = new Set();
|
|
27
|
-
|
|
28
|
-
function collect(currentKey) {
|
|
29
|
-
if (visited.has(currentKey)) return;
|
|
30
|
-
visited.add(currentKey);
|
|
31
|
-
|
|
32
|
-
const entry = manifest[currentKey];
|
|
33
|
-
if (!entry) return;
|
|
34
|
-
|
|
35
|
-
for (const cssFile of entry.css ?? []) {
|
|
36
|
-
css.add(cssFile);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
js.add(entry.file);
|
|
40
|
-
|
|
41
|
-
for (const importedKey of entry.imports ?? []) {
|
|
42
|
-
collect(importedKey);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
collect(key);
|
|
47
|
-
return {
|
|
48
|
-
css: [...css],
|
|
49
|
-
js: [...js],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const cssManifest = {};
|
|
54
|
-
const jsManifest = {};
|
|
55
|
-
|
|
56
|
-
for (const [key, entry] of Object.entries(manifest)) {
|
|
57
|
-
if (!entry.src) continue;
|
|
58
|
-
|
|
59
|
-
const deps = collectTransitiveDeps(key);
|
|
60
|
-
if (deps.css.length > 0) {
|
|
61
|
-
cssManifest[key] = deps.css.map((file) => `/${file}`);
|
|
62
|
-
}
|
|
63
|
-
if (deps.js.length > 0) {
|
|
64
|
-
jsManifest[key] = deps.js.map((file) => `/${file}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
|
|
70
|
-
cssManifest,
|
|
71
|
-
jsManifest,
|
|
72
|
-
};
|
|
73
|
-
}
|
package/lib/cli.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { HTTP_METHODS, VERSION } from "./constants.js";
|
|
2
|
-
|
|
3
|
-
export function printHelp() {
|
|
4
|
-
console.log(`pracht ${VERSION}
|
|
5
|
-
|
|
6
|
-
Usage:
|
|
7
|
-
pracht dev [port] Start development server with HMR
|
|
8
|
-
pracht build Production build (client + server)
|
|
9
|
-
pracht generate <kind> [flags] Scaffold framework files
|
|
10
|
-
pracht verify [--changed] [--json] Fast framework-aware verification
|
|
11
|
-
pracht inspect [target] [--json] Inspect resolved app graph
|
|
12
|
-
pracht doctor [--json] Validate app wiring
|
|
13
|
-
|
|
14
|
-
Generate kinds:
|
|
15
|
-
route --path /dashboard [--render ssr|spa|ssg|isg] [--shell app] [--middleware auth] [--loader]
|
|
16
|
-
shell --name app
|
|
17
|
-
middleware --name auth
|
|
18
|
-
api --path /health [--methods GET,POST]
|
|
19
|
-
`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function printInspectHelp() {
|
|
23
|
-
console.log(`Usage:
|
|
24
|
-
pracht inspect [routes|api|build] [--json]
|
|
25
|
-
pracht inspect --json
|
|
26
|
-
`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function printGenerateHelp() {
|
|
30
|
-
console.log(`Usage:
|
|
31
|
-
pracht generate route --path /dashboard [--render ssr|spa|ssg|isg] [--shell app] [--middleware auth] [--loader] [--json]
|
|
32
|
-
pracht generate shell --name app [--json]
|
|
33
|
-
pracht generate middleware --name auth [--json]
|
|
34
|
-
pracht generate api --path /health [--methods GET,POST] [--json]
|
|
35
|
-
`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function parseFlags(args) {
|
|
39
|
-
const options = { _: [] };
|
|
40
|
-
|
|
41
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
42
|
-
const token = args[index];
|
|
43
|
-
if (!token.startsWith("--")) {
|
|
44
|
-
options._.push(token);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (token.startsWith("--no-")) {
|
|
49
|
-
options[token.slice(5)] = false;
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const equalsIndex = token.indexOf("=");
|
|
54
|
-
if (equalsIndex !== -1) {
|
|
55
|
-
const key = token.slice(2, equalsIndex);
|
|
56
|
-
const value = token.slice(equalsIndex + 1);
|
|
57
|
-
assignOption(options, key, value);
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const key = token.slice(2);
|
|
62
|
-
const next = args[index + 1];
|
|
63
|
-
if (next && !next.startsWith("--")) {
|
|
64
|
-
assignOption(options, key, next);
|
|
65
|
-
index += 1;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
assignOption(options, key, true);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return options;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function requireStringOption(options, key) {
|
|
76
|
-
const value = requireOptionalString(options, key);
|
|
77
|
-
if (!value) {
|
|
78
|
-
throw new Error(`Missing required flag --${key}.`);
|
|
79
|
-
}
|
|
80
|
-
return value;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function requireOptionalString(options, key) {
|
|
84
|
-
const value = options[key];
|
|
85
|
-
if (Array.isArray(value)) {
|
|
86
|
-
return String(value[value.length - 1]);
|
|
87
|
-
}
|
|
88
|
-
if (typeof value === "string") {
|
|
89
|
-
return value;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function requireEnumOption(options, key, allowed, fallback) {
|
|
95
|
-
const value = requireOptionalString(options, key) ?? fallback;
|
|
96
|
-
if (!allowed.includes(value)) {
|
|
97
|
-
throw new Error(`Invalid value for --${key}. Expected one of ${allowed.join(", ")}.`);
|
|
98
|
-
}
|
|
99
|
-
return value;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function requirePositiveIntegerOption(options, key, fallback) {
|
|
103
|
-
const raw = requireOptionalString(options, key);
|
|
104
|
-
const value = raw == null ? fallback : Number.parseInt(raw, 10);
|
|
105
|
-
if (!Number.isInteger(value) || value <= 0) {
|
|
106
|
-
throw new Error(`--${key} must be a positive integer.`);
|
|
107
|
-
}
|
|
108
|
-
return value;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function parseCommaList(value) {
|
|
112
|
-
if (!value) return [];
|
|
113
|
-
const values = Array.isArray(value) ? value : [value];
|
|
114
|
-
return values
|
|
115
|
-
.flatMap((entry) => String(entry).split(","))
|
|
116
|
-
.map((entry) => entry.trim())
|
|
117
|
-
.filter(Boolean);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function parseApiMethods(value) {
|
|
121
|
-
const methods = parseCommaList(value);
|
|
122
|
-
const normalized = methods.length === 0 ? ["GET"] : methods.map((entry) => entry.toUpperCase());
|
|
123
|
-
|
|
124
|
-
for (const method of normalized) {
|
|
125
|
-
if (!HTTP_METHODS.has(method)) {
|
|
126
|
-
throw new Error(`Unsupported HTTP method "${method}".`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return [...new Set(normalized)];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function quote(value) {
|
|
134
|
-
return JSON.stringify(value);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function ensureTrailingNewline(value) {
|
|
138
|
-
return value.endsWith("\n") ? value : `${value}\n`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function handleCliError(error, { json }) {
|
|
142
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
-
if (json) {
|
|
144
|
-
console.error(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
145
|
-
} else {
|
|
146
|
-
console.error(message);
|
|
147
|
-
if (error instanceof Error && error.stack && process.env.DEBUG) {
|
|
148
|
-
console.error(error.stack);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function assignOption(options, key, value) {
|
|
155
|
-
if (!(key in options)) {
|
|
156
|
-
options[key] = value;
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (!Array.isArray(options[key])) {
|
|
161
|
-
options[key] = [options[key]];
|
|
162
|
-
}
|
|
163
|
-
options[key].push(value);
|
|
164
|
-
}
|
package/lib/commands/build.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
import { build as viteBuild } from "vite";
|
|
5
|
-
|
|
6
|
-
import { readClientBuildAssets } from "../build-metadata.js";
|
|
7
|
-
import { writeVercelBuildOutput } from "../build-shared.js";
|
|
8
|
-
|
|
9
|
-
export async function buildCommand() {
|
|
10
|
-
const root = process.cwd();
|
|
11
|
-
|
|
12
|
-
console.log("\n Building client...\n");
|
|
13
|
-
await viteBuild({
|
|
14
|
-
root,
|
|
15
|
-
build: {
|
|
16
|
-
outDir: "dist",
|
|
17
|
-
manifest: true,
|
|
18
|
-
rollupOptions: {
|
|
19
|
-
input: "virtual:pracht/client",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
console.log("\n Building server...\n");
|
|
25
|
-
await viteBuild({
|
|
26
|
-
root,
|
|
27
|
-
build: {
|
|
28
|
-
outDir: "dist/server",
|
|
29
|
-
ssr: "virtual:pracht/server",
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const serverEntry = resolve(root, "dist/server/server.js");
|
|
34
|
-
let clientDir;
|
|
35
|
-
if (existsSync(resolve(root, "dist/client/.vite/manifest.json"))) {
|
|
36
|
-
clientDir = resolve(root, "dist/client");
|
|
37
|
-
} else {
|
|
38
|
-
clientDir = resolve(root, "dist/client");
|
|
39
|
-
const distRoot = resolve(root, "dist");
|
|
40
|
-
mkdirSync(clientDir, { recursive: true });
|
|
41
|
-
for (const entry of readdirSync(distRoot)) {
|
|
42
|
-
if (entry === "server" || entry === "client") continue;
|
|
43
|
-
const sourcePath = join(distRoot, entry);
|
|
44
|
-
const destinationPath = join(clientDir, entry);
|
|
45
|
-
cpSync(sourcePath, destinationPath, { recursive: true });
|
|
46
|
-
rmSync(sourcePath, { force: true, recursive: true });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (existsSync(serverEntry)) {
|
|
51
|
-
const serverMod = await import(serverEntry);
|
|
52
|
-
const { prerenderApp } = serverMod;
|
|
53
|
-
const { clientEntryUrl, cssManifest, jsManifest } = readClientBuildAssets(root);
|
|
54
|
-
|
|
55
|
-
const { pages, isgManifest } = await prerenderApp({
|
|
56
|
-
app: serverMod.resolvedApp,
|
|
57
|
-
clientEntryUrl: clientEntryUrl ?? undefined,
|
|
58
|
-
cssManifest,
|
|
59
|
-
jsManifest,
|
|
60
|
-
registry: serverMod.registry,
|
|
61
|
-
withISGManifest: true,
|
|
62
|
-
});
|
|
63
|
-
const headersManifest = Object.fromEntries(
|
|
64
|
-
pages.map((page) => [page.path, page.headers ?? {}]),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
if (pages.length > 0) {
|
|
68
|
-
console.log(`\n Prerendering ${pages.length} SSG/ISG route(s)...\n`);
|
|
69
|
-
for (const page of pages) {
|
|
70
|
-
const filePath =
|
|
71
|
-
page.path === "/"
|
|
72
|
-
? join(clientDir, "index.html")
|
|
73
|
-
: join(clientDir, page.path, "index.html");
|
|
74
|
-
|
|
75
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
76
|
-
writeFileSync(filePath, page.html, "utf-8");
|
|
77
|
-
console.log(` ${page.path} → ${filePath.replace(root + "/", "")}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (Object.keys(headersManifest).length > 0) {
|
|
82
|
-
const headersManifestJson = `${JSON.stringify(headersManifest, null, 2)}\n`;
|
|
83
|
-
writeFileSync(
|
|
84
|
-
resolve(root, "dist/server/headers-manifest.json"),
|
|
85
|
-
headersManifestJson,
|
|
86
|
-
"utf-8",
|
|
87
|
-
);
|
|
88
|
-
mkdirSync(resolve(clientDir, "_pracht"), { recursive: true });
|
|
89
|
-
writeFileSync(resolve(clientDir, "_pracht/headers.json"), headersManifestJson, "utf-8");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (Object.keys(isgManifest).length > 0) {
|
|
93
|
-
const isgManifestPath = resolve(root, "dist/server/isg-manifest.json");
|
|
94
|
-
writeFileSync(isgManifestPath, JSON.stringify(isgManifest, null, 2), "utf-8");
|
|
95
|
-
console.log(
|
|
96
|
-
`\n ISG manifest → dist/server/isg-manifest.json (${Object.keys(isgManifest).length} route(s))\n`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (serverMod.buildTarget === "cloudflare") {
|
|
101
|
-
console.log("\n Cloudflare worker → dist/server/server.js\n");
|
|
102
|
-
console.log(" Deploy with: wrangler deploy\n");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (serverMod.buildTarget === "vercel") {
|
|
106
|
-
const outputPath = writeVercelBuildOutput({
|
|
107
|
-
functionName: serverMod.vercelFunctionName,
|
|
108
|
-
isgRoutes: Object.keys(isgManifest),
|
|
109
|
-
headersManifest,
|
|
110
|
-
regions: serverMod.vercelRegions,
|
|
111
|
-
root,
|
|
112
|
-
staticRoutes: pages.map((page) => page.path).filter((path) => !(path in isgManifest)),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
console.log(`\n Vercel build output → ${outputPath}\n`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
console.log("\n Build complete.\n");
|
|
120
|
-
}
|
package/lib/commands/dev.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createServer } from "vite";
|
|
2
|
-
|
|
3
|
-
import { parseFlags } from "../cli.js";
|
|
4
|
-
|
|
5
|
-
export async function devCommand(args) {
|
|
6
|
-
const options = parseFlags(args);
|
|
7
|
-
const port = parseInt(process.env.PORT || options._[0] || "3000", 10);
|
|
8
|
-
|
|
9
|
-
const server = await createServer({
|
|
10
|
-
root: process.cwd(),
|
|
11
|
-
server: { port },
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
await server.listen();
|
|
15
|
-
server.printUrls();
|
|
16
|
-
}
|