@pracht/cli 1.5.1 → 1.6.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 +69 -0
- package/README.md +48 -1
- package/dist/build-CGzTcST3.mjs +324 -0
- package/dist/{build-metadata-BOChHO8g.mjs → build-metadata-DyMzBVtV.mjs} +2 -0
- package/dist/bundle-report-DxTtmipa.mjs +218 -0
- package/dist/dev-DwAbdNBf.mjs +173 -0
- package/dist/{doctor-DyOWEA42.mjs → doctor-uALUogtk.mjs} +1 -1
- package/dist/{generate-DTMte0kd.mjs → generate-BczObays.mjs} +13 -3
- package/dist/index.mjs +9 -7
- package/dist/{inspect-WF08uLOl.mjs → inspect-Bxt-6Lmc.mjs} +14 -33
- package/dist/mcp-Dv9EeFtp.mjs +154 -0
- package/dist/prerender-module-hooks.mjs +46 -0
- package/dist/preview-B0-hgVfI.mjs +149 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/{typegen-Bl4fYQIy.mjs → typegen-KYYNjYDt.mjs} +25 -5
- package/dist/{verification-CSj-ngqk.mjs → verification-5w_FfRtE.mjs} +23 -2
- package/dist/{verify-FwVbdlxh.mjs → verify-B_HUuyEQ.mjs} +1 -1
- package/package.json +4 -2
- package/dist/build-B7_6RUBf.mjs +0 -234
- package/dist/dev-BCFe3g38.mjs +0 -25
- /package/dist/{manifest-Bs5hp3gA.mjs → manifest-nU7P84qF.mjs} +0 -0
- /package/dist/{project-voPTS9UC.mjs → project-2EajzTuX.mjs} +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { gzipSync } from "node:zlib";
|
|
4
|
+
//#region src/bundle-report.ts
|
|
5
|
+
const SIZE_UNITS = {
|
|
6
|
+
b: 1,
|
|
7
|
+
kb: 1024,
|
|
8
|
+
mb: 1024 * 1024,
|
|
9
|
+
gb: 1024 * 1024 * 1024
|
|
10
|
+
};
|
|
11
|
+
const SIZE_RE = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/;
|
|
12
|
+
/**
|
|
13
|
+
* Parse a size budget value into bytes. Accepts plain numbers (bytes) or
|
|
14
|
+
* size strings like "120kb" / "1mb" (1kb = 1024 bytes).
|
|
15
|
+
*/
|
|
16
|
+
function parseSizeToBytes(value) {
|
|
17
|
+
if (typeof value === "number") {
|
|
18
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`Invalid size ${JSON.stringify(value)}: expected a positive number of bytes.`);
|
|
19
|
+
return Math.floor(value);
|
|
20
|
+
}
|
|
21
|
+
const match = SIZE_RE.exec(value.trim().toLowerCase());
|
|
22
|
+
if (!match) throw new Error(`Invalid size ${JSON.stringify(value)}: expected a byte count or a size string like "120kb" or "1mb".`);
|
|
23
|
+
const amount = Number.parseFloat(match[1]);
|
|
24
|
+
const bytes = Math.round(amount * SIZE_UNITS[match[2] ?? "b"]);
|
|
25
|
+
if (bytes <= 0) throw new Error(`Invalid size ${JSON.stringify(value)}: expected a positive size.`);
|
|
26
|
+
return bytes;
|
|
27
|
+
}
|
|
28
|
+
function formatBytes(bytes) {
|
|
29
|
+
if (bytes < 1024) return `${bytes}b`;
|
|
30
|
+
const kb = bytes / 1024;
|
|
31
|
+
if (kb < 1024) return `${kb.toFixed(1)}kb`;
|
|
32
|
+
return `${(kb / 1024).toFixed(2)}mb`;
|
|
33
|
+
}
|
|
34
|
+
/** Strip leading `./` and `/` so module paths share one canonical form. */
|
|
35
|
+
function normalizeModulePath(path) {
|
|
36
|
+
return path.replace(/^\.?\//, "");
|
|
37
|
+
}
|
|
38
|
+
function buildSuffixIndex(manifest) {
|
|
39
|
+
const index = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const key of Object.keys(manifest)) {
|
|
41
|
+
const normalized = normalizeModulePath(key);
|
|
42
|
+
if (!normalized) continue;
|
|
43
|
+
if (!index.has(normalized)) index.set(normalized, key);
|
|
44
|
+
for (let i = normalized.indexOf("/"); i !== -1; i = normalized.indexOf("/", i + 1)) {
|
|
45
|
+
const suffix = normalized.slice(i + 1);
|
|
46
|
+
if (suffix && !index.has(suffix)) index.set(suffix, key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return index;
|
|
50
|
+
}
|
|
51
|
+
function resolveManifestEntries(manifest, suffixIndex, file) {
|
|
52
|
+
if (file in manifest) return manifest[file];
|
|
53
|
+
const resolved = suffixIndex.get(normalizeModulePath(file));
|
|
54
|
+
return resolved ? manifest[resolved] : [];
|
|
55
|
+
}
|
|
56
|
+
function collectBundleReport({ routes, jsManifest, clientEntryJs, clientDir }) {
|
|
57
|
+
const suffixIndex = buildSuffixIndex(jsManifest);
|
|
58
|
+
const chunkCache = /* @__PURE__ */ new Map();
|
|
59
|
+
function measureChunk(url) {
|
|
60
|
+
const cached = chunkCache.get(url);
|
|
61
|
+
if (cached) return cached;
|
|
62
|
+
const filePath = join(clientDir, url.replace(/^\//, ""));
|
|
63
|
+
let bytes = 0;
|
|
64
|
+
let gzipBytes = 0;
|
|
65
|
+
if (existsSync(filePath)) {
|
|
66
|
+
const contents = readFileSync(filePath);
|
|
67
|
+
bytes = contents.byteLength;
|
|
68
|
+
gzipBytes = gzipSync(contents).byteLength;
|
|
69
|
+
}
|
|
70
|
+
const chunk = {
|
|
71
|
+
url,
|
|
72
|
+
bytes,
|
|
73
|
+
gzipBytes
|
|
74
|
+
};
|
|
75
|
+
chunkCache.set(url, chunk);
|
|
76
|
+
return chunk;
|
|
77
|
+
}
|
|
78
|
+
const sharedUrls = new Set(clientEntryJs);
|
|
79
|
+
const sharedChunks = clientEntryJs.map(measureChunk);
|
|
80
|
+
const sharedBytes = sumBytes(sharedChunks);
|
|
81
|
+
const sharedGzipBytes = sumGzipBytes(sharedChunks);
|
|
82
|
+
const reportRoutes = routes.map((route) => {
|
|
83
|
+
const urls = /* @__PURE__ */ new Set();
|
|
84
|
+
if (route.shellFile) for (const url of resolveManifestEntries(jsManifest, suffixIndex, route.shellFile)) urls.add(url);
|
|
85
|
+
for (const url of resolveManifestEntries(jsManifest, suffixIndex, route.file)) urls.add(url);
|
|
86
|
+
const chunks = [...urls].filter((url) => !sharedUrls.has(url)).map(measureChunk).sort((left, right) => right.gzipBytes - left.gzipBytes);
|
|
87
|
+
const routeBytes = sumBytes(chunks);
|
|
88
|
+
const routeGzipBytes = sumGzipBytes(chunks);
|
|
89
|
+
return {
|
|
90
|
+
...route.id ? { id: route.id } : {},
|
|
91
|
+
path: route.path,
|
|
92
|
+
render: route.render ?? "ssr",
|
|
93
|
+
chunks,
|
|
94
|
+
routeBytes,
|
|
95
|
+
routeGzipBytes,
|
|
96
|
+
totalBytes: routeBytes + sharedBytes,
|
|
97
|
+
totalGzipBytes: routeGzipBytes + sharedGzipBytes
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
reportRoutes.sort((left, right) => right.totalGzipBytes - left.totalGzipBytes || left.path.localeCompare(right.path));
|
|
101
|
+
return {
|
|
102
|
+
shared: {
|
|
103
|
+
chunks: [...sharedChunks].sort((left, right) => right.gzipBytes - left.gzipBytes),
|
|
104
|
+
bytes: sharedBytes,
|
|
105
|
+
gzipBytes: sharedGzipBytes
|
|
106
|
+
},
|
|
107
|
+
routes: reportRoutes
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function sumBytes(chunks) {
|
|
111
|
+
return chunks.reduce((total, chunk) => total + chunk.bytes, 0);
|
|
112
|
+
}
|
|
113
|
+
function sumGzipBytes(chunks) {
|
|
114
|
+
return chunks.reduce((total, chunk) => total + chunk.gzipBytes, 0);
|
|
115
|
+
}
|
|
116
|
+
function evaluateBudgets(report, budgets) {
|
|
117
|
+
const defaultBudget = budgets["*"];
|
|
118
|
+
const explicitKeys = Object.keys(budgets).filter((key) => key !== "*");
|
|
119
|
+
const routePaths = new Set(report.routes.map((route) => route.path));
|
|
120
|
+
const unmatched = explicitKeys.filter((key) => !routePaths.has(key));
|
|
121
|
+
const results = [];
|
|
122
|
+
for (const route of report.routes) {
|
|
123
|
+
const source = route.path in budgets ? route.path : defaultBudget != null ? "*" : null;
|
|
124
|
+
if (source == null) continue;
|
|
125
|
+
const budget = budgets[source];
|
|
126
|
+
const limitBytes = parseSizeToBytes(budget);
|
|
127
|
+
results.push({
|
|
128
|
+
path: route.path,
|
|
129
|
+
render: route.render,
|
|
130
|
+
budget,
|
|
131
|
+
source,
|
|
132
|
+
limitBytes,
|
|
133
|
+
gzipBytes: route.totalGzipBytes,
|
|
134
|
+
ok: route.totalGzipBytes <= limitBytes
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
results,
|
|
139
|
+
unmatched,
|
|
140
|
+
ok: results.every((result) => result.ok)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function shouldUseColor() {
|
|
144
|
+
if (process.env.NO_COLOR) return false;
|
|
145
|
+
return Boolean(process.stdout.isTTY);
|
|
146
|
+
}
|
|
147
|
+
function paint(text, code, color) {
|
|
148
|
+
return color ? `\u001b[${code}m${text}\u001b[0m` : text;
|
|
149
|
+
}
|
|
150
|
+
function formatBundleReport(report, options = {}) {
|
|
151
|
+
const color = options.color ?? false;
|
|
152
|
+
const rows = [];
|
|
153
|
+
for (const route of report.routes) {
|
|
154
|
+
rows.push({
|
|
155
|
+
label: `${route.path} (${route.render})`,
|
|
156
|
+
raw: "",
|
|
157
|
+
gzip: "",
|
|
158
|
+
kind: "header"
|
|
159
|
+
});
|
|
160
|
+
for (const chunk of route.chunks) rows.push({
|
|
161
|
+
label: ` ${chunk.url}`,
|
|
162
|
+
raw: formatBytes(chunk.bytes),
|
|
163
|
+
gzip: formatBytes(chunk.gzipBytes),
|
|
164
|
+
kind: "chunk"
|
|
165
|
+
});
|
|
166
|
+
rows.push({
|
|
167
|
+
label: " total (incl. shared)",
|
|
168
|
+
raw: formatBytes(route.totalBytes),
|
|
169
|
+
gzip: formatBytes(route.totalGzipBytes),
|
|
170
|
+
kind: "total"
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
rows.push({
|
|
174
|
+
label: "shared entry (all routes)",
|
|
175
|
+
raw: "",
|
|
176
|
+
gzip: "",
|
|
177
|
+
kind: "header"
|
|
178
|
+
});
|
|
179
|
+
for (const chunk of report.shared.chunks) rows.push({
|
|
180
|
+
label: ` ${chunk.url}`,
|
|
181
|
+
raw: formatBytes(chunk.bytes),
|
|
182
|
+
gzip: formatBytes(chunk.gzipBytes),
|
|
183
|
+
kind: "chunk"
|
|
184
|
+
});
|
|
185
|
+
rows.push({
|
|
186
|
+
label: " total",
|
|
187
|
+
raw: formatBytes(report.shared.bytes),
|
|
188
|
+
gzip: formatBytes(report.shared.gzipBytes),
|
|
189
|
+
kind: "total"
|
|
190
|
+
});
|
|
191
|
+
const labelWidth = Math.max(13, ...rows.map((row) => row.label.length));
|
|
192
|
+
const gzipWidth = Math.max(4, ...rows.map((row) => row.gzip.length));
|
|
193
|
+
const rawWidth = Math.max(3, ...rows.map((row) => row.raw.length));
|
|
194
|
+
const lines = [];
|
|
195
|
+
lines.push(paint(`${"Route / chunk".padEnd(labelWidth)} ${"Gzip".padStart(gzipWidth)} ${"Raw".padStart(rawWidth)}`, "1", color));
|
|
196
|
+
for (const row of rows) {
|
|
197
|
+
const line = `${row.label.padEnd(labelWidth)} ${row.gzip.padStart(gzipWidth)} ${row.raw.padStart(rawWidth)}`;
|
|
198
|
+
if (row.kind === "header") lines.push(paint(line.trimEnd(), "1", color));
|
|
199
|
+
else if (row.kind === "total") lines.push(paint(line, "36", color));
|
|
200
|
+
else lines.push(paint(line, "2", color));
|
|
201
|
+
}
|
|
202
|
+
return lines.join("\n");
|
|
203
|
+
}
|
|
204
|
+
function formatBudgetResults(evaluation, options = {}) {
|
|
205
|
+
const color = options.color ?? false;
|
|
206
|
+
const lines = [paint("Budgets (gzip client JS)", "1", color)];
|
|
207
|
+
const pathWidth = Math.max(...evaluation.results.map((result) => result.path.length), 0);
|
|
208
|
+
for (const result of evaluation.results) {
|
|
209
|
+
const status = result.ok ? paint("PASS", "32", color) : paint("FAIL", "31", color);
|
|
210
|
+
const comparison = result.ok ? "<=" : ">";
|
|
211
|
+
const suffix = result.source === "*" ? " (*)" : "";
|
|
212
|
+
lines.push(`${status} ${result.path.padEnd(pathWidth)} ${formatBytes(result.gzipBytes)} ${comparison} ${formatBytes(result.limitBytes)}${suffix}`);
|
|
213
|
+
}
|
|
214
|
+
for (const key of evaluation.unmatched) lines.push(paint(`WARN budget for ${JSON.stringify(key)} does not match any route.`, "33", color));
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
export { formatBytes as a, formatBundleReport as i, evaluateBudgets as n, shouldUseColor as o, formatBudgetResults as r, collectBundleReport as t };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { t as HTTP_METHODS } from "./index.mjs";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { createServer } from "vite";
|
|
6
|
+
//#region src/app-graph.ts
|
|
7
|
+
const METHOD_ORDER = [...HTTP_METHODS];
|
|
8
|
+
/**
|
|
9
|
+
* Load the resolved app graph (page routes + API routes) from a running Vite
|
|
10
|
+
* dev server. Shared by `pracht inspect` and the `pracht dev` startup banner.
|
|
11
|
+
*/
|
|
12
|
+
async function collectAppGraph(server, root, options = {}) {
|
|
13
|
+
const serverModule = await server.ssrLoadModule("virtual:pracht/server");
|
|
14
|
+
return {
|
|
15
|
+
api: await collectApiRoutes(server, root, serverModule.apiRoutes, options),
|
|
16
|
+
routes: serializeResolvedRoutes(serverModule.resolvedApp.routes)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function serializeResolvedRoutes(routes) {
|
|
20
|
+
return routes.map((route) => ({
|
|
21
|
+
file: route.file,
|
|
22
|
+
id: route.id,
|
|
23
|
+
loaderFile: route.loaderFile ?? null,
|
|
24
|
+
middleware: route.middleware,
|
|
25
|
+
path: route.path,
|
|
26
|
+
render: route.render ?? null,
|
|
27
|
+
revalidate: route.revalidate ?? null,
|
|
28
|
+
shell: route.shell ?? null,
|
|
29
|
+
shellFile: route.shellFile ?? null
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
async function collectApiRoutes(server, root, apiRoutes, options = {}) {
|
|
33
|
+
return Promise.all(apiRoutes.map(async (route) => ({
|
|
34
|
+
file: route.file,
|
|
35
|
+
methods: await detectApiMethods(server, root, route.file, options),
|
|
36
|
+
path: route.path
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
39
|
+
async function detectApiMethods(server, root, file, options) {
|
|
40
|
+
const source = readFileSync(resolve(root, `.${file}`), "utf-8");
|
|
41
|
+
if (options.executeApiModules) try {
|
|
42
|
+
const module = await server.ssrLoadModule(file);
|
|
43
|
+
return METHOD_ORDER.filter((method) => typeof module[method] === "function");
|
|
44
|
+
} catch {}
|
|
45
|
+
return METHOD_ORDER.filter((method) => new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+${method}\\b`).test(source));
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/dev-banner.ts
|
|
49
|
+
const ANSI = {
|
|
50
|
+
bold: "1",
|
|
51
|
+
cyan: "36",
|
|
52
|
+
dim: "2",
|
|
53
|
+
green: "32",
|
|
54
|
+
magenta: "35",
|
|
55
|
+
yellow: "33"
|
|
56
|
+
};
|
|
57
|
+
const MODE_COLORS = {
|
|
58
|
+
isg: ANSI.cyan,
|
|
59
|
+
spa: ANSI.magenta,
|
|
60
|
+
ssg: ANSI.green,
|
|
61
|
+
ssr: ANSI.yellow
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Format the `pracht dev` startup banner: local URL(s) plus an aligned table
|
|
65
|
+
* of page routes (pattern, render mode, shell, middleware) and API routes.
|
|
66
|
+
*/
|
|
67
|
+
function formatDevBanner(options) {
|
|
68
|
+
const { apiRoutes, color = false, localUrls, networkUrls = [], routes } = options;
|
|
69
|
+
const paint = (text, code) => color ? `\u001b[${code}m${text}\u001b[0m` : text;
|
|
70
|
+
const lines = [];
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(` ${paint("pracht dev", ANSI.bold)}`);
|
|
73
|
+
lines.push("");
|
|
74
|
+
for (const url of localUrls) lines.push(` ${paint("➜", ANSI.green)} Local: ${paint(url, `${ANSI.bold};${ANSI.cyan}`)}`);
|
|
75
|
+
for (const url of networkUrls) lines.push(` ${paint("➜", ANSI.green)} Network: ${paint(url, ANSI.cyan)}`);
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push(` ${paint(`Routes (${routes.length})`, ANSI.bold)}`);
|
|
78
|
+
if (routes.length === 0) lines.push(" (none)");
|
|
79
|
+
else {
|
|
80
|
+
const rows = routes.map((route) => [
|
|
81
|
+
route.path,
|
|
82
|
+
route.render ?? "ssr",
|
|
83
|
+
route.shell ?? "-",
|
|
84
|
+
route.middleware.length > 0 ? route.middleware.join(", ") : "-"
|
|
85
|
+
]);
|
|
86
|
+
const header = [
|
|
87
|
+
"ROUTE",
|
|
88
|
+
"MODE",
|
|
89
|
+
"SHELL",
|
|
90
|
+
"MIDDLEWARE"
|
|
91
|
+
];
|
|
92
|
+
const widths = columnWidths([header, ...rows]);
|
|
93
|
+
lines.push(` ${paint(formatRow(header, widths), ANSI.dim)}`);
|
|
94
|
+
for (const row of rows) {
|
|
95
|
+
const [path, mode, shell, middleware] = row;
|
|
96
|
+
const cells = [
|
|
97
|
+
path.padEnd(widths[0]),
|
|
98
|
+
paint(mode.padEnd(widths[1]), MODE_COLORS[mode] ?? ANSI.dim),
|
|
99
|
+
shell.padEnd(widths[2]),
|
|
100
|
+
middleware
|
|
101
|
+
];
|
|
102
|
+
lines.push(` ${cells.join(" ")}`.trimEnd());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push(` ${paint(`API (${apiRoutes.length})`, ANSI.bold)}`);
|
|
107
|
+
if (apiRoutes.length === 0) lines.push(" (none)");
|
|
108
|
+
else {
|
|
109
|
+
const rows = apiRoutes.map((route) => [route.path, route.methods.length > 0 ? route.methods.join(", ") : "-"]);
|
|
110
|
+
const header = ["ROUTE", "METHODS"];
|
|
111
|
+
const widths = columnWidths([header, ...rows]);
|
|
112
|
+
lines.push(` ${paint(formatRow(header, widths), ANSI.dim)}`);
|
|
113
|
+
for (const row of rows) lines.push(` ${formatRow(row, widths)}`);
|
|
114
|
+
}
|
|
115
|
+
lines.push("");
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
/** Respect NO_COLOR (https://no-color.org) and only color TTY output. */
|
|
119
|
+
function supportsColor(env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
|
|
120
|
+
if (env.NO_COLOR) return false;
|
|
121
|
+
if (env.FORCE_COLOR) return true;
|
|
122
|
+
return isTTY;
|
|
123
|
+
}
|
|
124
|
+
function columnWidths(rows) {
|
|
125
|
+
const widths = [];
|
|
126
|
+
for (const row of rows) row.forEach((cell, index) => {
|
|
127
|
+
widths[index] = Math.max(widths[index] ?? 0, cell.length);
|
|
128
|
+
});
|
|
129
|
+
return widths;
|
|
130
|
+
}
|
|
131
|
+
function formatRow(cells, widths) {
|
|
132
|
+
return cells.map((cell, index) => index === cells.length - 1 ? cell : cell.padEnd(widths[index])).join(" ").trimEnd();
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/commands/dev.ts
|
|
136
|
+
var dev_default = defineCommand({
|
|
137
|
+
meta: {
|
|
138
|
+
name: "dev",
|
|
139
|
+
description: "Start development server with HMR"
|
|
140
|
+
},
|
|
141
|
+
args: { port: {
|
|
142
|
+
type: "positional",
|
|
143
|
+
description: "Port number",
|
|
144
|
+
required: false
|
|
145
|
+
} },
|
|
146
|
+
async run({ args }) {
|
|
147
|
+
const port = parseInt(process.env.PORT || args.port || "3000", 10);
|
|
148
|
+
const root = process.cwd();
|
|
149
|
+
const server = await createServer({
|
|
150
|
+
root,
|
|
151
|
+
server: { port }
|
|
152
|
+
});
|
|
153
|
+
await server.listen();
|
|
154
|
+
try {
|
|
155
|
+
const graph = await collectAppGraph(server, root);
|
|
156
|
+
const urls = server.resolvedUrls ?? {
|
|
157
|
+
local: [],
|
|
158
|
+
network: []
|
|
159
|
+
};
|
|
160
|
+
console.log(formatDevBanner({
|
|
161
|
+
apiRoutes: graph.api,
|
|
162
|
+
color: supportsColor(),
|
|
163
|
+
localUrls: urls.local,
|
|
164
|
+
networkUrls: urls.network,
|
|
165
|
+
routes: graph.routes
|
|
166
|
+
}));
|
|
167
|
+
} catch {
|
|
168
|
+
server.printUrls();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
//#endregion
|
|
173
|
+
export { dev_default as default };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import { _ as requireEnum, a as readProjectConfig, c as resolveProjectPath, d as writeGeneratedFile, f as ensureTrailingNewline, g as quote, h as parseCommaList, l as resolveRouteModulePath, m as parseApiMethods, n as displayPath, o as resolveApiModulePath, s as resolvePagesRouteModulePath, t as assertFileExists, u as resolveScopedFile, v as requirePositiveInteger } from "./project-2EajzTuX.mjs";
|
|
3
|
+
import { a as toManifestModulePath, i as insertArrayItem, n as extractRegistryEntries, o as upsertObjectEntry, t as ensureCoreNamedImport } from "./manifest-nU7P84qF.mjs";
|
|
3
4
|
import { defineCommand } from "citty";
|
|
4
5
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
6
|
//#region src/commands/generate-paths.ts
|
|
@@ -141,6 +142,15 @@ function buildStaticPathsStub(params) {
|
|
|
141
142
|
function escapeJsxText(value) {
|
|
142
143
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
143
144
|
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/commands/generate.ts
|
|
147
|
+
var generate_exports = /* @__PURE__ */ __exportAll({
|
|
148
|
+
default: () => generate_default,
|
|
149
|
+
generateApi: () => generateApi,
|
|
150
|
+
generateMiddleware: () => generateMiddleware,
|
|
151
|
+
generateRoute: () => generateRoute,
|
|
152
|
+
generateShell: () => generateShell
|
|
153
|
+
});
|
|
144
154
|
var generate_default = defineCommand({
|
|
145
155
|
meta: {
|
|
146
156
|
name: "generate",
|
|
@@ -400,4 +410,4 @@ function generateApi(args, project) {
|
|
|
400
410
|
};
|
|
401
411
|
}
|
|
402
412
|
//#endregion
|
|
403
|
-
export {
|
|
413
|
+
export { generate_exports as a, generateShell as i, generateMiddleware as n, generateRoute as r, generateApi as t };
|
package/dist/index.mjs
CHANGED
|
@@ -41,13 +41,15 @@ runMain(defineCommand({
|
|
|
41
41
|
description: "The pracht CLI"
|
|
42
42
|
},
|
|
43
43
|
subCommands: {
|
|
44
|
-
build: () => import("./build-
|
|
45
|
-
dev: () => import("./dev-
|
|
46
|
-
doctor: () => import("./doctor-
|
|
47
|
-
generate: () => import("./generate-
|
|
48
|
-
inspect: () => import("./inspect-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
build: () => import("./build-CGzTcST3.mjs").then((m) => m.default),
|
|
45
|
+
dev: () => import("./dev-DwAbdNBf.mjs").then((m) => m.default),
|
|
46
|
+
doctor: () => import("./doctor-uALUogtk.mjs").then((m) => m.default),
|
|
47
|
+
generate: () => import("./generate-BczObays.mjs").then((n) => n.a).then((m) => m.default),
|
|
48
|
+
inspect: () => import("./inspect-Bxt-6Lmc.mjs").then((n) => n.t).then((m) => m.default),
|
|
49
|
+
mcp: () => import("./mcp-Dv9EeFtp.mjs").then((m) => m.default),
|
|
50
|
+
preview: () => import("./preview-B0-hgVfI.mjs").then((m) => m.default),
|
|
51
|
+
typegen: () => import("./typegen-KYYNjYDt.mjs").then((m) => m.default),
|
|
52
|
+
verify: () => import("./verify-B_HUuyEQ.mjs").then((m) => m.default)
|
|
51
53
|
}
|
|
52
54
|
}));
|
|
53
55
|
//#endregion
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { t as readClientBuildAssets } from "./build-metadata-
|
|
3
|
-
import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import { t as readClientBuildAssets } from "./build-metadata-DyMzBVtV.mjs";
|
|
3
|
+
import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-2EajzTuX.mjs";
|
|
4
4
|
import { defineCommand } from "citty";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { createServer } from "vite";
|
|
8
|
+
import { serializeApiRoutes, serializeAppRoutes } from "@pracht/core";
|
|
8
9
|
//#region src/commands/inspect.ts
|
|
10
|
+
var inspect_exports = /* @__PURE__ */ __exportAll({
|
|
11
|
+
default: () => inspect_default,
|
|
12
|
+
runInspect: () => runInspect
|
|
13
|
+
});
|
|
9
14
|
const INSPECT_TARGETS = new Set([
|
|
10
15
|
"routes",
|
|
11
16
|
"api",
|
|
12
17
|
"build",
|
|
13
18
|
"all"
|
|
14
19
|
]);
|
|
15
|
-
const METHOD_ORDER = [...HTTP_METHODS];
|
|
16
20
|
var inspect_default = defineCommand({
|
|
17
21
|
meta: {
|
|
18
22
|
name: "inspect",
|
|
@@ -55,12 +59,11 @@ async function runInspect(root, { target = "all" } = {}) {
|
|
|
55
59
|
try {
|
|
56
60
|
const serverModule = await server.ssrLoadModule("virtual:pracht/server");
|
|
57
61
|
const report = { mode: project.mode };
|
|
58
|
-
if (target === "routes" || target === "all") report.routes =
|
|
59
|
-
if (target === "api" || target === "all") report.api = await
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})));
|
|
62
|
+
if (target === "routes" || target === "all") report.routes = serializeAppRoutes(serverModule.resolvedApp.routes);
|
|
63
|
+
if (target === "api" || target === "all") report.api = await serializeApiRoutes(serverModule.apiRoutes, {
|
|
64
|
+
loadModule: (file) => server.ssrLoadModule(file),
|
|
65
|
+
readSource: (file) => readFileSync(resolve(root, `.${file}`), "utf-8")
|
|
66
|
+
});
|
|
64
67
|
if (target === "build" || target === "all") {
|
|
65
68
|
const buildAssets = readClientBuildAssets(root);
|
|
66
69
|
report.build = {
|
|
@@ -75,28 +78,6 @@ async function runInspect(root, { target = "all" } = {}) {
|
|
|
75
78
|
await server.close();
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
|
-
function serializeRoutes(routes) {
|
|
79
|
-
return routes.map((route) => ({
|
|
80
|
-
file: route.file,
|
|
81
|
-
id: route.id,
|
|
82
|
-
loaderFile: route.loaderFile ?? null,
|
|
83
|
-
middleware: route.middleware,
|
|
84
|
-
path: route.path,
|
|
85
|
-
render: route.render ?? null,
|
|
86
|
-
revalidate: route.revalidate ?? null,
|
|
87
|
-
shell: route.shell ?? null,
|
|
88
|
-
shellFile: route.shellFile ?? null
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
async function detectApiMethods(server, root, file) {
|
|
92
|
-
const source = readFileSync(resolve(root, `.${file}`), "utf-8");
|
|
93
|
-
try {
|
|
94
|
-
const module = await server.ssrLoadModule(file);
|
|
95
|
-
return METHOD_ORDER.filter((method) => typeof module[method] === "function");
|
|
96
|
-
} catch {
|
|
97
|
-
return METHOD_ORDER.filter((method) => new RegExp(`export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+${method}\\b`).test(source));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
81
|
function printInspectReport(report) {
|
|
101
82
|
console.log(`Pracht inspect (${report.mode} mode)`);
|
|
102
83
|
if (report.routes) {
|
|
@@ -120,4 +101,4 @@ function printInspectReport(report) {
|
|
|
120
101
|
}
|
|
121
102
|
}
|
|
122
103
|
//#endregion
|
|
123
|
-
export {
|
|
104
|
+
export { runInspect as n, inspect_exports as t };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { r as VERSION } from "./index.mjs";
|
|
2
|
+
import { a as readProjectConfig } from "./project-2EajzTuX.mjs";
|
|
3
|
+
import { n as runVerification, t as runDoctor } from "./verification-5w_FfRtE.mjs";
|
|
4
|
+
import { i as generateShell, n as generateMiddleware, r as generateRoute, t as generateApi } from "./generate-BczObays.mjs";
|
|
5
|
+
import { n as runInspect } from "./inspect-Bxt-6Lmc.mjs";
|
|
6
|
+
import { defineCommand } from "citty";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { format } from "node:util";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
//#region src/mcp-server.ts
|
|
13
|
+
const cwdInput = { cwd: z.string().optional().describe("Absolute path to the pracht app root. Defaults to the server's working directory.") };
|
|
14
|
+
function createPrachtMcpServer() {
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "pracht",
|
|
17
|
+
version: VERSION
|
|
18
|
+
});
|
|
19
|
+
server.registerTool("inspect_routes", {
|
|
20
|
+
description: "Inspect the resolved page-route graph of a pracht app: path, id, render mode, shell, middleware, loader file. Same payload as `pracht inspect routes --json`.",
|
|
21
|
+
inputSchema: { ...cwdInput }
|
|
22
|
+
}, guard(({ cwd }) => runInspect(resolveCwd(cwd), { target: "routes" })));
|
|
23
|
+
server.registerTool("inspect_api", {
|
|
24
|
+
description: "Inspect the resolved API routes of a pracht app: endpoint path, source file, exported HTTP methods. Same payload as `pracht inspect api --json`.",
|
|
25
|
+
inputSchema: { ...cwdInput }
|
|
26
|
+
}, guard(({ cwd }) => runInspect(resolveCwd(cwd), { target: "api" })));
|
|
27
|
+
server.registerTool("inspect_build", {
|
|
28
|
+
description: "Inspect build metadata of a pracht app: adapter target, client entry URL, CSS/JS manifests. Requires a prior `pracht build`. Same payload as `pracht inspect build --json`.",
|
|
29
|
+
inputSchema: { ...cwdInput }
|
|
30
|
+
}, guard(({ cwd }) => runInspect(resolveCwd(cwd), { target: "build" })));
|
|
31
|
+
server.registerTool("doctor", {
|
|
32
|
+
description: "Validate pracht app wiring (config, manifest references, adapter dependency). Same payload as `pracht doctor --json`.",
|
|
33
|
+
inputSchema: { ...cwdInput }
|
|
34
|
+
}, guard(({ cwd }) => runDoctor(resolveCwd(cwd))));
|
|
35
|
+
server.registerTool("verify", {
|
|
36
|
+
description: "Run fast framework-aware verification checks on a pracht app. Same payload as `pracht verify --json`.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
...cwdInput,
|
|
39
|
+
changed: z.boolean().optional().describe("Only check files changed according to git (maps to --changed).")
|
|
40
|
+
}
|
|
41
|
+
}, guard(({ changed, cwd }) => runVerification(resolveCwd(cwd), { changed: Boolean(changed) })));
|
|
42
|
+
server.registerTool("generate_route", {
|
|
43
|
+
description: "Scaffold a pracht route module and wire it into the app (manifest apps update src/routes.ts; pages apps create the page file). Returns the files created and updated.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
...cwdInput,
|
|
46
|
+
path: z.string().describe("Route path, e.g. /dashboard or /blog/:slug"),
|
|
47
|
+
render: z.enum([
|
|
48
|
+
"spa",
|
|
49
|
+
"ssr",
|
|
50
|
+
"ssg",
|
|
51
|
+
"isg"
|
|
52
|
+
]).optional().describe("Render mode (defaults to ssr)."),
|
|
53
|
+
shell: z.string().optional().describe("Registered shell name (manifest apps only)."),
|
|
54
|
+
middleware: z.array(z.string()).optional().describe("Registered middleware names (manifest apps only)."),
|
|
55
|
+
loader: z.boolean().optional().describe("Include a loader export."),
|
|
56
|
+
errorBoundary: z.boolean().optional().describe("Include an error boundary export."),
|
|
57
|
+
staticPaths: z.boolean().optional().describe("Include a getStaticPaths export."),
|
|
58
|
+
title: z.string().optional().describe("Page title used in the head export."),
|
|
59
|
+
revalidate: z.number().int().positive().optional().describe("ISG revalidation window in seconds (isg render mode only).")
|
|
60
|
+
}
|
|
61
|
+
}, guard((input) => {
|
|
62
|
+
const project = readProjectConfig(resolveCwd(input.cwd));
|
|
63
|
+
return generateRoute({
|
|
64
|
+
"error-boundary": input.errorBoundary,
|
|
65
|
+
loader: input.loader,
|
|
66
|
+
middleware: input.middleware?.join(","),
|
|
67
|
+
path: input.path,
|
|
68
|
+
render: input.render,
|
|
69
|
+
revalidate: input.revalidate === void 0 ? void 0 : String(input.revalidate),
|
|
70
|
+
shell: input.shell,
|
|
71
|
+
"static-paths": input.staticPaths,
|
|
72
|
+
title: input.title
|
|
73
|
+
}, project);
|
|
74
|
+
}));
|
|
75
|
+
server.registerTool("generate_shell", {
|
|
76
|
+
description: "Scaffold a pracht shell component and register it in the app manifest (manifest apps only). Returns the files created and updated.",
|
|
77
|
+
inputSchema: {
|
|
78
|
+
...cwdInput,
|
|
79
|
+
name: z.string().describe("Shell name, e.g. app or public")
|
|
80
|
+
}
|
|
81
|
+
}, guard(({ cwd, name }) => {
|
|
82
|
+
return generateShell(name, readProjectConfig(resolveCwd(cwd)));
|
|
83
|
+
}));
|
|
84
|
+
server.registerTool("generate_middleware", {
|
|
85
|
+
description: "Scaffold a pracht middleware function and register it in the app manifest (manifest apps only). Returns the files created and updated.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
...cwdInput,
|
|
88
|
+
name: z.string().describe("Middleware name, e.g. auth")
|
|
89
|
+
}
|
|
90
|
+
}, guard(({ cwd, name }) => {
|
|
91
|
+
return generateMiddleware(name, readProjectConfig(resolveCwd(cwd)));
|
|
92
|
+
}));
|
|
93
|
+
server.registerTool("generate_api", {
|
|
94
|
+
description: "Scaffold a pracht API route with typed HTTP method handlers. Returns the files created and updated.",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
...cwdInput,
|
|
97
|
+
path: z.string().describe("API endpoint path, e.g. /health or /users/:id"),
|
|
98
|
+
methods: z.array(z.string()).optional().describe("HTTP methods to scaffold, e.g. [\"GET\", \"POST\"] (defaults to GET).")
|
|
99
|
+
}
|
|
100
|
+
}, guard(({ cwd, methods, path }) => {
|
|
101
|
+
const project = readProjectConfig(resolveCwd(cwd));
|
|
102
|
+
return generateApi({
|
|
103
|
+
methods: methods?.join(","),
|
|
104
|
+
path
|
|
105
|
+
}, project);
|
|
106
|
+
}));
|
|
107
|
+
return server;
|
|
108
|
+
}
|
|
109
|
+
function resolveCwd(cwd) {
|
|
110
|
+
return resolve(cwd ?? process.cwd());
|
|
111
|
+
}
|
|
112
|
+
function guard(handler) {
|
|
113
|
+
return async (input) => {
|
|
114
|
+
try {
|
|
115
|
+
const result = await handler(input);
|
|
116
|
+
return { content: [{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: JSON.stringify(result, null, 2)
|
|
119
|
+
}] };
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: error instanceof Error ? error.message : String(error)
|
|
125
|
+
}],
|
|
126
|
+
isError: true
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/commands/mcp.ts
|
|
133
|
+
var mcp_default = defineCommand({
|
|
134
|
+
meta: {
|
|
135
|
+
name: "mcp",
|
|
136
|
+
description: "Start a Model Context Protocol server on stdio"
|
|
137
|
+
},
|
|
138
|
+
async run() {
|
|
139
|
+
for (const method of [
|
|
140
|
+
"debug",
|
|
141
|
+
"error",
|
|
142
|
+
"info",
|
|
143
|
+
"log",
|
|
144
|
+
"trace",
|
|
145
|
+
"warn"
|
|
146
|
+
]) console[method] = (...args) => {
|
|
147
|
+
process.stderr.write(`${format(...args)}\n`);
|
|
148
|
+
};
|
|
149
|
+
await createPrachtMcpServer().connect(new StdioServerTransport());
|
|
150
|
+
process.stderr.write("pracht MCP server listening on stdio\n");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
//#endregion
|
|
154
|
+
export { mcp_default as default };
|