@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,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-
|
|
2
|
-
import { runInspect } from "./inspect-
|
|
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
|
|
2
|
-
import { n as
|
|
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,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/cli",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"
|
|
43
|
+
"zod": "^4.4.3",
|
|
44
|
+
"@pracht/core": "0.9.0"
|
|
43
45
|
},
|
|
44
46
|
"scripts": {
|
|
45
47
|
"build": "tsdown"
|
package/dist/build-B7_6RUBf.mjs
DELETED
|
@@ -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 };
|
package/dist/dev-BCFe3g38.mjs
DELETED
|
@@ -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 };
|
|
File without changes
|
|
File without changes
|