@pracht/cli 1.2.1 → 1.2.2

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,78 @@ 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, ":$1");
46
+ return normalizeRoutePath(relativePath ? `/api/${relativePath}` : "/api");
47
+ }
48
+ //#endregion
49
+ //#region src/verification-pages.ts
50
+ function scanPagesDirectory(pagesDir) {
51
+ return listFilesRecursively(pagesDir).filter((file) => PAGE_SOURCE_RE.test(file)).map((file) => describePagesFile(pagesDir, file));
52
+ }
53
+ function describePagesFile(pagesDir, file) {
54
+ const routePath = relative(pagesDir, file).replace(/\\/g, "/").replace(/\.(tsx?|jsx?|mdx?)$/, "");
55
+ const name = basename(routePath);
56
+ if (hasPagesAppShell(file)) return {
57
+ file,
58
+ kind: "shell"
59
+ };
60
+ if (name.startsWith("_")) return {
61
+ file,
62
+ kind: "ignored"
63
+ };
64
+ if (routePath === "index") return {
65
+ file,
66
+ kind: "route",
67
+ routePath: "/"
36
68
  };
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
69
  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))
70
+ file,
71
+ kind: "route",
72
+ routePath: normalizeRoutePath(`/${routePath.replace(/\/index$/, "").replace(/\[\.\.\.([^\]]+)\]/g, "*").replace(/\[([^\].]+)\]/g, ":$1")}`)
66
73
  };
67
74
  }
75
+ function collectDuplicateRoutePaths(routes) {
76
+ const routeMap = /* @__PURE__ */ new Map();
77
+ for (const route of routes) {
78
+ const files = routeMap.get(route.routePath) ?? [];
79
+ files.push(route.file);
80
+ routeMap.set(route.routePath, files);
81
+ }
82
+ return [...routeMap.entries()].filter(([, files]) => files.length > 1).map(([path, files]) => ({
83
+ files,
84
+ path
85
+ }));
86
+ }
87
+ //#endregion
88
+ //#region src/verification-checks.ts
68
89
  function collectConfigChecks(project, checks, configDisplayPath) {
69
90
  if (!project.configFile) checks.push(createCheck("error", "Missing vite config."));
70
91
  else checks.push(createCheck("ok", `Found ${configDisplayPath}.`));
@@ -223,6 +244,8 @@ function collectPackageChecks(project, checks, packageJsonPath) {
223
244
  if (adapterPackages.length === 0) checks.push(createCheck("warning", "No built-in pracht adapter dependency was found in package.json."));
224
245
  else checks.push(createCheck("ok", `Found adapter dependency ${adapterPackages.map((name) => JSON.stringify(name)).join(", ")}.`));
225
246
  }
247
+ //#endregion
248
+ //#region src/verification-scope.ts
226
249
  function collectChangedFiles(root) {
227
250
  try {
228
251
  const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
@@ -300,72 +323,57 @@ function requiresFullVerification(project, changedFiles) {
300
323
  return false;
301
324
  });
302
325
  }
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
- };
326
+ //#endregion
327
+ //#region src/verification.ts
328
+ function runDoctor(root) {
329
+ const report = runVerification(root);
322
330
  return {
323
- file,
324
- kind: "route",
325
- routePath: normalizeRoutePath(`/${routePath.replace(/\/index$/, "").replace(/\[\.\.\.([^\]]+)\]/g, "*").replace(/\[([^\].]+)\]/g, ":$1")}`)
331
+ checks: report.checks,
332
+ configFile: report.configFile,
333
+ mode: report.mode,
334
+ ok: report.ok
326
335
  };
327
336
  }
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);
337
+ function runVerification(root, options = {}) {
338
+ const project = readProjectConfig(root);
339
+ const checks = [];
340
+ const packageJsonPath = resolve(project.root, "package.json");
341
+ const configDisplayPath = project.configFile ? displayPath(root, project.configFile) : "vite.config.*";
342
+ const requestedScope = options.changed ? "changed" : "full";
343
+ collectConfigChecks(project, checks, configDisplayPath);
344
+ let changedInfo = {
345
+ files: [],
346
+ warning: null
347
+ };
348
+ if (options.changed) {
349
+ changedInfo = collectChangedFiles(project.root);
350
+ if (changedInfo.warning) checks.push(createCheck("warning", changedInfo.warning));
334
351
  }
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) {
352
+ const frameworkFiles = options.changed ? filterFrameworkFiles(project, changedInfo.files, packageJsonPath) : [];
353
+ const scope = options.changed && !changedInfo.warning && !requiresFullVerification(project, frameworkFiles) ? "changed" : "full";
354
+ if (project.mode === "pages") collectPagesVerification(project, checks, {
355
+ changedFiles: frameworkFiles,
356
+ scope
357
+ });
358
+ else collectManifestVerification(project, checks, {
359
+ changedFiles: frameworkFiles,
360
+ scope
361
+ });
362
+ collectApiVerification(project, checks, {
363
+ changedFiles: frameworkFiles,
364
+ scope
365
+ });
366
+ collectPackageChecks(project, checks, packageJsonPath);
367
+ if (options.changed && frameworkFiles.length === 0 && !changedInfo.warning) checks.push(createCheck("ok", "No changed framework files were detected in the current project scope."));
366
368
  return {
367
- message,
368
- status
369
+ checks,
370
+ configFile: project.configFile ? displayPath(root, project.configFile) : null,
371
+ mode: project.mode,
372
+ ok: !checks.some((check) => check.status === "error"),
373
+ requestedScope,
374
+ scope,
375
+ changedFiles: changedInfo.files.map((file) => displayPath(project.root, file)),
376
+ frameworkFiles: frameworkFiles.map((file) => displayPath(project.root, file))
369
377
  };
370
378
  }
371
379
  //#endregion
@@ -1,4 +1,4 @@
1
- import { n as runVerification } from "./verification-CId4crSF.mjs";
1
+ import { n as runVerification } from "./verification-Dwf1ZuNn.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.2.2",
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.3.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
- }