@pracht/cli 1.2.1 → 1.3.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.
@@ -1,9 +1,9 @@
1
- import { n as extractRegistryEntries, r as extractRelativeModulePaths } from "./manifest-DGq1n5LT.mjs";
2
- import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-DUyH_7eJ.mjs";
1
+ import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-BdMiN3s7.mjs";
2
+ import { n as extractRegistryEntries, r as extractRelativeModulePaths } from "./manifest-Bs5hp3gA.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { basename, dirname, relative, resolve } from "node:path";
5
5
  import { execFileSync } from "node:child_process";
6
- //#region src/verification.ts
6
+ //#region src/verification-helpers.ts
7
7
  const CONFIG_FILE_NAMES = new Set([
8
8
  "vite.config.ts",
9
9
  "vite.config.mts",
@@ -14,57 +14,79 @@ const CONFIG_FILE_NAMES = new Set([
14
14
  ]);
15
15
  const MODULE_SOURCE_RE = /\.(ts|tsx|js|jsx)$/;
16
16
  const PAGE_SOURCE_RE = /\.(ts|tsx|js|jsx|md|mdx)$/;
17
- function runDoctor(root) {
18
- const report = runVerification(root);
17
+ function createCheck(status, message) {
19
18
  return {
20
- checks: report.checks,
21
- configFile: report.configFile,
22
- mode: report.mode,
23
- ok: report.ok
19
+ message,
20
+ status
24
21
  };
25
22
  }
26
- function runVerification(root, options = {}) {
27
- const project = readProjectConfig(root);
28
- const checks = [];
29
- const packageJsonPath = resolve(project.root, "package.json");
30
- const configDisplayPath = project.configFile ? displayPath(root, project.configFile) : "vite.config.*";
31
- const requestedScope = options.changed ? "changed" : "full";
32
- collectConfigChecks(project, checks, configDisplayPath);
33
- let changedInfo = {
34
- files: [],
35
- warning: null
23
+ function isWithinDirectory(filePath, directoryPath) {
24
+ const relativePath = relative(directoryPath, filePath);
25
+ return relativePath === "" || !relativePath.startsWith("..") && !relativePath.startsWith("../");
26
+ }
27
+ function normalizePath(value) {
28
+ return value.replace(/\\/g, "/");
29
+ }
30
+ function toModuleSpecifier(fromDir, filePath) {
31
+ const relativePath = relative(fromDir, filePath).replace(/\\/g, "/");
32
+ if (relativePath.startsWith(".")) return relativePath;
33
+ return `./${relativePath}`;
34
+ }
35
+ function normalizeRoutePath(path) {
36
+ if (!path || path === "/") return "/";
37
+ const collapsed = (path.startsWith("/") ? path : `/${path}`).replace(/\/{2,}/g, "/");
38
+ return collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
39
+ }
40
+ function resolveApiRoutePath(apiDir, file) {
41
+ let relativePath = relative(apiDir, file).replace(/\\/g, "/");
42
+ relativePath = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
43
+ if (relativePath === "index") relativePath = "";
44
+ else relativePath = relativePath.replace(/\/index$/, "");
45
+ relativePath = relativePath.replace(/\[\.\.\.[^\]]+\]/g, "*");
46
+ relativePath = relativePath.replace(/\[([^\]]+)\]/g, ":$1");
47
+ return normalizeRoutePath(relativePath ? `/api/${relativePath}` : "/api");
48
+ }
49
+ //#endregion
50
+ //#region src/verification-pages.ts
51
+ function scanPagesDirectory(pagesDir) {
52
+ return listFilesRecursively(pagesDir).filter((file) => PAGE_SOURCE_RE.test(file)).map((file) => describePagesFile(pagesDir, file));
53
+ }
54
+ function describePagesFile(pagesDir, file) {
55
+ const routePath = relative(pagesDir, file).replace(/\\/g, "/").replace(/\.(tsx?|jsx?|mdx?)$/, "");
56
+ const name = basename(routePath);
57
+ if (hasPagesAppShell(file)) return {
58
+ file,
59
+ kind: "shell"
60
+ };
61
+ if (name.startsWith("_")) return {
62
+ file,
63
+ kind: "ignored"
64
+ };
65
+ if (routePath === "index") return {
66
+ file,
67
+ kind: "route",
68
+ routePath: "/"
36
69
  };
37
- if (options.changed) {
38
- changedInfo = collectChangedFiles(project.root);
39
- if (changedInfo.warning) checks.push(createCheck("warning", changedInfo.warning));
40
- }
41
- const frameworkFiles = options.changed ? filterFrameworkFiles(project, changedInfo.files, packageJsonPath) : [];
42
- const scope = options.changed && !changedInfo.warning && !requiresFullVerification(project, frameworkFiles) ? "changed" : "full";
43
- if (project.mode === "pages") collectPagesVerification(project, checks, {
44
- changedFiles: frameworkFiles,
45
- scope
46
- });
47
- else collectManifestVerification(project, checks, {
48
- changedFiles: frameworkFiles,
49
- scope
50
- });
51
- collectApiVerification(project, checks, {
52
- changedFiles: frameworkFiles,
53
- scope
54
- });
55
- collectPackageChecks(project, checks, packageJsonPath);
56
- if (options.changed && frameworkFiles.length === 0 && !changedInfo.warning) checks.push(createCheck("ok", "No changed framework files were detected in the current project scope."));
57
70
  return {
58
- checks,
59
- configFile: project.configFile ? displayPath(root, project.configFile) : null,
60
- mode: project.mode,
61
- ok: !checks.some((check) => check.status === "error"),
62
- requestedScope,
63
- scope,
64
- changedFiles: changedInfo.files.map((file) => displayPath(project.root, file)),
65
- frameworkFiles: frameworkFiles.map((file) => displayPath(project.root, file))
71
+ file,
72
+ kind: "route",
73
+ routePath: normalizeRoutePath(`/${routePath.replace(/\/index$/, "").replace(/\[\.\.\.([^\]]+)\]/g, "*").replace(/\[([^\].]+)\]/g, ":$1")}`)
66
74
  };
67
75
  }
76
+ function collectDuplicateRoutePaths(routes) {
77
+ const routeMap = /* @__PURE__ */ new Map();
78
+ for (const route of routes) {
79
+ const files = routeMap.get(route.routePath) ?? [];
80
+ files.push(route.file);
81
+ routeMap.set(route.routePath, files);
82
+ }
83
+ return [...routeMap.entries()].filter(([, files]) => files.length > 1).map(([path, files]) => ({
84
+ files,
85
+ path
86
+ }));
87
+ }
88
+ //#endregion
89
+ //#region src/verification-checks.ts
68
90
  function collectConfigChecks(project, checks, configDisplayPath) {
69
91
  if (!project.configFile) checks.push(createCheck("error", "Missing vite config."));
70
92
  else checks.push(createCheck("ok", `Found ${configDisplayPath}.`));
@@ -223,6 +245,8 @@ function collectPackageChecks(project, checks, packageJsonPath) {
223
245
  if (adapterPackages.length === 0) checks.push(createCheck("warning", "No built-in pracht adapter dependency was found in package.json."));
224
246
  else checks.push(createCheck("ok", `Found adapter dependency ${adapterPackages.map((name) => JSON.stringify(name)).join(", ")}.`));
225
247
  }
248
+ //#endregion
249
+ //#region src/verification-scope.ts
226
250
  function collectChangedFiles(root) {
227
251
  try {
228
252
  const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
@@ -300,72 +324,57 @@ function requiresFullVerification(project, changedFiles) {
300
324
  return false;
301
325
  });
302
326
  }
303
- function scanPagesDirectory(pagesDir) {
304
- return listFilesRecursively(pagesDir).filter((file) => PAGE_SOURCE_RE.test(file)).map((file) => describePagesFile(pagesDir, file));
305
- }
306
- function describePagesFile(pagesDir, file) {
307
- const routePath = relative(pagesDir, file).replace(/\\/g, "/").replace(/\.(tsx?|jsx?|mdx?)$/, "");
308
- const name = basename(routePath);
309
- if (hasPagesAppShell(file)) return {
310
- file,
311
- kind: "shell"
312
- };
313
- if (name.startsWith("_")) return {
314
- file,
315
- kind: "ignored"
316
- };
317
- if (routePath === "index") return {
318
- file,
319
- kind: "route",
320
- routePath: "/"
321
- };
327
+ //#endregion
328
+ //#region src/verification.ts
329
+ function runDoctor(root) {
330
+ const report = runVerification(root);
322
331
  return {
323
- file,
324
- kind: "route",
325
- routePath: normalizeRoutePath(`/${routePath.replace(/\/index$/, "").replace(/\[\.\.\.([^\]]+)\]/g, "*").replace(/\[([^\].]+)\]/g, ":$1")}`)
332
+ checks: report.checks,
333
+ configFile: report.configFile,
334
+ mode: report.mode,
335
+ ok: report.ok
326
336
  };
327
337
  }
328
- function collectDuplicateRoutePaths(routes) {
329
- const routeMap = /* @__PURE__ */ new Map();
330
- for (const route of routes) {
331
- const files = routeMap.get(route.routePath) ?? [];
332
- files.push(route.file);
333
- routeMap.set(route.routePath, files);
338
+ function runVerification(root, options = {}) {
339
+ const project = readProjectConfig(root);
340
+ const checks = [];
341
+ const packageJsonPath = resolve(project.root, "package.json");
342
+ const configDisplayPath = project.configFile ? displayPath(root, project.configFile) : "vite.config.*";
343
+ const requestedScope = options.changed ? "changed" : "full";
344
+ collectConfigChecks(project, checks, configDisplayPath);
345
+ let changedInfo = {
346
+ files: [],
347
+ warning: null
348
+ };
349
+ if (options.changed) {
350
+ changedInfo = collectChangedFiles(project.root);
351
+ if (changedInfo.warning) checks.push(createCheck("warning", changedInfo.warning));
334
352
  }
335
- return [...routeMap.entries()].filter(([, files]) => files.length > 1).map(([path, files]) => ({
336
- files,
337
- path
338
- }));
339
- }
340
- function resolveApiRoutePath(apiDir, file) {
341
- let relativePath = relative(apiDir, file).replace(/\\/g, "/");
342
- relativePath = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
343
- if (relativePath === "index") relativePath = "";
344
- else relativePath = relativePath.replace(/\/index$/, "");
345
- relativePath = relativePath.replace(/\[([^\]]+)\]/g, ":$1");
346
- return normalizeRoutePath(relativePath ? `/api/${relativePath}` : "/api");
347
- }
348
- function toModuleSpecifier(fromDir, filePath) {
349
- const relativePath = relative(fromDir, filePath).replace(/\\/g, "/");
350
- if (relativePath.startsWith(".")) return relativePath;
351
- return `./${relativePath}`;
352
- }
353
- function normalizeRoutePath(path) {
354
- if (!path || path === "/") return "/";
355
- const collapsed = (path.startsWith("/") ? path : `/${path}`).replace(/\/{2,}/g, "/");
356
- return collapsed.length > 1 && collapsed.endsWith("/") ? collapsed.slice(0, -1) : collapsed;
357
- }
358
- function isWithinDirectory(filePath, directoryPath) {
359
- const relativePath = relative(directoryPath, filePath);
360
- return relativePath === "" || !relativePath.startsWith("..") && !relativePath.startsWith("../");
361
- }
362
- function normalizePath(value) {
363
- return value.replace(/\\/g, "/");
364
- }
365
- function createCheck(status, message) {
353
+ const frameworkFiles = options.changed ? filterFrameworkFiles(project, changedInfo.files, packageJsonPath) : [];
354
+ const scope = options.changed && !changedInfo.warning && !requiresFullVerification(project, frameworkFiles) ? "changed" : "full";
355
+ if (project.mode === "pages") collectPagesVerification(project, checks, {
356
+ changedFiles: frameworkFiles,
357
+ scope
358
+ });
359
+ else collectManifestVerification(project, checks, {
360
+ changedFiles: frameworkFiles,
361
+ scope
362
+ });
363
+ collectApiVerification(project, checks, {
364
+ changedFiles: frameworkFiles,
365
+ scope
366
+ });
367
+ collectPackageChecks(project, checks, packageJsonPath);
368
+ if (options.changed && frameworkFiles.length === 0 && !changedInfo.warning) checks.push(createCheck("ok", "No changed framework files were detected in the current project scope."));
366
369
  return {
367
- message,
368
- status
370
+ checks,
371
+ configFile: project.configFile ? displayPath(root, project.configFile) : null,
372
+ mode: project.mode,
373
+ ok: !checks.some((check) => check.status === "error"),
374
+ requestedScope,
375
+ scope,
376
+ changedFiles: changedInfo.files.map((file) => displayPath(project.root, file)),
377
+ frameworkFiles: frameworkFiles.map((file) => displayPath(project.root, file))
369
378
  };
370
379
  }
371
380
  //#endregion
@@ -1,4 +1,4 @@
1
- import { n as runVerification } from "./verification-CId4crSF.mjs";
1
+ import { n as runVerification } from "./verification-WP-KTb41.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.2.1",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/cli",
6
6
  "bugs": {
@@ -14,13 +14,20 @@
14
14
  "bin": {
15
15
  "pracht": "./bin/pracht.js"
16
16
  },
17
+ "files": [
18
+ "bin",
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE",
22
+ "CHANGELOG.md"
23
+ ],
17
24
  "type": "module",
18
25
  "publishConfig": {
19
26
  "provenance": true
20
27
  },
21
28
  "dependencies": {
22
29
  "citty": "^0.1.6",
23
- "@pracht/core": "0.2.7"
30
+ "@pracht/core": "0.4.0"
24
31
  },
25
32
  "scripts": {
26
33
  "build": "tsdown"
@@ -1,101 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
3
-
4
- const MANIFEST_PATHS = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"];
5
- const PRACHT_CLIENT_MODULE_QUERY = "pracht-client";
6
-
7
- interface ViteManifestEntry {
8
- css?: string[];
9
- file: string;
10
- imports?: string[];
11
- src?: string;
12
- }
13
-
14
- export interface ClientBuildAssets {
15
- clientEntryUrl: string | null;
16
- cssManifest: Record<string, string[]>;
17
- jsManifest: Record<string, string[]>;
18
- }
19
-
20
- export function readClientBuildAssets(root: string = process.cwd()): ClientBuildAssets {
21
- const manifestPath = MANIFEST_PATHS.map((candidate) => resolve(root, candidate)).find((path) =>
22
- existsSync(path),
23
- );
24
-
25
- if (!manifestPath) {
26
- return {
27
- clientEntryUrl: null,
28
- cssManifest: {},
29
- jsManifest: {},
30
- };
31
- }
32
-
33
- const rawManifest = readFileSync(manifestPath, "utf-8");
34
- const manifest: Record<string, ViteManifestEntry> = JSON.parse(rawManifest);
35
- const clientEntry = manifest["virtual:pracht/client"];
36
-
37
- function collectTransitiveDeps(key: string): { css: string[]; js: string[] } {
38
- const css = new Set<string>();
39
- const js = new Set<string>();
40
- const visited = new Set<string>();
41
-
42
- function collect(currentKey: string): void {
43
- if (visited.has(currentKey)) return;
44
- visited.add(currentKey);
45
-
46
- const entry = manifest[currentKey];
47
- if (!entry) return;
48
-
49
- for (const cssFile of entry.css ?? []) {
50
- css.add(cssFile);
51
- }
52
-
53
- js.add(entry.file);
54
-
55
- for (const importedKey of entry.imports ?? []) {
56
- collect(importedKey);
57
- }
58
- }
59
-
60
- collect(key);
61
- return {
62
- css: [...css],
63
- js: [...js],
64
- };
65
- }
66
-
67
- const cssManifest: Record<string, string[]> = {};
68
- const jsManifest: Record<string, string[]> = {};
69
-
70
- for (const [key, entry] of Object.entries(manifest)) {
71
- if (!entry.src) continue;
72
-
73
- const deps = collectTransitiveDeps(key);
74
- const manifestKey = stripPrachtClientModuleQuery(entry.src);
75
- if (deps.css.length > 0) {
76
- cssManifest[manifestKey] = deps.css.map((file) => `/${file}`);
77
- }
78
- if (deps.js.length > 0) {
79
- jsManifest[manifestKey] = deps.js.map((file) => `/${file}`);
80
- }
81
- }
82
-
83
- return {
84
- clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
85
- cssManifest,
86
- jsManifest,
87
- };
88
- }
89
-
90
- function stripPrachtClientModuleQuery(id: string): string {
91
- const queryStart = id.indexOf("?");
92
- if (queryStart === -1) return id;
93
-
94
- const path = id.slice(0, queryStart);
95
- const query = id
96
- .slice(queryStart + 1)
97
- .split("&")
98
- .filter((part) => part !== PRACHT_CLIENT_MODULE_QUERY);
99
-
100
- return query.length > 0 ? `${path}?${query.join("&")}` : path;
101
- }
@@ -1,163 +0,0 @@
1
- import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- import { VERSION } from "./constants.js";
5
-
6
- const ROUTE_STATE_REQUEST_HEADER = "x-pracht-route-state-request";
7
-
8
- interface VercelBuildOutputOptions {
9
- functionName?: string;
10
- headersManifest?: Record<string, Record<string, string>>;
11
- isgRoutes: string[];
12
- regions?: string[];
13
- root: string;
14
- staticRoutes: string[];
15
- }
16
-
17
- export function writeVercelBuildOutput({
18
- functionName,
19
- headersManifest = {},
20
- isgRoutes,
21
- regions,
22
- root,
23
- staticRoutes,
24
- }: VercelBuildOutputOptions): string {
25
- const outputDir = join(root, ".vercel/output");
26
- const staticDir = join(outputDir, "static");
27
- const functionDir = join(outputDir, "functions", `${functionName || "render"}.func`);
28
-
29
- rmSync(outputDir, { force: true, recursive: true });
30
- mkdirSync(outputDir, { recursive: true });
31
- cpSync(join(root, "dist/client"), staticDir, { recursive: true });
32
- cpSync(join(root, "dist/server"), functionDir, { recursive: true });
33
-
34
- writeFileSync(
35
- join(outputDir, "config.json"),
36
- `${JSON.stringify(
37
- createVercelOutputConfig({ functionName, headersManifest, staticRoutes, isgRoutes }),
38
- null,
39
- 2,
40
- )}\n`,
41
- "utf-8",
42
- );
43
- writeFileSync(
44
- join(functionDir, ".vc-config.json"),
45
- `${JSON.stringify(createVercelFunctionConfig({ regions }), null, 2)}\n`,
46
- "utf-8",
47
- );
48
-
49
- return ".vercel/output";
50
- }
51
-
52
- function createVercelOutputConfig({
53
- functionName,
54
- headersManifest,
55
- staticRoutes,
56
- isgRoutes,
57
- }: {
58
- functionName?: string;
59
- headersManifest: Record<string, Record<string, string>>;
60
- isgRoutes: string[];
61
- staticRoutes: string[];
62
- }): Record<string, unknown> {
63
- const target = `/${functionName || "render"}`;
64
- const routes: Record<string, unknown>[] = [
65
- {
66
- dest: target,
67
- has: [{ type: "header", key: ROUTE_STATE_REQUEST_HEADER, value: "1" }],
68
- src: "/(.*)",
69
- },
70
- ];
71
-
72
- for (const route of sortStaticRoutes(staticRoutes)) {
73
- routes.push({
74
- dest: routeToStaticHtmlPath(route),
75
- src: routeToRouteExpression(route),
76
- });
77
- }
78
-
79
- for (const route of isgRoutes) {
80
- routes.push({
81
- dest: target,
82
- src: routeToRouteExpression(route),
83
- });
84
- }
85
-
86
- routes.push({ handle: "filesystem" });
87
- routes.push({ dest: target, src: "/(.*)" });
88
-
89
- const headers: Record<string, unknown>[] = [
90
- {
91
- headers: [
92
- {
93
- key: "permissions-policy",
94
- value:
95
- "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()",
96
- },
97
- { key: "referrer-policy", value: "strict-origin-when-cross-origin" },
98
- { key: "x-content-type-options", value: "nosniff" },
99
- { key: "x-frame-options", value: "SAMEORIGIN" },
100
- ],
101
- source: "/(.*)",
102
- },
103
- ];
104
-
105
- for (const route of sortStaticRoutes(staticRoutes)) {
106
- const routeHeaders = headersManifest[route];
107
- if (!routeHeaders) continue;
108
- headers.push({
109
- headers: Object.entries(routeHeaders).map(([key, value]) => ({ key, value })),
110
- source: routeToHeaderSource(route),
111
- });
112
- }
113
-
114
- return {
115
- headers,
116
- framework: {
117
- version: VERSION,
118
- },
119
- routes,
120
- version: 3,
121
- };
122
- }
123
-
124
- function createVercelFunctionConfig({ regions }: { regions?: string[] }): Record<string, unknown> {
125
- const config: Record<string, unknown> = {
126
- entrypoint: "server.js",
127
- runtime: "edge",
128
- };
129
-
130
- if (regions) {
131
- config.regions = regions;
132
- }
133
-
134
- return config;
135
- }
136
-
137
- function sortStaticRoutes(routes: string[]): string[] {
138
- return [...new Set(routes)].sort((left, right) => right.length - left.length);
139
- }
140
-
141
- function routeToRouteExpression(route: string): string {
142
- if (route === "/") {
143
- return "^/$";
144
- }
145
-
146
- return `^${escapeRegex(route)}/?$`;
147
- }
148
-
149
- function routeToStaticHtmlPath(route: string): string {
150
- if (route === "/") {
151
- return "/index.html";
152
- }
153
-
154
- return `${route}/index.html`;
155
- }
156
-
157
- function routeToHeaderSource(route: string): string {
158
- return route === "/" ? "/" : route;
159
- }
160
-
161
- function escapeRegex(value: string): string {
162
- return value.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
163
- }