@pracht/cli 1.2.2 → 1.3.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,25 @@
1
1
  # @pracht/cli
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#137](https://github.com/JoviDeCroock/pracht/pull/137) [`ac32c2c`](https://github.com/JoviDeCroock/pracht/commit/ac32c2cb9ce5e86a38cde1167269e368f41dea0e) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - Harden same-origin request checks and HTML head rendering, improve client prefetch/navigation behavior, fix cross-platform path handling, stream and conditionally revalidate Node static responses, de-document Cloudflare runtime ISG revalidation, and align starter/docs with the current CLI/runtime behavior.
8
+
9
+ - Updated dependencies [[`ac32c2c`](https://github.com/JoviDeCroock/pracht/commit/ac32c2cb9ce5e86a38cde1167269e368f41dea0e), [`49d6348`](https://github.com/JoviDeCroock/pracht/commit/49d6348bc984464cdb0e8c54c5ef9ba5cdec911e)]:
10
+ - @pracht/core@0.5.0
11
+
12
+ ## 1.3.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#133](https://github.com/JoviDeCroock/pracht/pull/133) [`f8c5c1f`](https://github.com/JoviDeCroock/pracht/commit/f8c5c1fe1a7c7b5d7accd8028e8c12929a218081) Thanks [@JoviDeCroock](https://github.com/JoviDeCroock)! - API routes now support catch-all segments (e.g. `src/api/files/[...path].ts` → `/api/files/*`), matching the existing page-routing convention. The matched rest-path is exposed on the route params as `"*"`. Previously `[...param]` was silently turned into a `:...param` dynamic segment with a broken name.
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [[`f8c5c1f`](https://github.com/JoviDeCroock/pracht/commit/f8c5c1fe1a7c7b5d7accd8028e8c12929a218081)]:
21
+ - @pracht/core@0.4.0
22
+
3
23
  ## 1.2.2
4
24
 
5
25
  ### Patch Changes
@@ -3,6 +3,7 @@ import { t as readClientBuildAssets } from "./build-metadata-BOChHO8g.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
6
7
  import { build } from "vite";
7
8
  //#region src/build-shared.ts
8
9
  const ROUTE_STATE_REQUEST_HEADER = "x-pracht-route-state-request";
@@ -145,7 +146,8 @@ var build_default = defineCommand({
145
146
  root,
146
147
  build: {
147
148
  outDir: "dist/server",
148
- ssr: "virtual:pracht/server"
149
+ rollupOptions: { input: "virtual:pracht/server" },
150
+ ssr: true
149
151
  }
150
152
  });
151
153
  const serverEntry = resolve(root, "dist/server/server.js");
@@ -168,7 +170,7 @@ var build_default = defineCommand({
168
170
  const publicDir = resolve(root, "public");
169
171
  if (existsSync(publicDir)) cpSync(publicDir, clientDir, { recursive: true });
170
172
  if (existsSync(serverEntry)) {
171
- const serverMod = await import(serverEntry);
173
+ const serverMod = await import(pathToFileURL(serverEntry).href);
172
174
  const { prerenderApp } = serverMod;
173
175
  const { clientEntryUrl, cssManifest, jsManifest } = readClientBuildAssets(root);
174
176
  const { pages, isgManifest } = await prerenderApp({
@@ -177,7 +179,8 @@ var build_default = defineCommand({
177
179
  cssManifest,
178
180
  jsManifest,
179
181
  registry: serverMod.registry,
180
- withISGManifest: true
182
+ withISGManifest: true,
183
+ concurrency: serverMod.prerenderConcurrency
181
184
  });
182
185
  const headersManifest = Object.fromEntries(pages.map((page) => [page.path, page.headers ?? {}]));
183
186
  if (pages.length > 0) {
@@ -200,6 +203,7 @@ var build_default = defineCommand({
200
203
  console.log(`\n ISG manifest → dist/server/isg-manifest.json (${Object.keys(isgManifest).length} route(s))\n`);
201
204
  }
202
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");
203
207
  console.log("\n Cloudflare worker → dist/server/server.js\n");
204
208
  console.log(" Deploy with: wrangler deploy\n");
205
209
  }
@@ -1,4 +1,4 @@
1
- import { t as runDoctor } from "./verification-Dwf1ZuNn.mjs";
1
+ import { t as runDoctor } from "./verification-ClzkW6_q.mjs";
2
2
  import { defineCommand } from "citty";
3
3
  //#region src/commands/doctor.ts
4
4
  var doctor_default = defineCommand({
@@ -1,4 +1,4 @@
1
- import { _ as requireEnum, a as readProjectConfig, c as resolveProjectPath, d as writeGeneratedFile, f as ensureTrailingNewline, g as quote, h as parseCommaList, l as resolveRouteModulePath, m as parseApiMethods, n as displayPath, o as resolveApiModulePath, s as resolvePagesRouteModulePath, t as assertFileExists, u as resolveScopedFile, v as requirePositiveInteger } from "./project-BdMiN3s7.mjs";
1
+ import { _ as requireEnum, a as readProjectConfig, c as resolveProjectPath, d as writeGeneratedFile, f as ensureTrailingNewline, g as quote, h as parseCommaList, l as resolveRouteModulePath, m as parseApiMethods, n as displayPath, o as resolveApiModulePath, s as resolvePagesRouteModulePath, t as assertFileExists, u as resolveScopedFile, v as requirePositiveInteger } from "./project-CfxHKBzP.mjs";
2
2
  import { a as toManifestModulePath, i as insertArrayItem, n as extractRegistryEntries, o as upsertObjectEntry, t as ensureCoreNamedImport } from "./manifest-Bs5hp3gA.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { readFileSync, writeFileSync } from "node:fs";
package/dist/index.mjs CHANGED
@@ -29,12 +29,12 @@ runMain(defineCommand({
29
29
  description: "The pracht CLI"
30
30
  },
31
31
  subCommands: {
32
- build: () => import("./build-DkP2ffu9.mjs").then((m) => m.default),
32
+ build: () => import("./build-CiFKxDlE.mjs").then((m) => m.default),
33
33
  dev: () => import("./dev-BCFe3g38.mjs").then((m) => m.default),
34
- doctor: () => import("./doctor-CwQJjJ83.mjs").then((m) => m.default),
35
- generate: () => import("./generate-C9AMSGsv.mjs").then((m) => m.default),
36
- inspect: () => import("./inspect-MseHPU9s.mjs").then((m) => m.default),
37
- verify: () => import("./verify-o0G5Xuff.mjs").then((m) => m.default)
34
+ doctor: () => import("./doctor-pY1BYaFH.mjs").then((m) => m.default),
35
+ generate: () => import("./generate-DqKWCmIu.mjs").then((m) => m.default),
36
+ inspect: () => import("./inspect-vYYIN8Ju.mjs").then((m) => m.default),
37
+ verify: () => import("./verify-eRkOdBaK.mjs").then((m) => m.default)
38
38
  }
39
39
  }));
40
40
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { t as HTTP_METHODS } from "./index.mjs";
2
2
  import { t as readClientBuildAssets } from "./build-metadata-BOChHO8g.mjs";
3
- import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-BdMiN3s7.mjs";
3
+ import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-CfxHKBzP.mjs";
4
4
  import { defineCommand } from "citty";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { resolve } from "node:path";
@@ -1,6 +1,6 @@
1
1
  import { n as PROJECT_DEFAULTS, t as HTTP_METHODS } from "./index.mjs";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
- import { basename, dirname, relative, resolve } from "node:path";
3
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
4
4
  //#region src/utils.ts
5
5
  function quote(value) {
6
6
  return JSON.stringify(value);
@@ -64,34 +64,47 @@ function resolveProjectPath(root, configPath) {
64
64
  return resolve(root, `.${configPath}`);
65
65
  }
66
66
  function resolveScopedFile(root, configDir, fileName) {
67
- return resolve(resolveProjectPath(root, configDir), fileName);
67
+ assertSafePathSegment(fileName.replace(/\.(ts|tsx|js|jsx)$/, ""));
68
+ const baseDir = resolveProjectPath(root, configDir);
69
+ const filePath = resolve(baseDir, fileName);
70
+ assertInsideDirectory(baseDir, filePath);
71
+ return filePath;
68
72
  }
69
73
  function resolveRouteModulePath(project, routePath, extension) {
70
74
  const segments = segmentsFromPath(routePath);
71
75
  const relativePath = segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
76
+ const baseDir = resolveProjectPath(project.root, project.routesDir);
77
+ const absolutePath = resolve(baseDir, relativePath);
78
+ assertInsideDirectory(baseDir, absolutePath);
72
79
  return {
73
- absolutePath: resolve(resolveProjectPath(project.root, project.routesDir), relativePath),
80
+ absolutePath,
74
81
  relativePath
75
82
  };
76
83
  }
77
84
  function resolvePagesRouteModulePath(project, routePath, extension) {
78
85
  const segments = segmentsFromPath(routePath);
79
86
  const relativePath = segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
87
+ const baseDir = resolveProjectPath(project.root, project.pagesDir);
88
+ const absolutePath = resolve(baseDir, relativePath);
89
+ assertInsideDirectory(baseDir, absolutePath);
80
90
  return {
81
- absolutePath: resolve(resolveProjectPath(project.root, project.pagesDir), relativePath),
91
+ absolutePath,
82
92
  relativePath
83
93
  };
84
94
  }
85
95
  function resolveApiModulePath(project, endpointPath) {
86
96
  const segments = segmentsFromPath(endpointPath);
87
97
  const relativePath = segments.length === 0 ? "index.ts" : `${segments.join("/")}.ts`;
98
+ const baseDir = resolveProjectPath(project.root, project.apiDir);
99
+ const absolutePath = resolve(baseDir, relativePath);
100
+ assertInsideDirectory(baseDir, absolutePath);
88
101
  return {
89
- absolutePath: resolve(resolveProjectPath(project.root, project.apiDir), relativePath),
102
+ absolutePath,
90
103
  relativePath
91
104
  };
92
105
  }
93
106
  function displayPath(root, filePath) {
94
- return relative(root, filePath) || ".";
107
+ return (relative(root, filePath) || ".").replace(/\\/g, "/");
95
108
  }
96
109
  function writeGeneratedFile(filePath, source) {
97
110
  if (existsSync(filePath)) throw new Error(`Refusing to overwrite existing file ${filePath}.`);
@@ -139,10 +152,23 @@ function normalizeConfigPath(value) {
139
152
  }
140
153
  function segmentsFromPath(path) {
141
154
  return path.split("/").filter(Boolean).map((segment) => {
142
- if (segment.startsWith(":")) return `[${segment.slice(1)}]`;
155
+ assertSafePathSegment(segment);
156
+ if (segment.startsWith(":")) {
157
+ const name = segment.endsWith("*") ? segment.slice(1, -1) : segment.slice(1);
158
+ assertSafePathSegment(name);
159
+ return segment.endsWith("*") ? `[...${name || "slug"}]` : `[${name}]`;
160
+ }
143
161
  if (segment === "*") return "[...slug]";
144
162
  return segment;
145
163
  });
146
164
  }
165
+ function assertSafePathSegment(segment) {
166
+ if (!segment || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || segment.includes("\0")) throw new Error(`Unsafe path segment: ${JSON.stringify(segment)}.`);
167
+ }
168
+ function assertInsideDirectory(baseDir, filePath) {
169
+ const relativePath = relative(baseDir, filePath);
170
+ if (relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath)) return;
171
+ throw new Error(`Refusing to write outside ${baseDir}.`);
172
+ }
147
173
  //#endregion
148
174
  export { requireEnum as _, readProjectConfig as a, resolveProjectPath as c, writeGeneratedFile as d, ensureTrailingNewline as f, quote as g, parseCommaList as h, listFilesRecursively as i, resolveRouteModulePath as l, parseApiMethods as m, displayPath as n, resolveApiModulePath as o, handleCliError as p, hasPagesAppShell as r, resolvePagesRouteModulePath as s, assertFileExists as t, resolveScopedFile as u, requirePositiveInteger as v };
@@ -1,7 +1,7 @@
1
- import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-BdMiN3s7.mjs";
1
+ import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-CfxHKBzP.mjs";
2
2
  import { n as extractRegistryEntries, r as extractRelativeModulePaths } from "./manifest-Bs5hp3gA.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
- import { basename, dirname, relative, resolve } from "node:path";
4
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
5
5
  import { execFileSync } from "node:child_process";
6
6
  //#region src/verification-helpers.ts
7
7
  const CONFIG_FILE_NAMES = new Set([
@@ -22,7 +22,7 @@ function createCheck(status, message) {
22
22
  }
23
23
  function isWithinDirectory(filePath, directoryPath) {
24
24
  const relativePath = relative(directoryPath, filePath);
25
- return relativePath === "" || !relativePath.startsWith("..") && !relativePath.startsWith("../");
25
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
26
26
  }
27
27
  function normalizePath(value) {
28
28
  return value.replace(/\\/g, "/");
@@ -42,6 +42,7 @@ function resolveApiRoutePath(apiDir, file) {
42
42
  relativePath = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
43
43
  if (relativePath === "index") relativePath = "";
44
44
  else relativePath = relativePath.replace(/\/index$/, "");
45
+ relativePath = relativePath.replace(/\[\.\.\.[^\]]+\]/g, "*");
45
46
  relativePath = relativePath.replace(/\[([^\]]+)\]/g, ":$1");
46
47
  return normalizeRoutePath(relativePath ? `/api/${relativePath}` : "/api");
47
48
  }
@@ -270,9 +271,9 @@ function collectChangedFiles(root) {
270
271
  if (!record) continue;
271
272
  if (record.includes(" -> ")) {
272
273
  const [from, to] = record.split(" -> ");
273
- addChangedFile(files, repoRoot, prefix, from);
274
- addChangedFile(files, repoRoot, prefix, to);
275
- } else addChangedFile(files, repoRoot, prefix, record);
274
+ addChangedFile(files, root, prefix, from);
275
+ addChangedFile(files, root, prefix, to);
276
+ } else addChangedFile(files, root, prefix, record);
276
277
  }
277
278
  return {
278
279
  files: [...files],
@@ -285,11 +286,11 @@ function collectChangedFiles(root) {
285
286
  };
286
287
  }
287
288
  }
288
- function addChangedFile(files, repoRoot, prefix, repoRelativePath) {
289
+ function addChangedFile(files, projectRoot, prefix, repoRelativePath) {
289
290
  if (prefix && !repoRelativePath.startsWith(prefix)) return;
290
291
  const projectRelativePath = prefix ? repoRelativePath.slice(prefix.length) : repoRelativePath;
291
292
  if (!projectRelativePath) return;
292
- files.add(resolve(repoRoot, projectRelativePath));
293
+ files.add(resolve(projectRoot, projectRelativePath));
293
294
  }
294
295
  function filterFrameworkFiles(project, files, packageJsonPath) {
295
296
  const appFile = resolveProjectPath(project.root, project.appFile);
@@ -1,4 +1,4 @@
1
- import { n as runVerification } from "./verification-Dwf1ZuNn.mjs";
1
+ import { n as runVerification } from "./verification-ClzkW6_q.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.2",
3
+ "version": "1.3.1",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/cli",
6
6
  "bugs": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "citty": "^0.1.6",
30
- "@pracht/core": "0.3.0"
30
+ "@pracht/core": "0.5.0"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsdown"