@pracht/cli 1.2.0 → 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,16 @@
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
+
3
14
  ## 1.2.0
4
15
 
5
16
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  import { r as VERSION } from "./index.mjs";
2
- import { t as readClientBuildAssets } from "./build-metadata-IABPFFKk.mjs";
2
+ 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";
@@ -157,6 +157,8 @@ var build_default = defineCommand({
157
157
  });
158
158
  }
159
159
  }
160
+ const publicDir = resolve(root, "public");
161
+ if (existsSync(publicDir)) cpSync(publicDir, clientDir, { recursive: true });
160
162
  if (existsSync(serverEntry)) {
161
163
  const serverMod = await import(serverEntry);
162
164
  const { prerenderApp } = serverMod;
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  //#region src/build-metadata.ts
4
4
  const MANIFEST_PATHS = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"];
5
+ const PRACHT_CLIENT_MODULE_QUERY = "pracht-client";
5
6
  function readClientBuildAssets(root = process.cwd()) {
6
7
  const manifestPath = MANIFEST_PATHS.map((candidate) => resolve(root, candidate)).find((path) => existsSync(path));
7
8
  if (!manifestPath) return {
@@ -36,8 +37,9 @@ function readClientBuildAssets(root = process.cwd()) {
36
37
  for (const [key, entry] of Object.entries(manifest)) {
37
38
  if (!entry.src) continue;
38
39
  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}`);
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}`);
41
43
  }
42
44
  return {
43
45
  clientEntryUrl: clientEntry ? `/${clientEntry.file}` : null,
@@ -45,5 +47,12 @@ function readClientBuildAssets(root = process.cwd()) {
45
47
  jsManifest
46
48
  };
47
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
+ }
48
57
  //#endregion
49
58
  export { readClientBuildAssets as t };
@@ -1,4 +1,4 @@
1
- import { t as runDoctor } from "./verification-Dfl3X4Zo.mjs";
1
+ import { t as runDoctor } from "./verification-CId4crSF.mjs";
2
2
  import { defineCommand } from "citty";
3
3
  //#region src/commands/doctor.ts
4
4
  var doctor_default = defineCommand({
@@ -1,5 +1,5 @@
1
1
  import { a as toManifestModulePath, i as insertArrayItem, n as extractRegistryEntries, o as upsertObjectEntry, t as ensureCoreNamedImport } from "./manifest-DGq1n5LT.mjs";
2
- 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-DydjqBoS.mjs";
2
+ 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-DUyH_7eJ.mjs";
3
3
  import { defineCommand } from "citty";
4
4
  import { readFileSync, writeFileSync } from "node:fs";
5
5
  var generate_default = defineCommand({
@@ -260,7 +260,8 @@ function generateApi(args, project) {
260
260
  updated: []
261
261
  };
262
262
  }
263
- function buildManifestRouteModuleSource({ includeErrorBoundary, includeLoader, includeStaticPaths, routePath, title }) {
263
+ function buildRouteModuleSections(opts) {
264
+ const { includeErrorBoundary, includeLoader, includeStaticPaths, routePath, title } = opts;
264
265
  const params = dynamicParamNames(routePath);
265
266
  const imports = [];
266
267
  const sections = [];
@@ -272,28 +273,23 @@ function buildManifestRouteModuleSource({ includeErrorBoundary, includeLoader, i
272
273
  }
273
274
  if (includeLoader) sections.push("export async function loader(_args: LoaderArgs) {", ` return { message: ${quote(`Welcome to ${title}.`)} };`, "}", "");
274
275
  if (includeStaticPaths) sections.push("export function getStaticPaths() {", ` return [${buildStaticPathsStub(params)}];`, "}", "");
275
- sections.push("export function head() {", ` return { title: ${quote(title)} };`, "}", "");
276
276
  if (includeLoader) sections.push("export function Component({ data }: RouteComponentProps<typeof loader>) {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " <p>{data.message}</p>", " </section>", " );", "}");
277
277
  else sections.push("export function Component() {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " </section>", " );", "}");
278
278
  if (includeErrorBoundary) sections.push("", "export function ErrorBoundary({ error }: ErrorBoundaryProps) {", " return <p>{error.message}</p>;", "}");
279
+ return sections;
280
+ }
281
+ function buildManifestRouteModuleSource(opts) {
282
+ const sections = buildRouteModuleSections(opts);
283
+ const componentIdx = sections.findIndex((s) => s.startsWith("export function Component"));
284
+ const insertAt = componentIdx === -1 ? sections.length : componentIdx;
285
+ sections.splice(insertAt, 0, "export function head() {", ` return { title: ${quote(opts.title)} };`, "}", "");
279
286
  return `${sections.join("\n")}\n`;
280
287
  }
281
- function buildPagesRouteModuleSource({ includeErrorBoundary, includeLoader, includeStaticPaths, render, routePath, title }) {
282
- const params = dynamicParamNames(routePath);
283
- const imports = [];
284
- const sections = [];
285
- if (includeLoader) imports.push("LoaderArgs", "RouteComponentProps");
286
- if (includeErrorBoundary) imports.push("ErrorBoundaryProps");
287
- if (imports.length > 0) {
288
- sections.push(`import type { ${imports.join(", ")} } from "@pracht/core";`);
289
- sections.push("");
290
- }
291
- sections.push(`export const RENDER_MODE = ${quote(render)};`, "");
292
- if (includeLoader) sections.push("export async function loader(_args: LoaderArgs) {", ` return { message: ${quote(`Welcome to ${title}.`)} };`, "}", "");
293
- if (includeStaticPaths) sections.push("export function getStaticPaths() {", ` return [${buildStaticPathsStub(params)}];`, "}", "");
294
- if (includeLoader) sections.push("export function Component({ data }: RouteComponentProps<typeof loader>) {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " <p>{data.message}</p>", " </section>", " );", "}");
295
- else sections.push("export function Component() {", " return (", " <section>", ` <h1>${escapeJsxText(title)}</h1>`, " </section>", " );", "}");
296
- if (includeErrorBoundary) sections.push("", "export function ErrorBoundary({ error }: ErrorBoundaryProps) {", " return <p>{error.message}</p>;", "}");
288
+ function buildPagesRouteModuleSource(opts) {
289
+ const sections = buildRouteModuleSections(opts);
290
+ const firstExportIdx = sections.findIndex((s) => s.startsWith("export"));
291
+ const insertAt = firstExportIdx === -1 ? sections.length : firstExportIdx;
292
+ sections.splice(insertAt, 0, `export const RENDER_MODE = ${quote(opts.render)};`, "");
297
293
  return `${sections.join("\n")}\n`;
298
294
  }
299
295
  function buildShellModuleSource(name) {
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-BzQAQjuy.mjs").then((m) => m.default),
32
+ build: () => import("./build-DlABFPbG.mjs").then((m) => m.default),
33
33
  dev: () => import("./dev-BCFe3g38.mjs").then((m) => m.default),
34
- doctor: () => import("./doctor-BcoqyBk-.mjs").then((m) => m.default),
35
- generate: () => import("./generate-BXkePCIx.mjs").then((m) => m.default),
36
- inspect: () => import("./inspect-DIjjCfs3.mjs").then((m) => m.default),
37
- verify: () => import("./verify-CObGxWtW.mjs").then((m) => m.default)
34
+ doctor: () => import("./doctor-CQq64tfW.mjs").then((m) => m.default),
35
+ generate: () => import("./generate-sHtRHRAH.mjs").then((m) => m.default),
36
+ inspect: () => import("./inspect-DP-h3vLw.mjs").then((m) => m.default),
37
+ verify: () => import("./verify-D7Bg1Su6.mjs").then((m) => m.default)
38
38
  }
39
39
  }));
40
40
  //#endregion
@@ -1,8 +1,8 @@
1
1
  import { t as HTTP_METHODS } from "./index.mjs";
2
- import { t as readClientBuildAssets } from "./build-metadata-IABPFFKk.mjs";
3
- import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-DydjqBoS.mjs";
2
+ import { t as readClientBuildAssets } from "./build-metadata-BOChHO8g.mjs";
3
+ import { a as readProjectConfig, c as resolveProjectPath, p as handleCliError } from "./project-DUyH_7eJ.mjs";
4
4
  import { defineCommand } from "citty";
5
- import { readFileSync } from "node:fs";
5
+ import { existsSync, readFileSync } from "node:fs";
6
6
  import { resolve } from "node:path";
7
7
  import { createServer } from "vite";
8
8
  //#region src/commands/inspect.ts
@@ -45,12 +45,7 @@ async function runInspect(root, { target = "all" } = {}) {
45
45
  if (!project.configFile) throw new Error("Missing vite config. `pracht inspect` requires a project with pracht configured.");
46
46
  if (!project.hasPrachtPlugin) throw new Error("vite.config does not appear to register the pracht plugin.");
47
47
  if (project.mode === "manifest") {
48
- const manifestPath = resolveProjectPath(project.root, project.appFile);
49
- try {
50
- readFileSync(manifestPath, "utf-8");
51
- } catch {
52
- throw new Error(`App manifest is missing at ${project.appFile}.`);
53
- }
48
+ if (!existsSync(resolveProjectPath(project.root, project.appFile))) throw new Error(`App manifest is missing at ${project.appFile}.`);
54
49
  }
55
50
  const server = await createServer({
56
51
  root,
@@ -67,7 +67,7 @@ function resolveScopedFile(root, configDir, fileName) {
67
67
  return resolve(resolveProjectPath(root, configDir), fileName);
68
68
  }
69
69
  function resolveRouteModulePath(project, routePath, extension) {
70
- const segments = segmentsFromRoutePath(routePath);
70
+ const segments = segmentsFromPath(routePath);
71
71
  const relativePath = segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
72
72
  return {
73
73
  absolutePath: resolve(resolveProjectPath(project.root, project.routesDir), relativePath),
@@ -75,7 +75,7 @@ function resolveRouteModulePath(project, routePath, extension) {
75
75
  };
76
76
  }
77
77
  function resolvePagesRouteModulePath(project, routePath, extension) {
78
- const segments = segmentsFromRoutePath(routePath);
78
+ const segments = segmentsFromPath(routePath);
79
79
  const relativePath = segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
80
80
  return {
81
81
  absolutePath: resolve(resolveProjectPath(project.root, project.pagesDir), relativePath),
@@ -83,7 +83,7 @@ function resolvePagesRouteModulePath(project, routePath, extension) {
83
83
  };
84
84
  }
85
85
  function resolveApiModulePath(project, endpointPath) {
86
- const segments = segmentsFromApiPath(endpointPath);
86
+ const segments = segmentsFromPath(endpointPath);
87
87
  const relativePath = segments.length === 0 ? "index.ts" : `${segments.join("/")}.ts`;
88
88
  return {
89
89
  absolutePath: resolve(resolveProjectPath(project.root, project.apiDir), relativePath),
@@ -137,15 +137,8 @@ function normalizeConfigPath(value) {
137
137
  if (!value) return value;
138
138
  return value.startsWith("/") ? value : `/${value}`;
139
139
  }
140
- function segmentsFromRoutePath(routePath) {
141
- return routePath.split("/").filter(Boolean).map((segment) => {
142
- if (segment.startsWith(":")) return `[${segment.slice(1)}]`;
143
- if (segment === "*") return "[...slug]";
144
- return segment;
145
- });
146
- }
147
- function segmentsFromApiPath(endpointPath) {
148
- return endpointPath.split("/").filter(Boolean).map((segment) => {
140
+ function segmentsFromPath(path) {
141
+ return path.split("/").filter(Boolean).map((segment) => {
149
142
  if (segment.startsWith(":")) return `[${segment.slice(1)}]`;
150
143
  if (segment === "*") return "[...slug]";
151
144
  return segment;
@@ -1,5 +1,5 @@
1
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-DydjqBoS.mjs";
2
+ import { a as readProjectConfig, c as resolveProjectPath, i as listFilesRecursively, n as displayPath, r as hasPagesAppShell } from "./project-DUyH_7eJ.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";
@@ -1,4 +1,4 @@
1
- import { n as runVerification } from "./verification-Dfl3X4Zo.mjs";
1
+ import { n as runVerification } from "./verification-CId4crSF.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.0",
3
+ "version": "1.2.1",
4
4
  "license": "MIT",
5
5
  "homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/cli",
6
6
  "bugs": {
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "citty": "^0.1.6",
23
- "@pracht/core": "0.2.6"
23
+ "@pracht/core": "0.2.7"
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsdown"
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
 
4
4
  const MANIFEST_PATHS = ["dist/client/.vite/manifest.json", "dist/.vite/manifest.json"];
5
+ const PRACHT_CLIENT_MODULE_QUERY = "pracht-client";
5
6
 
6
7
  interface ViteManifestEntry {
7
8
  css?: string[];
@@ -70,11 +71,12 @@ export function readClientBuildAssets(root: string = process.cwd()): ClientBuild
70
71
  if (!entry.src) continue;
71
72
 
72
73
  const deps = collectTransitiveDeps(key);
74
+ const manifestKey = stripPrachtClientModuleQuery(entry.src);
73
75
  if (deps.css.length > 0) {
74
- cssManifest[key] = deps.css.map((file) => `/${file}`);
76
+ cssManifest[manifestKey] = deps.css.map((file) => `/${file}`);
75
77
  }
76
78
  if (deps.js.length > 0) {
77
- jsManifest[key] = deps.js.map((file) => `/${file}`);
79
+ jsManifest[manifestKey] = deps.js.map((file) => `/${file}`);
78
80
  }
79
81
  }
80
82
 
@@ -84,3 +86,16 @@ export function readClientBuildAssets(root: string = process.cwd()): ClientBuild
84
86
  jsManifest,
85
87
  };
86
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,26 +1,10 @@
1
1
  import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- import { DEFAULT_SECURITY_HEADERS, VERSION } from "./constants.js";
4
+ import { VERSION } from "./constants.js";
5
5
 
6
6
  const ROUTE_STATE_REQUEST_HEADER = "x-pracht-route-state-request";
7
7
 
8
- interface HeaderSettable {
9
- setHeader(key: string, value: string): void;
10
- }
11
-
12
- export function setDefaultSecurityHeaders(
13
- res: HeaderSettable,
14
- headers: Record<string, string> = {},
15
- ): void {
16
- for (const [key, value] of Object.entries({
17
- ...DEFAULT_SECURITY_HEADERS,
18
- ...headers,
19
- })) {
20
- res.setHeader(key, value);
21
- }
22
- }
23
-
24
8
  interface VercelBuildOutputOptions {
25
9
  functionName?: string;
26
10
  headersManifest?: Record<string, Record<string, string>>;
@@ -53,6 +53,11 @@ export default defineCommand({
53
53
  }
54
54
  }
55
55
 
56
+ const publicDir = resolve(root, "public");
57
+ if (existsSync(publicDir)) {
58
+ cpSync(publicDir, clientDir, { recursive: true });
59
+ }
60
+
56
61
  if (existsSync(serverEntry)) {
57
62
  const serverMod = await import(serverEntry);
58
63
  const { prerenderApp } = serverMod;
@@ -361,19 +361,16 @@ function generateApi(args: ApiArgs, project: ProjectConfig): GenerateResult {
361
361
  };
362
362
  }
363
363
 
364
- function buildManifestRouteModuleSource({
365
- includeErrorBoundary,
366
- includeLoader,
367
- includeStaticPaths,
368
- routePath,
369
- title,
370
- }: {
364
+ interface RouteModuleParts {
371
365
  includeErrorBoundary: boolean;
372
366
  includeLoader: boolean;
373
367
  includeStaticPaths: boolean;
374
368
  routePath: string;
375
369
  title: string;
376
- }): string {
370
+ }
371
+
372
+ function buildRouteModuleSections(opts: RouteModuleParts): string[] {
373
+ const { includeErrorBoundary, includeLoader, includeStaticPaths, routePath, title } = opts;
377
374
  const params = dynamicParamNames(routePath);
378
375
  const imports: string[] = [];
379
376
  const sections: string[] = [];
@@ -408,8 +405,6 @@ function buildManifestRouteModuleSource({
408
405
  );
409
406
  }
410
407
 
411
- sections.push("export function head() {", ` return { title: ${quote(title)} };`, "}", "");
412
-
413
408
  if (includeLoader) {
414
409
  sections.push(
415
410
  "export function Component({ data }: RouteComponentProps<typeof loader>) {",
@@ -442,91 +437,34 @@ function buildManifestRouteModuleSource({
442
437
  );
443
438
  }
444
439
 
445
- return `${sections.join("\n")}\n`;
440
+ return sections;
446
441
  }
447
442
 
448
- function buildPagesRouteModuleSource({
449
- includeErrorBoundary,
450
- includeLoader,
451
- includeStaticPaths,
452
- render,
453
- routePath,
454
- title,
455
- }: {
456
- includeErrorBoundary: boolean;
457
- includeLoader: boolean;
458
- includeStaticPaths: boolean;
459
- render: string;
460
- routePath: string;
461
- title: string;
462
- }): string {
463
- const params = dynamicParamNames(routePath);
464
- const imports: string[] = [];
465
- const sections: string[] = [];
466
-
467
- if (includeLoader) {
468
- imports.push("LoaderArgs", "RouteComponentProps");
469
- }
470
- if (includeErrorBoundary) {
471
- imports.push("ErrorBoundaryProps");
472
- }
443
+ function buildManifestRouteModuleSource(opts: RouteModuleParts): string {
444
+ const sections = buildRouteModuleSections(opts);
473
445
 
474
- if (imports.length > 0) {
475
- sections.push(`import type { ${imports.join(", ")} } from "@pracht/core";`);
476
- sections.push("");
477
- }
478
-
479
- sections.push(`export const RENDER_MODE = ${quote(render)};`, "");
480
-
481
- if (includeLoader) {
482
- sections.push(
483
- "export async function loader(_args: LoaderArgs) {",
484
- ` return { message: ${quote(`Welcome to ${title}.`)} };`,
485
- "}",
486
- "",
487
- );
488
- }
446
+ // Insert head() before the Component export (after loader/getStaticPaths)
447
+ const componentIdx = sections.findIndex((s) => s.startsWith("export function Component"));
448
+ const insertAt = componentIdx === -1 ? sections.length : componentIdx;
449
+ sections.splice(
450
+ insertAt,
451
+ 0,
452
+ "export function head() {",
453
+ ` return { title: ${quote(opts.title)} };`,
454
+ "}",
455
+ "",
456
+ );
489
457
 
490
- if (includeStaticPaths) {
491
- sections.push(
492
- "export function getStaticPaths() {",
493
- ` return [${buildStaticPathsStub(params)}];`,
494
- "}",
495
- "",
496
- );
497
- }
458
+ return `${sections.join("\n")}\n`;
459
+ }
498
460
 
499
- if (includeLoader) {
500
- sections.push(
501
- "export function Component({ data }: RouteComponentProps<typeof loader>) {",
502
- " return (",
503
- " <section>",
504
- ` <h1>${escapeJsxText(title)}</h1>`,
505
- " <p>{data.message}</p>",
506
- " </section>",
507
- " );",
508
- "}",
509
- );
510
- } else {
511
- sections.push(
512
- "export function Component() {",
513
- " return (",
514
- " <section>",
515
- ` <h1>${escapeJsxText(title)}</h1>`,
516
- " </section>",
517
- " );",
518
- "}",
519
- );
520
- }
461
+ function buildPagesRouteModuleSource(opts: RouteModuleParts & { render: string }): string {
462
+ const sections = buildRouteModuleSections(opts);
521
463
 
522
- if (includeErrorBoundary) {
523
- sections.push(
524
- "",
525
- "export function ErrorBoundary({ error }: ErrorBoundaryProps) {",
526
- " return <p>{error.message}</p>;",
527
- "}",
528
- );
529
- }
464
+ // Insert RENDER_MODE before the first exported declaration (after imports)
465
+ const firstExportIdx = sections.findIndex((s) => s.startsWith("export"));
466
+ const insertAt = firstExportIdx === -1 ? sections.length : firstExportIdx;
467
+ sections.splice(insertAt, 0, `export const RENDER_MODE = ${quote(opts.render)};`, "");
530
468
 
531
469
  return `${sections.join("\n")}\n`;
532
470
  }
@@ -1,4 +1,4 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
 
4
4
  import { defineCommand } from "citty";
@@ -85,9 +85,7 @@ async function runInspect(root: string, { target = "all" } = {}): Promise<Inspec
85
85
 
86
86
  if (project.mode === "manifest") {
87
87
  const manifestPath = resolveProjectPath(project.root, project.appFile);
88
- try {
89
- readFileSync(manifestPath, "utf-8");
90
- } catch {
88
+ if (!existsSync(manifestPath)) {
91
89
  throw new Error(`App manifest is missing at ${project.appFile}.`);
92
90
  }
93
91
  }
package/src/constants.ts CHANGED
@@ -1,10 +1,6 @@
1
- export const DEFAULT_SECURITY_HEADERS: Record<string, string> = {
2
- "permissions-policy":
3
- "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()",
4
- "referrer-policy": "strict-origin-when-cross-origin",
5
- "x-content-type-options": "nosniff",
6
- "x-frame-options": "SAMEORIGIN",
7
- };
1
+ import type { HttpMethod } from "@pracht/core";
2
+
3
+ export type { HttpMethod };
8
4
 
9
5
  export const VERSION = "0.0.0";
10
6
 
@@ -19,8 +15,6 @@ export const PROJECT_DEFAULTS = {
19
15
  shellsDir: "/src/shells",
20
16
  };
21
17
 
22
- export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
23
-
24
18
  export const HTTP_METHODS = new Set<HttpMethod>([
25
19
  "GET",
26
20
  "POST",
package/src/project.ts CHANGED
@@ -56,7 +56,7 @@ export function resolveRouteModulePath(
56
56
  routePath: string,
57
57
  extension: string,
58
58
  ): { absolutePath: string; relativePath: string } {
59
- const segments = segmentsFromRoutePath(routePath);
59
+ const segments = segmentsFromPath(routePath);
60
60
  const relativePath =
61
61
  segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
62
62
  const absolutePath = resolve(resolveProjectPath(project.root, project.routesDir), relativePath);
@@ -68,7 +68,7 @@ export function resolvePagesRouteModulePath(
68
68
  routePath: string,
69
69
  extension: string,
70
70
  ): { absolutePath: string; relativePath: string } {
71
- const segments = segmentsFromRoutePath(routePath);
71
+ const segments = segmentsFromPath(routePath);
72
72
  const relativePath =
73
73
  segments.length === 0 ? `index${extension}` : `${segments.join("/")}${extension}`;
74
74
  const absolutePath = resolve(resolveProjectPath(project.root, project.pagesDir), relativePath);
@@ -79,7 +79,7 @@ export function resolveApiModulePath(
79
79
  project: ProjectConfig,
80
80
  endpointPath: string,
81
81
  ): { absolutePath: string; relativePath: string } {
82
- const segments = segmentsFromApiPath(endpointPath);
82
+ const segments = segmentsFromPath(endpointPath);
83
83
  const relativePath = segments.length === 0 ? "index.ts" : `${segments.join("/")}.ts`;
84
84
  const absolutePath = resolve(resolveProjectPath(project.root, project.apiDir), relativePath);
85
85
  return { absolutePath, relativePath };
@@ -148,19 +148,8 @@ function normalizeConfigPath(value: string): string {
148
148
  return value.startsWith("/") ? value : `/${value}`;
149
149
  }
150
150
 
151
- function segmentsFromRoutePath(routePath: string): string[] {
152
- return routePath
153
- .split("/")
154
- .filter(Boolean)
155
- .map((segment) => {
156
- if (segment.startsWith(":")) return `[${segment.slice(1)}]`;
157
- if (segment === "*") return "[...slug]";
158
- return segment;
159
- });
160
- }
161
-
162
- function segmentsFromApiPath(endpointPath: string): string[] {
163
- return endpointPath
151
+ function segmentsFromPath(path: string): string[] {
152
+ return path
164
153
  .split("/")
165
154
  .filter(Boolean)
166
155
  .map((segment) => {