@pracht/cli 1.1.5 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @pracht/cli
2
2
 
3
+ ## 1.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#116](https://github.com/JoviDeCroock/pracht/pull/116) [`411da18`](https://github.com/JoviDeCroock/pracht/commit/411da18d0fa8bbc20270729584c6677376be7f24) Thanks [@kinngh](https://github.com/kinngh)! - Strip server-only route and shell exports from client module imports so inline loaders can statically import server-only dependencies without evaluating them in browser bundles.
8
+
9
+ - [#117](https://github.com/JoviDeCroock/pracht/pull/117) [`39a226d`](https://github.com/JoviDeCroock/pracht/commit/39a226d1023317c357df8b72e020034a2c68d896) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Copy public/ folder contents to dist/client/ during build so that static assets like favicons and robots.txt are available for deployment platforms
10
+
11
+ - Updated dependencies [[`f0ed41e`](https://github.com/JoviDeCroock/pracht/commit/f0ed41e4b886e751fbdfd29ae10f880c3aa364d4), [`49732fc`](https://github.com/JoviDeCroock/pracht/commit/49732fc78a776cbaabe9579e5a7f2fb154497479), [`d88c9e4`](https://github.com/JoviDeCroock/pracht/commit/d88c9e4b8347c4d3ecacdbc5f7674ee38af0092e), [`7ee2a93`](https://github.com/JoviDeCroock/pracht/commit/7ee2a936357a0f0b4ff7f5a7f6f3206b070f3890), [`00c4014`](https://github.com/JoviDeCroock/pracht/commit/00c401410b13c2d904c0beafc4da62dfb8f0f91e), [`f0ed41e`](https://github.com/JoviDeCroock/pracht/commit/f0ed41e4b886e751fbdfd29ae10f880c3aa364d4)]:
12
+ - @pracht/core@0.2.7
13
+
14
+ ## 1.2.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [#96](https://github.com/JoviDeCroock/pracht/pull/96) [`755dc1f`](https://github.com/JoviDeCroock/pracht/commit/755dc1fd80e0c0457f29e85abf59b2f2ff3f1bdc) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Convert CLI codebase from JavaScript to TypeScript and replace custom flag parsing with citty
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [[`f7b5366`](https://github.com/JoviDeCroock/pracht/commit/f7b5366cead40f2237d55e6027dc4bfb7f8b324f), [`d284596`](https://github.com/JoviDeCroock/pracht/commit/d284596fe00c3c74d56e7dc040ea1e8c9961eb99), [`2c95189`](https://github.com/JoviDeCroock/pracht/commit/2c95189209b4b09f862194078f7d2ced15f22dde)]:
23
+ - @pracht/core@0.2.6
24
+
3
25
  ## 1.1.5
4
26
 
5
27
  ### Patch Changes
package/bin/pracht.js CHANGED
@@ -1,41 +1,2 @@
1
1
  #!/usr/bin/env node
2
-
3
- import { doctorCommand } from "../lib/commands/doctor.js";
4
- import { buildCommand } from "../lib/commands/build.js";
5
- import { devCommand } from "../lib/commands/dev.js";
6
- import { generateCommand } from "../lib/commands/generate.js";
7
- import { verifyCommand } from "../lib/commands/verify.js";
8
- import { inspectCommand } from "../lib/commands/inspect.js";
9
- import { handleCliError, printHelp } from "../lib/cli.js";
10
- import { VERSION } from "../lib/constants.js";
11
-
12
- const argv = process.argv.slice(2);
13
- const command = argv[0];
14
- const jsonOutput = argv.includes("--json");
15
-
16
- if (!command || command === "help" || command === "--help" || command === "-h") {
17
- printHelp();
18
- process.exit(0);
19
- }
20
-
21
- if (command === "--version" || command === "-v") {
22
- console.log(VERSION);
23
- process.exit(0);
24
- }
25
-
26
- const handlers = {
27
- build: buildCommand,
28
- dev: devCommand,
29
- doctor: doctorCommand,
30
- generate: generateCommand,
31
- verify: verifyCommand,
32
- inspect: inspectCommand,
33
- };
34
-
35
- if (!(command in handlers)) {
36
- handleCliError(new Error(`Unknown pracht command: ${command}`), { json: false });
37
- }
38
-
39
- handlers[command](argv.slice(1)).catch((error) => {
40
- handleCliError(error, { json: jsonOutput });
41
- });
2
+ import "../dist/index.mjs";
@@ -0,0 +1,214 @@
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, join, resolve } from "node:path";
6
+ import { build } from "vite";
7
+ //#region src/build-shared.ts
8
+ const ROUTE_STATE_REQUEST_HEADER = "x-pracht-route-state-request";
9
+ function writeVercelBuildOutput({ functionName, headersManifest = {}, isgRoutes, regions, root, staticRoutes }) {
10
+ const outputDir = join(root, ".vercel/output");
11
+ const staticDir = join(outputDir, "static");
12
+ const functionDir = join(outputDir, "functions", `${functionName || "render"}.func`);
13
+ rmSync(outputDir, {
14
+ force: true,
15
+ recursive: true
16
+ });
17
+ mkdirSync(outputDir, { recursive: true });
18
+ cpSync(join(root, "dist/client"), staticDir, { recursive: true });
19
+ cpSync(join(root, "dist/server"), functionDir, { recursive: true });
20
+ writeFileSync(join(outputDir, "config.json"), `${JSON.stringify(createVercelOutputConfig({
21
+ functionName,
22
+ headersManifest,
23
+ staticRoutes,
24
+ isgRoutes
25
+ }), null, 2)}\n`, "utf-8");
26
+ writeFileSync(join(functionDir, ".vc-config.json"), `${JSON.stringify(createVercelFunctionConfig({ regions }), null, 2)}\n`, "utf-8");
27
+ return ".vercel/output";
28
+ }
29
+ function createVercelOutputConfig({ functionName, headersManifest, staticRoutes, isgRoutes }) {
30
+ const target = `/${functionName || "render"}`;
31
+ const routes = [{
32
+ dest: target,
33
+ has: [{
34
+ type: "header",
35
+ key: ROUTE_STATE_REQUEST_HEADER,
36
+ value: "1"
37
+ }],
38
+ src: "/(.*)"
39
+ }];
40
+ for (const route of sortStaticRoutes(staticRoutes)) routes.push({
41
+ dest: routeToStaticHtmlPath(route),
42
+ src: routeToRouteExpression(route)
43
+ });
44
+ for (const route of isgRoutes) routes.push({
45
+ dest: target,
46
+ src: routeToRouteExpression(route)
47
+ });
48
+ routes.push({ handle: "filesystem" });
49
+ routes.push({
50
+ dest: target,
51
+ src: "/(.*)"
52
+ });
53
+ const headers = [{
54
+ headers: [
55
+ {
56
+ key: "permissions-policy",
57
+ value: "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
58
+ },
59
+ {
60
+ key: "referrer-policy",
61
+ value: "strict-origin-when-cross-origin"
62
+ },
63
+ {
64
+ key: "x-content-type-options",
65
+ value: "nosniff"
66
+ },
67
+ {
68
+ key: "x-frame-options",
69
+ value: "SAMEORIGIN"
70
+ }
71
+ ],
72
+ source: "/(.*)"
73
+ }];
74
+ for (const route of sortStaticRoutes(staticRoutes)) {
75
+ const routeHeaders = headersManifest[route];
76
+ if (!routeHeaders) continue;
77
+ headers.push({
78
+ headers: Object.entries(routeHeaders).map(([key, value]) => ({
79
+ key,
80
+ value
81
+ })),
82
+ source: routeToHeaderSource(route)
83
+ });
84
+ }
85
+ return {
86
+ headers,
87
+ framework: { version: VERSION },
88
+ routes,
89
+ version: 3
90
+ };
91
+ }
92
+ function createVercelFunctionConfig({ regions }) {
93
+ const config = {
94
+ entrypoint: "server.js",
95
+ runtime: "edge"
96
+ };
97
+ if (regions) config.regions = regions;
98
+ return config;
99
+ }
100
+ function sortStaticRoutes(routes) {
101
+ return [...new Set(routes)].sort((left, right) => right.length - left.length);
102
+ }
103
+ function routeToRouteExpression(route) {
104
+ if (route === "/") return "^/$";
105
+ return `^${escapeRegex(route)}/?$`;
106
+ }
107
+ function routeToStaticHtmlPath(route) {
108
+ if (route === "/") return "/index.html";
109
+ return `${route}/index.html`;
110
+ }
111
+ function routeToHeaderSource(route) {
112
+ return route === "/" ? "/" : route;
113
+ }
114
+ function escapeRegex(value) {
115
+ return value.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
116
+ }
117
+ //#endregion
118
+ //#region src/commands/build.ts
119
+ var build_default = defineCommand({
120
+ meta: {
121
+ name: "build",
122
+ description: "Production build (client + server)"
123
+ },
124
+ async run() {
125
+ const root = process.cwd();
126
+ console.log("\n Building client...\n");
127
+ await build({
128
+ root,
129
+ build: {
130
+ outDir: "dist",
131
+ manifest: true,
132
+ rollupOptions: { input: "virtual:pracht/client" }
133
+ }
134
+ });
135
+ console.log("\n Building server...\n");
136
+ await build({
137
+ root,
138
+ build: {
139
+ outDir: "dist/server",
140
+ ssr: "virtual:pracht/server"
141
+ }
142
+ });
143
+ const serverEntry = resolve(root, "dist/server/server.js");
144
+ let clientDir;
145
+ if (existsSync(resolve(root, "dist/client/.vite/manifest.json"))) clientDir = resolve(root, "dist/client");
146
+ else {
147
+ clientDir = resolve(root, "dist/client");
148
+ const distRoot = resolve(root, "dist");
149
+ mkdirSync(clientDir, { recursive: true });
150
+ for (const entry of readdirSync(distRoot)) {
151
+ if (entry === "server" || entry === "client") continue;
152
+ const sourcePath = join(distRoot, entry);
153
+ cpSync(sourcePath, join(clientDir, entry), { recursive: true });
154
+ rmSync(sourcePath, {
155
+ force: true,
156
+ recursive: true
157
+ });
158
+ }
159
+ }
160
+ const publicDir = resolve(root, "public");
161
+ if (existsSync(publicDir)) cpSync(publicDir, clientDir, { recursive: true });
162
+ if (existsSync(serverEntry)) {
163
+ const serverMod = await import(serverEntry);
164
+ const { prerenderApp } = serverMod;
165
+ const { clientEntryUrl, cssManifest, jsManifest } = readClientBuildAssets(root);
166
+ const { pages, isgManifest } = await prerenderApp({
167
+ app: serverMod.resolvedApp,
168
+ clientEntryUrl: clientEntryUrl ?? void 0,
169
+ cssManifest,
170
+ jsManifest,
171
+ registry: serverMod.registry,
172
+ withISGManifest: true
173
+ });
174
+ const headersManifest = Object.fromEntries(pages.map((page) => [page.path, page.headers ?? {}]));
175
+ if (pages.length > 0) {
176
+ console.log(`\n Prerendering ${pages.length} SSG/ISG route(s)...\n`);
177
+ for (const page of pages) {
178
+ const filePath = page.path === "/" ? join(clientDir, "index.html") : join(clientDir, page.path, "index.html");
179
+ mkdirSync(dirname(filePath), { recursive: true });
180
+ writeFileSync(filePath, page.html, "utf-8");
181
+ console.log(` ${page.path} → ${filePath.replace(root + "/", "")}`);
182
+ }
183
+ }
184
+ if (Object.keys(headersManifest).length > 0) {
185
+ const headersManifestJson = `${JSON.stringify(headersManifest, null, 2)}\n`;
186
+ writeFileSync(resolve(root, "dist/server/headers-manifest.json"), headersManifestJson, "utf-8");
187
+ mkdirSync(resolve(clientDir, "_pracht"), { recursive: true });
188
+ writeFileSync(resolve(clientDir, "_pracht/headers.json"), headersManifestJson, "utf-8");
189
+ }
190
+ if (Object.keys(isgManifest).length > 0) {
191
+ writeFileSync(resolve(root, "dist/server/isg-manifest.json"), JSON.stringify(isgManifest, null, 2), "utf-8");
192
+ console.log(`\n ISG manifest → dist/server/isg-manifest.json (${Object.keys(isgManifest).length} route(s))\n`);
193
+ }
194
+ if (serverMod.buildTarget === "cloudflare") {
195
+ console.log("\n Cloudflare worker → dist/server/server.js\n");
196
+ console.log(" Deploy with: wrangler deploy\n");
197
+ }
198
+ if (serverMod.buildTarget === "vercel") {
199
+ const outputPath = writeVercelBuildOutput({
200
+ functionName: serverMod.vercelFunctionName,
201
+ isgRoutes: Object.keys(isgManifest),
202
+ headersManifest,
203
+ regions: serverMod.vercelRegions,
204
+ root,
205
+ staticRoutes: pages.map((page) => page.path).filter((path) => !(path in isgManifest))
206
+ });
207
+ console.log(`\n Vercel build output → ${outputPath}\n`);
208
+ }
209
+ }
210
+ console.log("\n Build complete.\n");
211
+ }
212
+ });
213
+ //#endregion
214
+ export { build_default as default };
@@ -0,0 +1,58 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ //#region src/build-metadata.ts
4
+ const MANIFEST_PATHS = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"];
5
+ const PRACHT_CLIENT_MODULE_QUERY = "pracht-client";
6
+ function readClientBuildAssets(root = process.cwd()) {
7
+ const manifestPath = MANIFEST_PATHS.map((candidate) => resolve(root, candidate)).find((path) => existsSync(path));
8
+ if (!manifestPath) return {
9
+ clientEntryUrl: null,
10
+ cssManifest: {},
11
+ jsManifest: {}
12
+ };
13
+ const rawManifest = readFileSync(manifestPath, "utf-8");
14
+ const manifest = JSON.parse(rawManifest);
15
+ const clientEntry = manifest["virtual:pracht/client"];
16
+ function collectTransitiveDeps(key) {
17
+ const css = /* @__PURE__ */ new Set();
18
+ const js = /* @__PURE__ */ new Set();
19
+ const visited = /* @__PURE__ */ new Set();
20
+ function collect(currentKey) {
21
+ if (visited.has(currentKey)) return;
22
+ visited.add(currentKey);
23
+ const entry = manifest[currentKey];
24
+ if (!entry) return;
25
+ for (const cssFile of entry.css ?? []) css.add(cssFile);
26
+ js.add(entry.file);
27
+ for (const importedKey of entry.imports ?? []) collect(importedKey);
28
+ }
29
+ collect(key);
30
+ return {
31
+ css: [...css],
32
+ js: [...js]
33
+ };
34
+ }
35
+ const cssManifest = {};
36
+ const jsManifest = {};
37
+ for (const [key, entry] of Object.entries(manifest)) {
38
+ if (!entry.src) continue;
39
+ const deps = collectTransitiveDeps(key);
40
+ const manifestKey = stripPrachtClientModuleQuery(entry.src);
41
+ if (deps.css.length > 0) cssManifest[manifestKey] = deps.css.map((file) => `/${file}`);
42
+ if (deps.js.length > 0) jsManifest[manifestKey] = deps.js.map((file) => `/${file}`);
43
+ }
44
+ return {
45
+ clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
46
+ cssManifest,
47
+ jsManifest
48
+ };
49
+ }
50
+ function stripPrachtClientModuleQuery(id) {
51
+ const queryStart = id.indexOf("?");
52
+ if (queryStart === -1) return id;
53
+ const path = id.slice(0, queryStart);
54
+ const query = id.slice(queryStart + 1).split("&").filter((part) => part !== PRACHT_CLIENT_MODULE_QUERY);
55
+ return query.length > 0 ? `${path}?${query.join("&")}` : path;
56
+ }
57
+ //#endregion
58
+ export { readClientBuildAssets as t };
@@ -0,0 +1,25 @@
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 };
@@ -0,0 +1,25 @@
1
+ import { t as runDoctor } from "./verification-CId4crSF.mjs";
2
+ import { defineCommand } from "citty";
3
+ //#region src/commands/doctor.ts
4
+ var doctor_default = defineCommand({
5
+ meta: {
6
+ name: "doctor",
7
+ description: "Validate app wiring"
8
+ },
9
+ args: { json: {
10
+ type: "boolean",
11
+ description: "Output as JSON"
12
+ } },
13
+ async run({ args }) {
14
+ const report = runDoctor(process.cwd());
15
+ if (args.json) console.log(JSON.stringify(report, null, 2));
16
+ else {
17
+ console.log(`Pracht doctor (${report.mode} mode)`);
18
+ for (const check of report.checks) console.log(`${check.status.toUpperCase().padEnd(5)} ${check.message}`);
19
+ console.log(report.ok ? "\nNo blocking issues found." : "\nBlocking issues found.");
20
+ }
21
+ if (!report.ok) process.exitCode = 1;
22
+ }
23
+ });
24
+ //#endregion
25
+ export { doctor_default as default };