@pracht/cli 1.1.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @pracht/cli
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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
8
+
9
+ ### Patch Changes
10
+
11
+ - 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)]:
12
+ - @pracht/core@0.2.6
13
+
3
14
  ## 1.1.5
4
15
 
5
16
  ### 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,212 @@
1
+ import { r as VERSION } from "./index.mjs";
2
+ import { t as readClientBuildAssets } from "./build-metadata-IABPFFKk.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
+ if (existsSync(serverEntry)) {
161
+ const serverMod = await import(serverEntry);
162
+ const { prerenderApp } = serverMod;
163
+ const { clientEntryUrl, cssManifest, jsManifest } = readClientBuildAssets(root);
164
+ const { pages, isgManifest } = await prerenderApp({
165
+ app: serverMod.resolvedApp,
166
+ clientEntryUrl: clientEntryUrl ?? void 0,
167
+ cssManifest,
168
+ jsManifest,
169
+ registry: serverMod.registry,
170
+ withISGManifest: true
171
+ });
172
+ const headersManifest = Object.fromEntries(pages.map((page) => [page.path, page.headers ?? {}]));
173
+ if (pages.length > 0) {
174
+ console.log(`\n Prerendering ${pages.length} SSG/ISG route(s)...\n`);
175
+ for (const page of pages) {
176
+ const filePath = page.path === "/" ? join(clientDir, "index.html") : join(clientDir, page.path, "index.html");
177
+ mkdirSync(dirname(filePath), { recursive: true });
178
+ writeFileSync(filePath, page.html, "utf-8");
179
+ console.log(` ${page.path} → ${filePath.replace(root + "/", "")}`);
180
+ }
181
+ }
182
+ if (Object.keys(headersManifest).length > 0) {
183
+ const headersManifestJson = `${JSON.stringify(headersManifest, null, 2)}\n`;
184
+ writeFileSync(resolve(root, "dist/server/headers-manifest.json"), headersManifestJson, "utf-8");
185
+ mkdirSync(resolve(clientDir, "_pracht"), { recursive: true });
186
+ writeFileSync(resolve(clientDir, "_pracht/headers.json"), headersManifestJson, "utf-8");
187
+ }
188
+ if (Object.keys(isgManifest).length > 0) {
189
+ writeFileSync(resolve(root, "dist/server/isg-manifest.json"), JSON.stringify(isgManifest, null, 2), "utf-8");
190
+ console.log(`\n ISG manifest → dist/server/isg-manifest.json (${Object.keys(isgManifest).length} route(s))\n`);
191
+ }
192
+ if (serverMod.buildTarget === "cloudflare") {
193
+ console.log("\n Cloudflare worker → dist/server/server.js\n");
194
+ console.log(" Deploy with: wrangler deploy\n");
195
+ }
196
+ if (serverMod.buildTarget === "vercel") {
197
+ const outputPath = writeVercelBuildOutput({
198
+ functionName: serverMod.vercelFunctionName,
199
+ isgRoutes: Object.keys(isgManifest),
200
+ headersManifest,
201
+ regions: serverMod.vercelRegions,
202
+ root,
203
+ staticRoutes: pages.map((page) => page.path).filter((path) => !(path in isgManifest))
204
+ });
205
+ console.log(`\n Vercel build output → ${outputPath}\n`);
206
+ }
207
+ }
208
+ console.log("\n Build complete.\n");
209
+ }
210
+ });
211
+ //#endregion
212
+ export { build_default as default };
@@ -0,0 +1,49 @@
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
+ function readClientBuildAssets(root = process.cwd()) {
6
+ const manifestPath = MANIFEST_PATHS.map((candidate) => resolve(root, candidate)).find((path) => existsSync(path));
7
+ if (!manifestPath) return {
8
+ clientEntryUrl: null,
9
+ cssManifest: {},
10
+ jsManifest: {}
11
+ };
12
+ const rawManifest = readFileSync(manifestPath, "utf-8");
13
+ const manifest = JSON.parse(rawManifest);
14
+ const clientEntry = manifest["virtual:pracht/client"];
15
+ function collectTransitiveDeps(key) {
16
+ const css = /* @__PURE__ */ new Set();
17
+ const js = /* @__PURE__ */ new Set();
18
+ const visited = /* @__PURE__ */ new Set();
19
+ function collect(currentKey) {
20
+ if (visited.has(currentKey)) return;
21
+ visited.add(currentKey);
22
+ const entry = manifest[currentKey];
23
+ if (!entry) return;
24
+ for (const cssFile of entry.css ?? []) css.add(cssFile);
25
+ js.add(entry.file);
26
+ for (const importedKey of entry.imports ?? []) collect(importedKey);
27
+ }
28
+ collect(key);
29
+ return {
30
+ css: [...css],
31
+ js: [...js]
32
+ };
33
+ }
34
+ const cssManifest = {};
35
+ const jsManifest = {};
36
+ for (const [key, entry] of Object.entries(manifest)) {
37
+ if (!entry.src) continue;
38
+ const deps = collectTransitiveDeps(key);
39
+ if (deps.css.length > 0) cssManifest[key] = deps.css.map((file) => `/${file}`);
40
+ if (deps.js.length > 0) jsManifest[key] = deps.js.map((file) => `/${file}`);
41
+ }
42
+ return {
43
+ clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
44
+ cssManifest,
45
+ jsManifest
46
+ };
47
+ }
48
+ //#endregion
49
+ 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-Dfl3X4Zo.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 };