@pracht/cli 1.5.0 → 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.
@@ -0,0 +1,46 @@
1
+ //#region src/prerender-module-hooks.ts
2
+ /**
3
+ * Node module hooks that stub `cloudflare:*` platform modules while
4
+ * `pracht build` imports the built server bundle for SSG prerendering.
5
+ *
6
+ * Edge server bundles keep platform-scheme imports external (they only exist
7
+ * inside workerd), so importing the bundle in Node would otherwise fail
8
+ * before the prerender pass can run. Nothing that touches these classes runs
9
+ * during prerendering — SSG only reads the resolved route manifest and
10
+ * renders page components.
11
+ *
12
+ * Registered from the build command via `module.register()`.
13
+ */
14
+ const STUB_PREFIX = "pracht-cloudflare-stub:";
15
+ const STUB_SOURCES = {
16
+ "cloudflare:workers": [
17
+ "export class WorkerEntrypoint {}",
18
+ "export class DurableObject {}",
19
+ "export class WorkflowEntrypoint {}",
20
+ "export class RpcTarget {}",
21
+ "export const env = {};",
22
+ ""
23
+ ].join("\n"),
24
+ "cloudflare:email": "export class EmailMessage {}\n",
25
+ "cloudflare:sockets": "export function connect() { throw new Error(\"cloudflare:sockets is not available during prerendering\"); }\n"
26
+ };
27
+ function resolve(specifier, context, nextResolve) {
28
+ if (specifier.startsWith("cloudflare:")) {
29
+ if (!(specifier in STUB_SOURCES)) throw new Error(`pracht build has no prerender stub for "${specifier}". SSG prerendering imports the server bundle in Node, where Cloudflare platform modules do not exist. Please report this so the stub list can be extended.`);
30
+ return {
31
+ url: `${STUB_PREFIX}${specifier}`,
32
+ shortCircuit: true
33
+ };
34
+ }
35
+ return nextResolve(specifier, context);
36
+ }
37
+ function load(url, context, nextLoad) {
38
+ if (url.startsWith(STUB_PREFIX)) return {
39
+ format: "module",
40
+ source: STUB_SOURCES[url.slice(23)] ?? "",
41
+ shortCircuit: true
42
+ };
43
+ return nextLoad(url, context);
44
+ }
45
+ //#endregion
46
+ export { load, resolve };
@@ -0,0 +1,149 @@
1
+ import { runBuild } from "./build-CGzTcST3.mjs";
2
+ import { a as readProjectConfig, v as requirePositiveInteger } from "./project-2EajzTuX.mjs";
3
+ import { defineCommand } from "citty";
4
+ import { existsSync } from "node:fs";
5
+ import { delimiter, join, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { spawn } from "node:child_process";
8
+ //#region src/commands/preview.ts
9
+ const SERVER_ENTRY = "dist/server/server.js";
10
+ const WRANGLER_CONFIG_FILES = [
11
+ "wrangler.jsonc",
12
+ "wrangler.json",
13
+ "wrangler.toml"
14
+ ];
15
+ const ADAPTER_TARGETS = new Set([
16
+ "cloudflare",
17
+ "node",
18
+ "vercel"
19
+ ]);
20
+ var preview_default = defineCommand({
21
+ meta: {
22
+ name: "preview",
23
+ description: "Build and serve the production build locally"
24
+ },
25
+ args: {
26
+ port: {
27
+ type: "string",
28
+ description: "Port number (defaults to $PORT or 3000)"
29
+ },
30
+ "skip-build": {
31
+ type: "boolean",
32
+ description: "Serve the existing build output without rebuilding"
33
+ }
34
+ },
35
+ async run({ args }) {
36
+ const root = process.cwd();
37
+ const project = readProjectConfig(root);
38
+ if (!project.configFile) throw new Error("Missing vite config. `pracht preview` requires a project with pracht configured.");
39
+ if (!project.hasPrachtPlugin) throw new Error("vite.config does not appear to register the pracht plugin.");
40
+ const skipBuild = Boolean(args["skip-build"]);
41
+ let target = skipBuild ? await readBuildTarget(root) : null;
42
+ target ??= detectAdapterTarget(project);
43
+ if (target === "vercel") {
44
+ printVercelGuidance();
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ const port = requirePositiveInteger(args.port ?? process.env.PORT, "port", 3e3);
49
+ if (!skipBuild) {
50
+ const { buildTarget } = await runBuild(root);
51
+ target = normalizeAdapterTarget(buildTarget) ?? target;
52
+ if (target === "vercel") {
53
+ printVercelGuidance();
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+ }
58
+ const serverEntry = resolve(root, SERVER_ENTRY);
59
+ if (!existsSync(serverEntry)) throw new Error(`Missing ${SERVER_ENTRY}. Run \`pracht build\` first, or drop --skip-build to build automatically.`);
60
+ if (target === "cloudflare") {
61
+ const wranglerBin = resolveWranglerBin(root);
62
+ if (!wranglerBin) throw new Error(["`pracht preview` needs wrangler to serve Cloudflare builds, but it was not found in node_modules or on your PATH.", "Install it with `npm install --save-dev wrangler` (or `pnpm add -D wrangler`) and re-run `pracht preview`."].join("\n"));
63
+ if (!findWranglerConfig(root)) throw new Error(["`pracht preview` needs a wrangler config (wrangler.jsonc, wrangler.json, or wrangler.toml) pointing at the built worker.", "Create one with `\"main\": \"dist/server/worker.js\"` — see docs/ADAPTERS.md for a full example."].join("\n"));
64
+ console.log(`\n Previewing Cloudflare build with wrangler dev on port ${port}...\n`);
65
+ spawnPreviewProcess(wranglerBin, [
66
+ "dev",
67
+ "--port",
68
+ String(port)
69
+ ], { cwd: root });
70
+ return;
71
+ }
72
+ console.log(`\n Previewing production build → http://localhost:${port}\n`);
73
+ spawnPreviewProcess(process.execPath, [serverEntry], {
74
+ cwd: root,
75
+ env: {
76
+ ...process.env,
77
+ PORT: String(port)
78
+ }
79
+ });
80
+ }
81
+ });
82
+ function detectAdapterTarget(project) {
83
+ const source = project.rawConfig;
84
+ if (/\bcloudflareAdapter\s*\(/.test(source) || source.includes("@pracht/adapter-cloudflare")) return "cloudflare";
85
+ if (/\bvercelAdapter\s*\(/.test(source) || source.includes("@pracht/adapter-vercel")) return "vercel";
86
+ return "node";
87
+ }
88
+ function normalizeAdapterTarget(value) {
89
+ return typeof value === "string" && ADAPTER_TARGETS.has(value) ? value : null;
90
+ }
91
+ function resolveWranglerBin(root, env = process.env) {
92
+ const binNames = process.platform === "win32" ? [
93
+ "wrangler.cmd",
94
+ "wrangler.exe",
95
+ "wrangler"
96
+ ] : ["wrangler"];
97
+ const searchDirs = [resolve(root, "node_modules/.bin"), ...(env.PATH ?? "").split(delimiter).filter(Boolean)];
98
+ for (const dir of searchDirs) for (const name of binNames) {
99
+ const candidate = join(dir, name);
100
+ if (existsSync(candidate)) return candidate;
101
+ }
102
+ return null;
103
+ }
104
+ async function readBuildTarget(root) {
105
+ const serverEntry = resolve(root, SERVER_ENTRY);
106
+ if (!existsSync(serverEntry)) return null;
107
+ try {
108
+ return normalizeAdapterTarget((await import(pathToFileURL(serverEntry).href)).buildTarget);
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+ function findWranglerConfig(root) {
114
+ for (const name of WRANGLER_CONFIG_FILES) {
115
+ const candidate = resolve(root, name);
116
+ if (existsSync(candidate)) return candidate;
117
+ }
118
+ return null;
119
+ }
120
+ function printVercelGuidance() {
121
+ console.log([
122
+ "",
123
+ " The Vercel adapter has no faithful local production runtime, so `pracht preview` does not emulate it.",
124
+ "",
125
+ " To exercise the Vercel build output locally, use Vercel's own tooling:",
126
+ "",
127
+ " vercel build # reproduce the production build (.vercel/output) with your project settings",
128
+ " vercel dev # run a local Vercel development environment",
129
+ "",
130
+ " To ship the output of `pracht build`, run: vercel deploy --prebuilt",
131
+ ""
132
+ ].join("\n"));
133
+ }
134
+ function spawnPreviewProcess(command, commandArgs, options) {
135
+ const child = spawn(command, commandArgs, {
136
+ cwd: options.cwd,
137
+ env: options.env ?? process.env,
138
+ stdio: "inherit"
139
+ });
140
+ child.on("close", (code) => {
141
+ process.exitCode = code ?? 0;
142
+ });
143
+ child.on("error", (error) => {
144
+ console.error(`Failed to start preview process: ${error.message}`);
145
+ process.exitCode = 1;
146
+ });
147
+ }
148
+ //#endregion
149
+ export { preview_default as default };
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
@@ -1,5 +1,5 @@
1
- import { f as ensureTrailingNewline, n as displayPath, p as handleCliError } from "./project-voPTS9UC.mjs";
2
- import { runInspect } from "./inspect-WF08uLOl.mjs";
1
+ import { a as readProjectConfig, c as resolveProjectPath, f as ensureTrailingNewline, n as displayPath, p as handleCliError } from "./project-2EajzTuX.mjs";
2
+ import { n as runInspect } from "./inspect-Bxt-6Lmc.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { dirname, isAbsolute, relative, resolve } from "node:path";
@@ -60,11 +60,16 @@ async function runTypegen(options) {
60
60
  const report = await runInspect(options.root, { target: "routes" });
61
61
  const routes = report.routes ?? [];
62
62
  validateRoutes(routes);
63
+ const project = readProjectConfig(options.root);
63
64
  const declarationPath = resolveOutputPath(options.root, options.declarationOut);
64
65
  const runtimePath = resolveOutputPath(options.root, options.runtimeOut);
65
66
  const outputs = [{
66
67
  path: declarationPath,
67
- source: buildDeclarationSource(routes)
68
+ source: buildDeclarationSource(routes, {
69
+ appDir: dirname(resolveProjectPath(options.root, project.appFile)),
70
+ declarationDir: dirname(declarationPath),
71
+ root: options.root
72
+ })
68
73
  }, {
69
74
  path: runtimePath,
70
75
  source: buildRuntimeSource(routes)
@@ -96,11 +101,11 @@ function validateRoutes(routes) {
96
101
  inferRouteParams(route.path);
97
102
  }
98
103
  }
99
- function buildDeclarationSource(routes) {
104
+ function buildDeclarationSource(routes, context) {
100
105
  const lines = [
101
106
  "// Generated by `pracht typegen`. Do not edit manually.",
102
107
  "import \"@pracht/core\";",
103
- "import type { RouteParamInput, SearchParamsInput } from \"@pracht/core\";",
108
+ "import type { RouteLoaderData, RouteParamInput, SearchParamsInput } from \"@pracht/core\";",
104
109
  "",
105
110
  "declare module \"@pracht/core\" {",
106
111
  " interface Register {",
@@ -111,6 +116,7 @@ function buildDeclarationSource(routes) {
111
116
  lines.push(` path: ${JSON.stringify(route.path)};`);
112
117
  lines.push(` params: ${formatParamsType(inferRouteParams(route.path))};`);
113
118
  lines.push(" search: SearchParamsInput;");
119
+ lines.push(` data: ${formatRouteDataType(route, context)};`);
114
120
  lines.push(" };");
115
121
  }
116
122
  lines.push(" };");
@@ -155,6 +161,20 @@ function inferRouteParams(path) {
155
161
  }
156
162
  return params;
157
163
  }
164
+ const IMPORTABLE_MODULE_PATTERN = /\.(ts|tsx|js|jsx)$/;
165
+ function formatRouteDataType(route, context) {
166
+ const routeModule = formatModuleSpecifier(route.file, context);
167
+ const loaderModule = route.loaderFile ? formatModuleSpecifier(route.loaderFile, context) : null;
168
+ if (loaderModule) return routeModule ? `RouteLoaderData<typeof import(${loaderModule}), typeof import(${routeModule})>` : `RouteLoaderData<typeof import(${loaderModule})>`;
169
+ return routeModule ? `RouteLoaderData<typeof import(${routeModule})>` : "unknown";
170
+ }
171
+ function formatModuleSpecifier(file, context) {
172
+ if (!IMPORTABLE_MODULE_PATTERN.test(file)) return null;
173
+ const absolutePath = file.startsWith("/") ? resolveProjectPath(context.root, file) : resolve(context.appDir, file);
174
+ const relativePath = relative(context.declarationDir, absolutePath).replace(/\\/g, "/").replace(IMPORTABLE_MODULE_PATTERN, "");
175
+ const specifier = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
176
+ return JSON.stringify(specifier);
177
+ }
158
178
  function formatParamsType(params) {
159
179
  if (params.length === 0) return "Record<never, never>";
160
180
  return `{ ${params.map((param) => `${JSON.stringify(param)}: RouteParamInput;`).join(" ")} }`;
@@ -1,5 +1,6 @@
1
- import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-voPTS9UC.mjs";
2
- import { n as extractRegistryEntries, r as extractRelativeModulePaths } from "./manifest-Bs5hp3gA.mjs";
1
+ import { a as formatBytes } from "./bundle-report-DxTtmipa.mjs";
2
+ import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-2EajzTuX.mjs";
3
+ import { n as extractRegistryEntries, r as extractRelativeModulePaths } from "./manifest-nU7P84qF.mjs";
3
4
  import { existsSync, readFileSync } from "node:fs";
4
5
  import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
5
6
  import { execFileSync } from "node:child_process";
@@ -230,6 +231,25 @@ function collectApiVerification(project, checks, { changedFiles, scope }) {
230
231
  checks.push(createCheck("ok", `Changed API route ${JSON.stringify(display)} resolves to ${JSON.stringify(resolveApiRoutePath(apiDir, file))}.`));
231
232
  }
232
233
  }
234
+ function collectBudgetChecks(project, checks) {
235
+ const reportPath = resolve(project.root, "dist/server/budget-report.json");
236
+ if (!existsSync(reportPath)) return;
237
+ let report;
238
+ try {
239
+ report = JSON.parse(readFileSync(reportPath, "utf-8"));
240
+ } catch {
241
+ checks.push(createCheck("warning", "dist/server/budget-report.json exists but could not be parsed."));
242
+ return;
243
+ }
244
+ const results = report.results ?? [];
245
+ if (results.length === 0) return;
246
+ const failed = results.filter((result) => !result.ok);
247
+ if (failed.length === 0) {
248
+ checks.push(createCheck("ok", `All ${results.length} route client JS budget${results.length === 1 ? "" : "s"} pass (from the last \`pracht build\`).`));
249
+ return;
250
+ }
251
+ for (const result of failed) checks.push(createCheck("error", `Route ${JSON.stringify(result.path)} exceeds its client JS budget: ${formatBytes(result.gzipBytes)} gzip > ${formatBytes(result.limitBytes)} (from the last \`pracht build\`).`));
252
+ }
233
253
  function collectPackageChecks(project, checks, packageJsonPath) {
234
254
  if (!existsSync(packageJsonPath)) {
235
255
  checks.push(createCheck("warning", "No package.json found in the current app root."));
@@ -365,6 +385,7 @@ function runVerification(root, options = {}) {
365
385
  scope
366
386
  });
367
387
  collectPackageChecks(project, checks, packageJsonPath);
388
+ collectBudgetChecks(project, checks);
368
389
  if (options.changed && frameworkFiles.length === 0 && !changedInfo.warning) checks.push(createCheck("ok", "No changed framework files were detected in the current project scope."));
369
390
  return {
370
391
  checks,
@@ -1,4 +1,4 @@
1
- import { n as runVerification } from "./verification-CSj-ngqk.mjs";
1
+ import { n as runVerification } from "./verification-5w_FfRtE.mjs";
2
2
  import { defineCommand } from "citty";
3
3
  //#region src/commands/verify.ts
4
4
  var verify_default = defineCommand({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pracht/cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI for developing, building, validating, inspecting, and scaffolding Pracht apps.",
5
5
  "keywords": [
6
6
  "pracht",
@@ -38,8 +38,10 @@
38
38
  "provenance": true
39
39
  },
40
40
  "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.29.0",
41
42
  "citty": "^0.1.6",
42
- "@pracht/core": "0.8.0"
43
+ "zod": "^4.4.3",
44
+ "@pracht/core": "0.9.0"
43
45
  },
44
46
  "scripts": {
45
47
  "build": "tsdown"
@@ -1,234 +0,0 @@
1
- import { r as VERSION } from "./index.mjs";
2
- import { t as readClientBuildAssets } from "./build-metadata-BOChHO8g.mjs";
3
- import { defineCommand } from "citty";
4
- import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
5
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
- import { pathToFileURL } from "node:url";
7
- import { build } from "vite";
8
- //#region src/build-shared.ts
9
- const ROUTE_STATE_REQUEST_HEADER = "x-pracht-route-state-request";
10
- function writeVercelBuildOutput({ functionName, headersManifest = {}, isgRoutes, regions, root, staticRoutes }) {
11
- const outputDir = join(root, ".vercel/output");
12
- const staticDir = join(outputDir, "static");
13
- const functionDir = join(outputDir, "functions", `${functionName || "render"}.func`);
14
- rmSync(outputDir, {
15
- force: true,
16
- recursive: true
17
- });
18
- mkdirSync(outputDir, { recursive: true });
19
- cpSync(join(root, "dist/client"), staticDir, { recursive: true });
20
- cpSync(join(root, "dist/server"), functionDir, { recursive: true });
21
- writeFileSync(join(outputDir, "config.json"), `${JSON.stringify(createVercelOutputConfig({
22
- functionName,
23
- headersManifest,
24
- staticRoutes,
25
- isgRoutes
26
- }), null, 2)}\n`, "utf-8");
27
- writeFileSync(join(functionDir, ".vc-config.json"), `${JSON.stringify(createVercelFunctionConfig({ regions }), null, 2)}\n`, "utf-8");
28
- return ".vercel/output";
29
- }
30
- function createVercelOutputConfig({ functionName, headersManifest, staticRoutes, isgRoutes }) {
31
- const target = `/${functionName || "render"}`;
32
- const routes = [{
33
- dest: target,
34
- has: [{
35
- type: "header",
36
- key: ROUTE_STATE_REQUEST_HEADER,
37
- value: "1"
38
- }],
39
- src: "/(.*)"
40
- }, {
41
- dest: target,
42
- has: [{
43
- type: "query",
44
- key: "_data",
45
- value: "1"
46
- }],
47
- src: "/(.*)"
48
- }];
49
- for (const route of sortStaticRoutes(staticRoutes)) routes.push({
50
- dest: routeToStaticHtmlPath(route),
51
- src: routeToRouteExpression(route)
52
- });
53
- for (const route of isgRoutes) routes.push({
54
- dest: target,
55
- src: routeToRouteExpression(route)
56
- });
57
- routes.push({ handle: "filesystem" });
58
- routes.push({
59
- dest: target,
60
- src: "/(.*)"
61
- });
62
- const headers = [{
63
- headers: [
64
- {
65
- key: "permissions-policy",
66
- value: "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
67
- },
68
- {
69
- key: "referrer-policy",
70
- value: "strict-origin-when-cross-origin"
71
- },
72
- {
73
- key: "x-content-type-options",
74
- value: "nosniff"
75
- },
76
- {
77
- key: "x-frame-options",
78
- value: "SAMEORIGIN"
79
- }
80
- ],
81
- source: "/(.*)"
82
- }];
83
- for (const route of sortStaticRoutes(staticRoutes)) {
84
- const routeHeaders = headersManifest[route];
85
- if (!routeHeaders) continue;
86
- headers.push({
87
- headers: Object.entries(routeHeaders).map(([key, value]) => ({
88
- key,
89
- value
90
- })),
91
- source: routeToHeaderSource(route)
92
- });
93
- }
94
- return {
95
- headers,
96
- framework: { version: VERSION },
97
- routes,
98
- version: 3
99
- };
100
- }
101
- function createVercelFunctionConfig({ regions }) {
102
- const config = {
103
- entrypoint: "server.js",
104
- runtime: "edge"
105
- };
106
- if (regions) config.regions = regions;
107
- return config;
108
- }
109
- function sortStaticRoutes(routes) {
110
- return [...new Set(routes)].sort((left, right) => right.length - left.length);
111
- }
112
- function routeToRouteExpression(route) {
113
- if (route === "/") return "^/$";
114
- return `^${escapeRegex(route)}/?$`;
115
- }
116
- function routeToStaticHtmlPath(route) {
117
- if (route === "/") return "/index.html";
118
- return `${route}/index.html`;
119
- }
120
- function routeToHeaderSource(route) {
121
- return route === "/" ? "/" : route;
122
- }
123
- function escapeRegex(value) {
124
- return value.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
125
- }
126
- //#endregion
127
- //#region src/commands/build.ts
128
- var build_default = defineCommand({
129
- meta: {
130
- name: "build",
131
- description: "Production build (client + server)"
132
- },
133
- async run() {
134
- const root = process.cwd();
135
- console.log("\n Building client...\n");
136
- await build({
137
- root,
138
- build: {
139
- outDir: "dist",
140
- manifest: true,
141
- rollupOptions: { input: "virtual:pracht/client" }
142
- }
143
- });
144
- console.log("\n Building server...\n");
145
- await build({
146
- root,
147
- build: {
148
- outDir: "dist/server",
149
- rollupOptions: { input: "virtual:pracht/server" },
150
- ssr: true
151
- }
152
- });
153
- const serverEntry = resolve(root, "dist/server/server.js");
154
- let clientDir;
155
- if (existsSync(resolve(root, "dist/client/.vite/manifest.json"))) clientDir = resolve(root, "dist/client");
156
- else {
157
- clientDir = resolve(root, "dist/client");
158
- const distRoot = resolve(root, "dist");
159
- mkdirSync(clientDir, { recursive: true });
160
- for (const entry of readdirSync(distRoot)) {
161
- if (entry === "server" || entry === "client") continue;
162
- const sourcePath = join(distRoot, entry);
163
- cpSync(sourcePath, join(clientDir, entry), { recursive: true });
164
- rmSync(sourcePath, {
165
- force: true,
166
- recursive: true
167
- });
168
- }
169
- }
170
- const publicDir = resolve(root, "public");
171
- if (existsSync(publicDir)) cpSync(publicDir, clientDir, { recursive: true });
172
- if (existsSync(serverEntry)) {
173
- const serverMod = await import(pathToFileURL(serverEntry).href);
174
- const { prerenderApp } = serverMod;
175
- const { clientEntryUrl, cssManifest, jsManifest } = readClientBuildAssets(root);
176
- const { pages, isgManifest } = await prerenderApp({
177
- app: serverMod.resolvedApp,
178
- clientEntryUrl: clientEntryUrl ?? void 0,
179
- cssManifest,
180
- jsManifest,
181
- registry: serverMod.registry,
182
- withISGManifest: true,
183
- concurrency: serverMod.prerenderConcurrency
184
- });
185
- const headersManifest = Object.fromEntries(pages.map((page) => [page.path, page.headers ?? {}]));
186
- if (pages.length > 0) {
187
- console.log(`\n Prerendering ${pages.length} SSG/ISG route(s)...\n`);
188
- for (const page of pages) {
189
- const filePath = resolvePrerenderOutputPath(clientDir, page.path);
190
- mkdirSync(dirname(filePath), { recursive: true });
191
- writeFileSync(filePath, page.html, "utf-8");
192
- console.log(` ${page.path} → ${filePath.replace(root + "/", "")}`);
193
- }
194
- }
195
- if (Object.keys(headersManifest).length > 0) {
196
- const headersManifestJson = `${JSON.stringify(headersManifest, null, 2)}\n`;
197
- writeFileSync(resolve(root, "dist/server/headers-manifest.json"), headersManifestJson, "utf-8");
198
- mkdirSync(resolve(clientDir, "_pracht"), { recursive: true });
199
- writeFileSync(resolve(clientDir, "_pracht/headers.json"), headersManifestJson, "utf-8");
200
- }
201
- if (Object.keys(isgManifest).length > 0) {
202
- writeFileSync(resolve(root, "dist/server/isg-manifest.json"), JSON.stringify(isgManifest, null, 2), "utf-8");
203
- console.log(`\n ISG manifest → dist/server/isg-manifest.json (${Object.keys(isgManifest).length} route(s))\n`);
204
- }
205
- if (serverMod.buildTarget === "cloudflare") {
206
- if (Object.keys(isgManifest).length > 0) console.warn("\n Warning: Cloudflare adapter currently serves prerendered ISG HTML as static assets and does not perform runtime revalidation. Use SSR/SSG on Cloudflare, or deploy ISG routes to Node until Cloudflare ISG support is added.\n");
207
- console.log("\n Cloudflare worker → dist/server/server.js\n");
208
- console.log(" Deploy with: wrangler deploy\n");
209
- }
210
- if (serverMod.buildTarget === "vercel") {
211
- const outputPath = writeVercelBuildOutput({
212
- functionName: serverMod.vercelFunctionName,
213
- isgRoutes: Object.keys(isgManifest),
214
- headersManifest,
215
- regions: serverMod.vercelRegions,
216
- root,
217
- staticRoutes: pages.map((page) => page.path).filter((path) => !(path in isgManifest))
218
- });
219
- console.log(`\n Vercel build output → ${outputPath}\n`);
220
- }
221
- }
222
- console.log("\n Build complete.\n");
223
- }
224
- });
225
- function resolvePrerenderOutputPath(clientDir, routePath) {
226
- if (routePath.includes("\0")) throw new Error(`Refusing to write prerendered route "${routePath}" with a NUL byte.`);
227
- const root = resolve(clientDir);
228
- const filePath = routePath === "/" ? resolve(root, "index.html") : resolve(root, `.${routePath}`, "index.html");
229
- const relativePath = relative(root, filePath);
230
- if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) throw new Error(`Refusing to write prerendered route "${routePath}" outside dist/client (${filePath}).`);
231
- return filePath;
232
- }
233
- //#endregion
234
- export { build_default as default };
@@ -1,25 +0,0 @@
1
- import { defineCommand } from "citty";
2
- import { createServer } from "vite";
3
- //#region src/commands/dev.ts
4
- var dev_default = defineCommand({
5
- meta: {
6
- name: "dev",
7
- description: "Start development server with HMR"
8
- },
9
- args: { port: {
10
- type: "positional",
11
- description: "Port number",
12
- required: false
13
- } },
14
- async run({ args }) {
15
- const port = parseInt(process.env.PORT || args.port || "3000", 10);
16
- const server = await createServer({
17
- root: process.cwd(),
18
- server: { port }
19
- });
20
- await server.listen();
21
- server.printUrls();
22
- }
23
- });
24
- //#endregion
25
- export { dev_default as default };