@pracht/cli 1.1.4 → 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/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
- }
@@ -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
- }
@@ -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
- }
@@ -1,21 +0,0 @@
1
- import { parseFlags } from "../cli.js";
2
- import { runDoctor } from "../verification.js";
3
-
4
- export async function doctorCommand(args) {
5
- const options = parseFlags(args);
6
- const report = runDoctor(process.cwd());
7
-
8
- if (options.json) {
9
- console.log(JSON.stringify(report, null, 2));
10
- } else {
11
- console.log(`Pracht doctor (${report.mode} mode)`);
12
- for (const check of report.checks) {
13
- console.log(`${check.status.toUpperCase().padEnd(5)} ${check.message}`);
14
- }
15
- console.log(report.ok ? "\nNo blocking issues found." : "\nBlocking issues found.");
16
- }
17
-
18
- if (!report.ok) {
19
- process.exitCode = 1;
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- import { parseFlags } from "../cli.js";
2
- import { runVerification } from "../verification.js";
3
-
4
- export async function verifyCommand(args) {
5
- const options = parseFlags(args);
6
- const report = runVerification(process.cwd(), { changed: Boolean(options.changed) });
7
-
8
- if (options.json) {
9
- console.log(JSON.stringify(report, null, 2));
10
- } else {
11
- console.log(`Pracht verify (${report.mode} mode, ${report.scope} scope)`);
12
- for (const check of report.checks) {
13
- console.log(`${check.status.toUpperCase().padEnd(7)} ${check.message}`);
14
- }
15
- console.log(report.ok ? "\nNo blocking issues found." : "\nBlocking issues found.");
16
- }
17
-
18
- if (!report.ok) {
19
- process.exitCode = 1;
20
- }
21
- }