@pracht/vite-plugin 0.7.4 → 0.8.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/README.md +8 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.mjs +248 -16
- package/dist/{pages-router-CStJY2Zp.mjs → pages-router-BQlG21oC.mjs} +78 -13
- package/dist/pages-router.d.mts +2 -0
- package/dist/pages-router.mjs +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -86,3 +86,11 @@ Target-specific Vite plugins (e.g. `@cloudflare/vite-plugin`) are pulled in by
|
|
|
86
86
|
the adapter package you install (`@pracht/adapter-cloudflare`,
|
|
87
87
|
`@pracht/adapter-vercel`, etc.). The default path uses `@pracht/adapter-node`,
|
|
88
88
|
which ships as a dependency of this package.
|
|
89
|
+
|
|
90
|
+
Custom adapters can expose two separate plugin hooks on `PrachtAdapter`:
|
|
91
|
+
|
|
92
|
+
- `vitePlugins()` for the platform's normal development and build integration.
|
|
93
|
+
- `graphVitePlugins()` for metadata-only CLI servers. This optional hook must
|
|
94
|
+
not start runtimes, listeners, persistent workers, or debuggers; use it only
|
|
95
|
+
for safe resolvers or platform-module stubs. When omitted, graph commands
|
|
96
|
+
load no adapter-contributed plugins.
|
package/dist/index.d.mts
CHANGED
|
@@ -70,6 +70,13 @@ interface PrachtAdapter {
|
|
|
70
70
|
* Returned plugins are appended to the plugin array returned by `pracht()`.
|
|
71
71
|
*/
|
|
72
72
|
vitePlugins?(): Plugin[];
|
|
73
|
+
/**
|
|
74
|
+
* Vite plugins that can safely run in the CLI's graph-only server. These
|
|
75
|
+
* plugins may provide runtime-module stubs or other metadata-only support,
|
|
76
|
+
* but must not start deployment runtimes, listeners, or persistent workers.
|
|
77
|
+
* When omitted, graph commands load no adapter-contributed plugins.
|
|
78
|
+
*/
|
|
79
|
+
graphVitePlugins?(): Plugin[];
|
|
73
80
|
/**
|
|
74
81
|
* If true, the adapter owns dev-server request handling and the vite-plugin
|
|
75
82
|
* will not install its own SSR middleware. Used when the adapter contributes
|
|
@@ -103,6 +110,19 @@ interface PrachtLlmsTxtOptions {
|
|
|
103
110
|
origin?: string;
|
|
104
111
|
/** Sections to emit. Defaults to ["pages", "api", "capabilities"]. */
|
|
105
112
|
include?: LlmsTxtSection[];
|
|
113
|
+
/**
|
|
114
|
+
* Route/API path patterns to leave out, using the same segment globs as
|
|
115
|
+
* `defineApp({ constraints })` (`*` = one segment, trailing `**` = the
|
|
116
|
+
* rest). llms.txt invites agents to fetch every URL it lists, so exclude
|
|
117
|
+
* anything an anonymous agent cannot use — pages behind an auth middleware,
|
|
118
|
+
* internal tooling, deliberate error routes. Capabilities are matched by
|
|
119
|
+
* their dispatch path (`/api/capabilities/**`).
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* llmsTxt: { exclude: ["/dashboard", "/admin/**"] }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
exclude?: string[];
|
|
106
126
|
}
|
|
107
127
|
interface PrachtPluginOptions {
|
|
108
128
|
appFile?: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { i as scanPagesDirectory, n as generatePagesManifestSource, o as createRouteLoaderHints } from "./pages-router-
|
|
2
|
-
import { createRequire } from "node:module";
|
|
1
|
+
import { i as scanPagesDirectory, n as generatePagesManifestSource, o as createRouteLoaderHints } from "./pages-router-BQlG21oC.mjs";
|
|
2
|
+
import { createRequire, isBuiltin } from "node:module";
|
|
3
3
|
import { preactSsrPrecompile } from "@pracht/preact-ssr-precompile";
|
|
4
4
|
import preact from "@preact/preset-vite";
|
|
5
5
|
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
6
6
|
import { dirname, extname, join, resolve } from "node:path";
|
|
7
7
|
import { loadEnv, parseAst } from "vite";
|
|
8
|
+
import { PRACHT_GRAPH_ONLY_ENV } from "@pracht/core/server";
|
|
8
9
|
import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER } from "@pracht/capabilities";
|
|
9
|
-
import { extractCapabilityProjection, extractCapabilityRegistrations } from "@pracht/capabilities/static";
|
|
10
|
+
import { extractCapabilityProjection, extractCapabilityRegistrations, extractDefineAppObjectBody, scanTopLevelProperties } from "@pracht/capabilities/static";
|
|
10
11
|
import { createNodeServerEntryModule } from "@pracht/adapter-node";
|
|
11
|
-
import { resolveRegistryModule } from "@pracht/core";
|
|
12
|
+
import { applyDefaultSecurityHeaders, resolveRegistryModule } from "@pracht/core";
|
|
12
13
|
//#region src/client-module-query.ts
|
|
13
14
|
const CLIENT_MODULE_QUERY = "pracht-client";
|
|
14
15
|
const PRACHT_CLIENT_MODULE_QUERY = `?${CLIENT_MODULE_QUERY}`;
|
|
@@ -1367,6 +1368,12 @@ function resolveOptions(options) {
|
|
|
1367
1368
|
...options
|
|
1368
1369
|
};
|
|
1369
1370
|
if (resolved.llmsTxt === void 0) resolved.llmsTxt = false;
|
|
1371
|
+
if (!new Set([
|
|
1372
|
+
"spa",
|
|
1373
|
+
"ssr",
|
|
1374
|
+
"ssg",
|
|
1375
|
+
"isg"
|
|
1376
|
+
]).has(resolved.pagesDefaultRender)) throw new Error("pracht({ pagesDefaultRender }) expects \"spa\", \"ssr\", \"ssg\", or \"isg\".");
|
|
1370
1377
|
if (!Number.isInteger(resolved.prerenderConcurrency) || resolved.prerenderConcurrency <= 0) throw new Error("pracht({ prerenderConcurrency }) expects a positive integer.");
|
|
1371
1378
|
if (!Number.isInteger(resolved.maxBodySize) || resolved.maxBodySize <= 0) throw new Error("pracht({ maxBodySize }) expects a positive integer number of bytes.");
|
|
1372
1379
|
validateBudgets(resolved.budgets);
|
|
@@ -1416,6 +1423,82 @@ function validateBudgets(budgets) {
|
|
|
1416
1423
|
* rather than silently dropping an endpoint.
|
|
1417
1424
|
*/
|
|
1418
1425
|
/**
|
|
1426
|
+
* Whether the app can reach the agent surface at all — registered capabilities
|
|
1427
|
+
* or a `defineApp({ agents })` config. Drives the `__PRACHT_AGENT_SURFACE__`
|
|
1428
|
+
* define, which lets the bundler drop the capability and Web Bot Auth runtimes
|
|
1429
|
+
* from the server bundle of apps that use neither.
|
|
1430
|
+
*
|
|
1431
|
+
* Deliberately one-sided: it only answers `false` when the manifest is readable
|
|
1432
|
+
* and provably free of both. An unreadable manifest, a parse failure, or any
|
|
1433
|
+
* spread inside the manifest file (which could carry registrations this
|
|
1434
|
+
* analyzer cannot see) answers `true`, so the runtime keeps deciding for
|
|
1435
|
+
* itself. Being wrong the other way would 404 a capability in production that
|
|
1436
|
+
* works in dev.
|
|
1437
|
+
*/
|
|
1438
|
+
function hasAgentSurface(options = {}, root = process.cwd()) {
|
|
1439
|
+
const resolved = resolveOptions(options);
|
|
1440
|
+
if (resolved.pagesDir) return false;
|
|
1441
|
+
const appFileAbs = resolve(root, resolved.appFile.replace(/^\//, ""));
|
|
1442
|
+
let manifestSource;
|
|
1443
|
+
try {
|
|
1444
|
+
manifestSource = readFileSync(appFileAbs, "utf-8");
|
|
1445
|
+
} catch {
|
|
1446
|
+
return true;
|
|
1447
|
+
}
|
|
1448
|
+
const appBody = extractDefineAppObjectBody(manifestSource);
|
|
1449
|
+
if (appBody === null) return true;
|
|
1450
|
+
const properties = scanTopLevelProperties(appBody);
|
|
1451
|
+
if (properties.has("agents") || properties.has("capabilities")) return true;
|
|
1452
|
+
if (/\b(?:agents|capabilities)\b/.test(appBody)) return true;
|
|
1453
|
+
if (appBody.includes("...") || hasOpaqueTopLevelProperty(appBody)) return true;
|
|
1454
|
+
try {
|
|
1455
|
+
return extractCapabilityRegistrations(manifestSource).length > 0;
|
|
1456
|
+
} catch {
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/** Whether an object literal body contains an opaque key at its top level. */
|
|
1461
|
+
function hasOpaqueTopLevelProperty(objectBody) {
|
|
1462
|
+
let braces = 0;
|
|
1463
|
+
let brackets = 0;
|
|
1464
|
+
let parentheses = 0;
|
|
1465
|
+
let expectingKey = true;
|
|
1466
|
+
for (let index = 0; index < objectBody.length; index += 1) {
|
|
1467
|
+
const char = objectBody[index];
|
|
1468
|
+
const next = objectBody[index + 1];
|
|
1469
|
+
if (char === "\"" || char === "'" || char === "`") {
|
|
1470
|
+
const quote = char;
|
|
1471
|
+
for (index += 1; index < objectBody.length; index += 1) if (objectBody[index] === "\\") index += 1;
|
|
1472
|
+
else if (objectBody[index] === quote) break;
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
if (char === "/" && next === "/") {
|
|
1476
|
+
index = objectBody.indexOf("\n", index + 2);
|
|
1477
|
+
if (index === -1) break;
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
if (char === "/" && next === "*") {
|
|
1481
|
+
const end = objectBody.indexOf("*/", index + 2);
|
|
1482
|
+
if (end === -1) return true;
|
|
1483
|
+
index = end + 1;
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
if (char === "/") return true;
|
|
1487
|
+
const atTopLevel = braces === 0 && brackets === 0 && parentheses === 0;
|
|
1488
|
+
if (atTopLevel && expectingKey && char === "[") return true;
|
|
1489
|
+
if (atTopLevel && expectingKey && char === "\\") return true;
|
|
1490
|
+
if (atTopLevel && char === ":") expectingKey = false;
|
|
1491
|
+
if (atTopLevel && char === ",") expectingKey = true;
|
|
1492
|
+
if (char === "{") braces += 1;
|
|
1493
|
+
else if (char === "}") braces -= 1;
|
|
1494
|
+
else if (char === "[") brackets += 1;
|
|
1495
|
+
else if (char === "]") brackets -= 1;
|
|
1496
|
+
else if (char === "(") parentheses += 1;
|
|
1497
|
+
else if (char === ")") parentheses -= 1;
|
|
1498
|
+
}
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1419
1502
|
* Extract capability registrations (name → module path) from the app
|
|
1420
1503
|
* manifest source and their exposure metadata from each capability source.
|
|
1421
1504
|
* Pages-router apps have no manifest, so capabilities are manifest-mode only.
|
|
@@ -1489,7 +1572,7 @@ function extractCapabilityMetadata(name, file, source) {
|
|
|
1489
1572
|
*/
|
|
1490
1573
|
function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions = {}) {
|
|
1491
1574
|
const capabilities = extractCapabilities(options, buildOptions.root);
|
|
1492
|
-
const endpoints =
|
|
1575
|
+
const endpoints = Object.create(null);
|
|
1493
1576
|
for (const capability of capabilities) if (capability.httpPath) endpoints[capability.name] = {
|
|
1494
1577
|
method: "POST",
|
|
1495
1578
|
path: capability.httpPath,
|
|
@@ -1499,7 +1582,9 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1499
1582
|
"// Generated by @pracht/vite-plugin from the app manifest capability registrations.",
|
|
1500
1583
|
"// Contains only http-exposed capability names, endpoints, and effects —",
|
|
1501
1584
|
"// capability modules themselves are server-only and never reach the client graph.",
|
|
1502
|
-
|
|
1585
|
+
"import { createUseCapability } from \"@pracht/core\";",
|
|
1586
|
+
"",
|
|
1587
|
+
`const endpoints = Object.assign(Object.create(null), JSON.parse(${JSON.stringify(JSON.stringify(endpoints))}));`,
|
|
1503
1588
|
"",
|
|
1504
1589
|
"export const capabilityEndpoints = endpoints;",
|
|
1505
1590
|
"",
|
|
@@ -1508,7 +1593,9 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1508
1593
|
" try {",
|
|
1509
1594
|
" const headers = new Headers(opts && opts.headers);",
|
|
1510
1595
|
" headers.set(\"content-type\", \"application/json\");",
|
|
1511
|
-
" if (opts && opts.
|
|
1596
|
+
" if (opts && opts.prepare) {",
|
|
1597
|
+
` headers.delete(${JSON.stringify(CONFIRMATION_HEADER)});`,
|
|
1598
|
+
" } else if (opts && opts.confirm) {",
|
|
1512
1599
|
` headers.set(${JSON.stringify(CONFIRMATION_HEADER)}, opts.confirm);`,
|
|
1513
1600
|
" }",
|
|
1514
1601
|
" response = await fetch(endpoint.path, {",
|
|
@@ -1524,8 +1611,9 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1524
1611
|
" error: { code: \"network_error\", message: String((error && error.message) || error) },",
|
|
1525
1612
|
" };",
|
|
1526
1613
|
" }",
|
|
1614
|
+
" let result;",
|
|
1527
1615
|
" try {",
|
|
1528
|
-
"
|
|
1616
|
+
" result = await response.json();",
|
|
1529
1617
|
" } catch {",
|
|
1530
1618
|
" return {",
|
|
1531
1619
|
" ok: false,",
|
|
@@ -1535,6 +1623,23 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1535
1623
|
" },",
|
|
1536
1624
|
" };",
|
|
1537
1625
|
" }",
|
|
1626
|
+
" if (",
|
|
1627
|
+
" !result || typeof result !== \"object\" ||",
|
|
1628
|
+
" (result.ok !== true && result.ok !== false) ||",
|
|
1629
|
+
" (result.ok === true && !(\"data\" in result)) ||",
|
|
1630
|
+
" (result.ok === false &&",
|
|
1631
|
+
" (!result.error || typeof result.error !== \"object\" ||",
|
|
1632
|
+
" typeof result.error.code !== \"string\" || typeof result.error.message !== \"string\"))",
|
|
1633
|
+
" ) {",
|
|
1634
|
+
" return {",
|
|
1635
|
+
" ok: false,",
|
|
1636
|
+
" error: {",
|
|
1637
|
+
" code: \"invalid_response\",",
|
|
1638
|
+
" message: `Capability endpoint returned an invalid envelope (status ${response.status}).`,",
|
|
1639
|
+
" },",
|
|
1640
|
+
" };",
|
|
1641
|
+
" }",
|
|
1642
|
+
" return result;",
|
|
1538
1643
|
"}",
|
|
1539
1644
|
"",
|
|
1540
1645
|
"export async function callCapability(name, input, opts) {",
|
|
@@ -1565,6 +1670,38 @@ function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions =
|
|
|
1565
1670
|
" } catch {}",
|
|
1566
1671
|
" return result;",
|
|
1567
1672
|
"}",
|
|
1673
|
+
"",
|
|
1674
|
+
"// Nested client: dotted capability names become object paths, so",
|
|
1675
|
+
"// `capabilities.notes.search(input)` calls `callCapability(\"notes.search\", input)`.",
|
|
1676
|
+
"// Built from the same endpoint table, so there is one dispatch path.",
|
|
1677
|
+
"function buildCapabilityClient(names) {",
|
|
1678
|
+
" const root = Object.create(null);",
|
|
1679
|
+
" for (const name of names) {",
|
|
1680
|
+
" const segments = name.split(\".\");",
|
|
1681
|
+
" const leaf = segments.pop();",
|
|
1682
|
+
" let node = root;",
|
|
1683
|
+
" for (const segment of segments) {",
|
|
1684
|
+
" // A name that is both a namespace and a leaf (`a` plus `a.b`) would",
|
|
1685
|
+
" // collide; the namespace wins and the leaf stays reachable through",
|
|
1686
|
+
" // callCapability(). `pracht verify` reports the shadowed name.",
|
|
1687
|
+
" if (typeof node[segment] !== \"object\" || node[segment] === null) {",
|
|
1688
|
+
" node[segment] = Object.create(null);",
|
|
1689
|
+
" }",
|
|
1690
|
+
" node = node[segment];",
|
|
1691
|
+
" }",
|
|
1692
|
+
" if (typeof node[leaf] !== \"object\") {",
|
|
1693
|
+
" node[leaf] = (input, opts) => callCapability(name, input, opts);",
|
|
1694
|
+
" }",
|
|
1695
|
+
" }",
|
|
1696
|
+
" return root;",
|
|
1697
|
+
"}",
|
|
1698
|
+
"",
|
|
1699
|
+
"export const capabilities = /*@__PURE__*/ buildCapabilityClient(Object.keys(endpoints));",
|
|
1700
|
+
"",
|
|
1701
|
+
"// The hook's implementation lives in @pracht/core (typed and unit-tested);",
|
|
1702
|
+
"// only the app-specific dispatch is bound here, so every projection shares",
|
|
1703
|
+
"// one call path. Pure-annotated: apps that never call it pay nothing.",
|
|
1704
|
+
"export const useCapability = /*@__PURE__*/ createUseCapability(callCapability);",
|
|
1568
1705
|
""
|
|
1569
1706
|
].join("\n");
|
|
1570
1707
|
}
|
|
@@ -1884,6 +2021,7 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1884
2021
|
};
|
|
1885
2022
|
const adapter = resolved.adapter;
|
|
1886
2023
|
const llmsTxtConfig = resolveLlmsTxtConfig(resolved, buildOptions.root);
|
|
2024
|
+
const islandsBootstrapRequired = hasWebmcpCapabilities(resolved, buildOptions.root);
|
|
1887
2025
|
let prachtImports = adapter?.serverImports ? adapter.serverImports + "\nimport { prerenderApp } from \"@pracht/core/server\";" : "import { resolveApp, resolveApiRoutes, prerenderApp } from \"@pracht/core/server\";";
|
|
1888
2026
|
if (llmsTxtConfig) prachtImports += "\nimport { buildLlmsTxt } from \"@pracht/core/server\";";
|
|
1889
2027
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
@@ -1911,6 +2049,7 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1911
2049
|
`export const buildTarget = ${JSON.stringify(adapter?.id ?? "node")};`,
|
|
1912
2050
|
`export const clientEntryUrl = ${JSON.stringify(clientBuild.clientEntryUrl ?? "/@pracht/client.js")};`,
|
|
1913
2051
|
`export const islandsEntryUrl = ${JSON.stringify(islandsEntryUrl ?? null)};`,
|
|
2052
|
+
`export const islandsBootstrapRequired = ${JSON.stringify(islandsBootstrapRequired)};`,
|
|
1914
2053
|
`export const cssManifest = ${JSON.stringify(clientBuild.cssManifest)};`,
|
|
1915
2054
|
`export const jsManifest = ${JSON.stringify(clientBuild.jsManifest)};`,
|
|
1916
2055
|
`export const prerenderConcurrency = ${JSON.stringify(resolved.prerenderConcurrency)};`,
|
|
@@ -1937,12 +2076,14 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1937
2076
|
function createPrachtDevModuleSource(options = {}, buildOptions = {}) {
|
|
1938
2077
|
const resolved = resolveOptions(options);
|
|
1939
2078
|
return [
|
|
1940
|
-
"import { resolveApp } from \"@pracht/core/server\";",
|
|
2079
|
+
"import { resolveApp, resolveApiRoutes } from \"@pracht/core/server\";",
|
|
1941
2080
|
resolved.pagesDir ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`,
|
|
1942
2081
|
"",
|
|
1943
2082
|
createPrachtRegistryModuleSource(resolved),
|
|
1944
2083
|
"",
|
|
1945
2084
|
"export const resolvedApp = resolveApp(app);",
|
|
2085
|
+
`export const apiRoutes = resolveApiRoutes(Object.keys(apiModules), ${JSON.stringify(resolved.apiDir)});`,
|
|
2086
|
+
`export const buildTarget = ${JSON.stringify(resolved.adapter?.id ?? "node")};`,
|
|
1946
2087
|
""
|
|
1947
2088
|
].join("\n");
|
|
1948
2089
|
}
|
|
@@ -1962,6 +2103,7 @@ function resolveLlmsTxtConfig(resolved, root = process.cwd()) {
|
|
|
1962
2103
|
if (description) config.description = description;
|
|
1963
2104
|
if (resolved.llmsTxt.origin) config.origin = resolved.llmsTxt.origin;
|
|
1964
2105
|
if (resolved.llmsTxt.include) config.include = resolved.llmsTxt.include;
|
|
2106
|
+
if (resolved.llmsTxt.exclude?.length) config.exclude = resolved.llmsTxt.exclude;
|
|
1965
2107
|
return config;
|
|
1966
2108
|
}
|
|
1967
2109
|
function createApplyRouteLoaderHintsSource() {
|
|
@@ -1997,6 +2139,7 @@ function createRouteLoaderHintsForVirtualModules(options, root = process.cwd())
|
|
|
1997
2139
|
}
|
|
1998
2140
|
function createPrachtRegistryModuleSource(options = {}) {
|
|
1999
2141
|
const resolved = resolveOptions(options);
|
|
2142
|
+
const apiGlobs = [`${resolved.apiDir}/**/*.{ts,js,tsx,jsx}`, `!${resolved.apiDir}/**/*.d.ts`];
|
|
2000
2143
|
const isPagesMode = !!resolved.pagesDir;
|
|
2001
2144
|
const routeGlob = isPagesMode ? `${resolved.pagesDir}/**/*.{ts,tsx,js,jsx,md,mdx}` : `${resolved.routesDir}/**/*.{ts,tsx,js,jsx,md,mdx}`;
|
|
2002
2145
|
const routeTsrxGlob = isPagesMode ? `${resolved.pagesDir}/**/*.tsrx` : `${resolved.routesDir}/**/*.tsrx`;
|
|
@@ -2012,7 +2155,7 @@ function createPrachtRegistryModuleSource(options = {}) {
|
|
|
2012
2155
|
` ...import.meta.glob(${JSON.stringify(shellTsrxGlob)}),`,
|
|
2013
2156
|
`};`,
|
|
2014
2157
|
`export const middlewareModules = import.meta.glob(${JSON.stringify(`${resolved.middlewareDir}/**/*.{ts,tsx,js,jsx}`)});`,
|
|
2015
|
-
`export const apiModules = import.meta.glob(${JSON.stringify(
|
|
2158
|
+
`export const apiModules = import.meta.glob(${JSON.stringify(apiGlobs)});`,
|
|
2016
2159
|
`export const dataModules = import.meta.glob(${JSON.stringify(`${resolved.serverDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
2017
2160
|
`export const capabilityModules = import.meta.glob(${JSON.stringify(`${resolved.capabilitiesDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
2018
2161
|
"",
|
|
@@ -2093,6 +2236,9 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2093
2236
|
const llmsTxt = await serverMod.generateLlmsTxt();
|
|
2094
2237
|
res.statusCode = 200;
|
|
2095
2238
|
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
2239
|
+
applyDefaultSecurityHeaders(new Headers()).forEach((value, key) => {
|
|
2240
|
+
res.setHeader(key, value);
|
|
2241
|
+
});
|
|
2096
2242
|
res.end(llmsTxt);
|
|
2097
2243
|
return;
|
|
2098
2244
|
}
|
|
@@ -2116,12 +2262,14 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
2116
2262
|
request: webRequest,
|
|
2117
2263
|
debugErrors: true,
|
|
2118
2264
|
clientEntryUrl: CLIENT_BROWSER_PATH,
|
|
2265
|
+
islandsEntryUrl: ISLANDS_CLIENT_BROWSER_PATH,
|
|
2266
|
+
islandsBootstrapRequired: serverMod.islandsBootstrapRequired === true,
|
|
2119
2267
|
apiRoutes: serverMod.apiRoutes,
|
|
2120
2268
|
timings
|
|
2121
2269
|
});
|
|
2122
2270
|
const responseContentType = response.headers.get("content-type") ?? "";
|
|
2123
2271
|
if (response.status === 404 && !responseContentType.includes("application/json") && !routeMatchers.app?.notFound) return next();
|
|
2124
|
-
const contentType = response.headers.get("content-type") ?? "
|
|
2272
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
2125
2273
|
let body = await response.text();
|
|
2126
2274
|
if (contentType.includes("text/html")) body = await server.transformIndexHtml(url, body);
|
|
2127
2275
|
res.statusCode = response.status;
|
|
@@ -2442,6 +2590,8 @@ const DEV_ASSET_EXTENSIONS = new Set([
|
|
|
2442
2590
|
".js",
|
|
2443
2591
|
".json",
|
|
2444
2592
|
".map",
|
|
2593
|
+
".markdown",
|
|
2594
|
+
".md",
|
|
2445
2595
|
".mjs",
|
|
2446
2596
|
".pdf",
|
|
2447
2597
|
".png",
|
|
@@ -2500,13 +2650,18 @@ function pracht(options = {}) {
|
|
|
2500
2650
|
const isEdge = resolved.adapter.edge === true;
|
|
2501
2651
|
const isSSRBuild = env.isSsrBuild;
|
|
2502
2652
|
const configRoot = _config.root ?? process.cwd();
|
|
2503
|
-
const wantsIslandsEntry = env.command === "build" && !isSSRBuild && existsSync(resolveConfigPath(configRoot, resolved.islandsDir));
|
|
2653
|
+
const wantsIslandsEntry = env.command === "build" && !isSSRBuild && (existsSync(resolveConfigPath(configRoot, resolved.islandsDir)) || hasWebmcpCapabilities(resolved, configRoot));
|
|
2504
2654
|
const envDir = _config.envDir ? resolve(configRoot, _config.envDir) : configRoot;
|
|
2505
2655
|
const publicEnvDefine = JSON.stringify(loadEnv(env.mode, envDir, PUBLIC_ENV_PREFIX));
|
|
2656
|
+
const agentSurfaceDefine = env.command === "build" ? String(hasAgentSurface(resolved, configRoot)) : "true";
|
|
2506
2657
|
return {
|
|
2507
2658
|
appType: "custom",
|
|
2508
2659
|
envPrefix: ["VITE_", PUBLIC_ENV_PREFIX],
|
|
2509
|
-
|
|
2660
|
+
resolve: { dedupe: PREACT_DEDUPE },
|
|
2661
|
+
define: {
|
|
2662
|
+
__PRACHT_PUBLIC_ENV__: publicEnvDefine,
|
|
2663
|
+
__PRACHT_AGENT_SURFACE__: agentSurfaceDefine
|
|
2664
|
+
},
|
|
2510
2665
|
...isSSRBuild ? {} : { build: { rollupOptions: {
|
|
2511
2666
|
...wantsIslandsEntry ? { input: [PRACHT_ISLANDS_CLIENT_MODULE_ID] } : {},
|
|
2512
2667
|
output: { manualChunks(id) {
|
|
@@ -2518,6 +2673,15 @@ function pracht(options = {}) {
|
|
|
2518
2673
|
noExternal: true,
|
|
2519
2674
|
target: "webworker"
|
|
2520
2675
|
},
|
|
2676
|
+
environments: { ssr: { resolve: {
|
|
2677
|
+
conditions: [
|
|
2678
|
+
"worker",
|
|
2679
|
+
"module",
|
|
2680
|
+
"browser",
|
|
2681
|
+
"development|production"
|
|
2682
|
+
],
|
|
2683
|
+
external: ["node:module"]
|
|
2684
|
+
} } },
|
|
2521
2685
|
build: { rollupOptions: { external: [/^cloudflare:/] } }
|
|
2522
2686
|
} : {}
|
|
2523
2687
|
};
|
|
@@ -2643,6 +2807,7 @@ function pracht(options = {}) {
|
|
|
2643
2807
|
};
|
|
2644
2808
|
}
|
|
2645
2809
|
};
|
|
2810
|
+
const edgeRuntimeSafetyPlugin = resolved.adapter.edge ? createEdgeRuntimeSafetyPlugin() : null;
|
|
2646
2811
|
const optimizeDepsEntriesPlugin = {
|
|
2647
2812
|
name: "pracht:optimize-deps-entries",
|
|
2648
2813
|
enforce: "post",
|
|
@@ -2659,13 +2824,77 @@ function pracht(options = {}) {
|
|
|
2659
2824
|
...preact(),
|
|
2660
2825
|
prachtPlugin,
|
|
2661
2826
|
clientModuleTransformPlugin,
|
|
2827
|
+
...edgeRuntimeSafetyPlugin ? [edgeRuntimeSafetyPlugin] : [],
|
|
2662
2828
|
createEnvSafetyPlugin(resolved.envSafety)
|
|
2663
2829
|
];
|
|
2664
|
-
const adapterPlugins = resolved.adapter.vitePlugins?.();
|
|
2830
|
+
const adapterPlugins = isGraphOnlyMode() ? resolved.adapter.graphVitePlugins?.() : resolved.adapter.vitePlugins?.();
|
|
2665
2831
|
if (adapterPlugins?.length) plugins.push(...adapterPlugins);
|
|
2666
2832
|
plugins.push(optimizeDepsEntriesPlugin);
|
|
2667
2833
|
return plugins;
|
|
2668
2834
|
}
|
|
2835
|
+
function isGraphOnlyMode() {
|
|
2836
|
+
return process.env[PRACHT_GRAPH_ONLY_ENV] === "1";
|
|
2837
|
+
}
|
|
2838
|
+
function createEdgeRuntimeSafetyPlugin() {
|
|
2839
|
+
let isSsrBuild = false;
|
|
2840
|
+
return {
|
|
2841
|
+
name: "pracht:edge-runtime-safety",
|
|
2842
|
+
apply: "build",
|
|
2843
|
+
enforce: "post",
|
|
2844
|
+
configResolved(config) {
|
|
2845
|
+
isSsrBuild = !!config.build.ssr;
|
|
2846
|
+
},
|
|
2847
|
+
generateBundle(_options, bundle) {
|
|
2848
|
+
const consumer = this.environment?.config?.consumer;
|
|
2849
|
+
if (!(consumer ? consumer === "server" : isSsrBuild)) return;
|
|
2850
|
+
const survivors = [];
|
|
2851
|
+
for (const [fileName, output] of Object.entries(bundle)) {
|
|
2852
|
+
if (output.type !== "chunk") continue;
|
|
2853
|
+
for (const specifier of collectNodeBuiltinImports(this.parse(output.code))) survivors.push({
|
|
2854
|
+
chunk: fileName,
|
|
2855
|
+
specifier
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
if (survivors.length === 0) return;
|
|
2859
|
+
this.error([
|
|
2860
|
+
"[pracht] Edge server bundle retains Node.js builtin imports that are unavailable at runtime:",
|
|
2861
|
+
...survivors.map(({ chunk, specifier }) => ` - ${specifier} in ${chunk}`),
|
|
2862
|
+
"Remove the Node-only dependency or move that route to a Node deployment target."
|
|
2863
|
+
].join("\n"));
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
function collectNodeBuiltinImports(program) {
|
|
2868
|
+
const imports = /* @__PURE__ */ new Set();
|
|
2869
|
+
function sourceValue(node) {
|
|
2870
|
+
if (!node || typeof node !== "object" || !("value" in node)) return null;
|
|
2871
|
+
return typeof node.value === "string" ? node.value : null;
|
|
2872
|
+
}
|
|
2873
|
+
function visit(node) {
|
|
2874
|
+
if (Array.isArray(node)) {
|
|
2875
|
+
for (const item of node) visit(item);
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
if (!node || typeof node !== "object") return;
|
|
2879
|
+
const record = node;
|
|
2880
|
+
const type = record.type;
|
|
2881
|
+
if (type === "ImportDeclaration" || type === "ExportAllDeclaration" || type === "ExportNamedDeclaration" || type === "ImportExpression") {
|
|
2882
|
+
const specifier = sourceValue(record.source);
|
|
2883
|
+
if (specifier && isBuiltin(specifier)) imports.add(specifier);
|
|
2884
|
+
} else if (type === "CallExpression") {
|
|
2885
|
+
const callee = record.callee;
|
|
2886
|
+
const isImport = callee?.type === "Import";
|
|
2887
|
+
const isRequire = callee?.type === "Identifier" && callee.name === "require";
|
|
2888
|
+
if (isImport || isRequire) {
|
|
2889
|
+
const specifier = sourceValue(record.arguments?.[0]);
|
|
2890
|
+
if (specifier && isBuiltin(specifier)) imports.add(specifier);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
for (const value of Object.values(record)) visit(value);
|
|
2894
|
+
}
|
|
2895
|
+
visit(program);
|
|
2896
|
+
return imports;
|
|
2897
|
+
}
|
|
2669
2898
|
const MANIFEST_CORE_IMPORTS = new Set([
|
|
2670
2899
|
"defineApp",
|
|
2671
2900
|
"group",
|
|
@@ -2685,6 +2914,7 @@ const PRACHT_OPTIMIZE_DEPS_INCLUDE = [
|
|
|
2685
2914
|
"@pracht/core/islands-client",
|
|
2686
2915
|
"@pracht/core/manifest"
|
|
2687
2916
|
];
|
|
2917
|
+
const PREACT_DEDUPE = ["preact", "preact-render-to-string"];
|
|
2688
2918
|
function createPrachtOptimizeDepsInclude(root) {
|
|
2689
2919
|
try {
|
|
2690
2920
|
if (!toPosixPath(createRequire(join(root, "package.json")).resolve("@pracht/core/package.json")).includes("/node_modules/")) return [];
|
|
@@ -2706,10 +2936,12 @@ function withPrachtOptimizeDepsEntries(config, prachtEntries, prachtInclude) {
|
|
|
2706
2936
|
function createPrachtOptimizeDepsEntries(resolved) {
|
|
2707
2937
|
const scriptExtensions = "{ts,tsx,js,jsx}";
|
|
2708
2938
|
const routeExtensions = "{ts,tsx,js,jsx,md,mdx,tsrx}";
|
|
2939
|
+
const apiDir = toOptimizeDepsEntry(resolved.apiDir);
|
|
2940
|
+
const apiEntries = [`${apiDir}/**/*.{ts,js,tsx,jsx}`, `!${apiDir}/**/*.d.ts`];
|
|
2709
2941
|
const entries = resolved.pagesDir ? [
|
|
2710
2942
|
`${toOptimizeDepsEntry(resolved.pagesDir)}/**/*.${routeExtensions}`,
|
|
2711
2943
|
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
2712
|
-
|
|
2944
|
+
...apiEntries,
|
|
2713
2945
|
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
2714
2946
|
`${toOptimizeDepsEntry(resolved.islandsDir)}/**/*.${scriptExtensions}`
|
|
2715
2947
|
] : [
|
|
@@ -2717,7 +2949,7 @@ function createPrachtOptimizeDepsEntries(resolved) {
|
|
|
2717
2949
|
`${toOptimizeDepsEntry(resolved.routesDir)}/**/*.${routeExtensions}`,
|
|
2718
2950
|
`${toOptimizeDepsEntry(resolved.shellsDir)}/**/*.${routeExtensions}`,
|
|
2719
2951
|
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
2720
|
-
|
|
2952
|
+
...apiEntries,
|
|
2721
2953
|
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
2722
2954
|
`${toOptimizeDepsEntry(resolved.islandsDir)}/**/*.${scriptExtensions}`,
|
|
2723
2955
|
`${toOptimizeDepsEntry(resolved.capabilitiesDir)}/**/*.{ts,js,tsx,jsx}`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, extname, join, relative } from "node:path";
|
|
3
|
+
import { maskCommentsAndStrings } from "@pracht/capabilities/static";
|
|
3
4
|
//#region src/route-loader-hints.ts
|
|
4
5
|
const ROUTE_EXTENSIONS = new Set([
|
|
5
6
|
".tsx",
|
|
@@ -84,6 +85,8 @@ const SHELL_EXTENSIONS = new Set([
|
|
|
84
85
|
function scanPagesDirectory(pagesDir) {
|
|
85
86
|
const pages = [];
|
|
86
87
|
scan(pagesDir, pagesDir, pages);
|
|
88
|
+
const appShell = pages.find((page) => page.routePath === "__shell__");
|
|
89
|
+
if (appShell?.hasRevalidateExport) throw new Error(`[pracht] Pages app shell ${JSON.stringify(appShell.relativePath)} exports REVALIDATE, but app shells are not ISG routes. Declare the policy on each ISG page instead.`);
|
|
87
90
|
return sortRoutes(pages);
|
|
88
91
|
}
|
|
89
92
|
function scan(dir, root, pages) {
|
|
@@ -105,10 +108,11 @@ function scan(dir, root, pages) {
|
|
|
105
108
|
if (name.startsWith("_") && name !== "_app") continue;
|
|
106
109
|
const rel = relative(root, abs);
|
|
107
110
|
const routePath = filePathToRoutePath(rel);
|
|
108
|
-
const
|
|
109
|
-
const renderMode =
|
|
110
|
-
const hydrationMode =
|
|
111
|
-
const
|
|
111
|
+
const analysisSource = maskMarkdownFences(readFileSync(abs, "utf-8"), rel);
|
|
112
|
+
const renderMode = extractQuotedPageExport(analysisSource, "RENDER_MODE", rel);
|
|
113
|
+
const hydrationMode = extractQuotedPageExport(analysisSource, "HYDRATION", rel);
|
|
114
|
+
const revalidate = extractRevalidateSeconds(analysisSource, rel);
|
|
115
|
+
const hasLoader = detectLoaderExport(analysisSource);
|
|
112
116
|
pages.push({
|
|
113
117
|
absolutePath: abs,
|
|
114
118
|
relativePath: rel,
|
|
@@ -118,6 +122,8 @@ function scan(dir, root, pages) {
|
|
|
118
122
|
isDynamic: routePath.split("/").some((segment) => segment.startsWith(":")),
|
|
119
123
|
renderMode,
|
|
120
124
|
hydrationMode,
|
|
125
|
+
revalidateSeconds: revalidate.seconds,
|
|
126
|
+
hasRevalidateExport: revalidate.present,
|
|
121
127
|
hasLoader
|
|
122
128
|
});
|
|
123
129
|
}
|
|
@@ -159,15 +165,70 @@ function getRouteSegmentSpecificity(segment) {
|
|
|
159
165
|
if (segment.startsWith(":")) return 2;
|
|
160
166
|
return 3;
|
|
161
167
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
function extractQuotedPageExport(source, name, relativePath) {
|
|
169
|
+
const declarations = [...maskCommentsAndStrings(source).matchAll(new RegExp(`export\\s+const\\s+${name}\\s*=`, "g"))];
|
|
170
|
+
if (declarations.length === 0) return void 0;
|
|
171
|
+
if (declarations.length > 1) throw new Error(`[pracht] Pages route ${JSON.stringify(relativePath)} exports ${name} more than once.`);
|
|
172
|
+
const declaration = declarations[0];
|
|
173
|
+
const valueStart = (declaration.index ?? 0) + declaration[0].length;
|
|
174
|
+
return source.slice(valueStart).trimStart().match(/^["'](\w+)["']/)?.[1];
|
|
166
175
|
}
|
|
167
|
-
const
|
|
168
|
-
function
|
|
169
|
-
const
|
|
170
|
-
|
|
176
|
+
const REVALIDATE_RE = /export\s+const\s+REVALIDATE\s*=\s*([^;\n]+)/;
|
|
177
|
+
function extractRevalidateSeconds(source, relativePath) {
|
|
178
|
+
const matches = [...maskCommentsAndStrings(source).matchAll(new RegExp(REVALIDATE_RE, "g"))];
|
|
179
|
+
if (matches.length === 0) return { present: false };
|
|
180
|
+
if (matches.length > 1) throw new Error(`[pracht] Pages route ${JSON.stringify(relativePath)} exports REVALIDATE more than once.`);
|
|
181
|
+
const expression = matches[0][1].trim().replace(/\s+as\s+const$/, "");
|
|
182
|
+
if (!/^\d(?:_?\d)*$/.test(expression)) throw new Error(`[pracht] Pages route ${JSON.stringify(relativePath)} must export REVALIDATE as a positive integer literal number of seconds (for example, \`export const REVALIDATE = 60\`).`);
|
|
183
|
+
const seconds = Number(expression.replaceAll("_", ""));
|
|
184
|
+
if (!Number.isSafeInteger(seconds) || seconds <= 0) throw new Error(`[pracht] Pages route ${JSON.stringify(relativePath)} must export REVALIDATE as a positive integer literal number of seconds within JavaScript's safe integer range.`);
|
|
185
|
+
return {
|
|
186
|
+
present: true,
|
|
187
|
+
seconds
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Mask Markdown fenced examples while preserving source offsets and top-level MDX exports. */
|
|
191
|
+
function maskMarkdownFences(source, relativePath) {
|
|
192
|
+
if (!/\.mdx?$/.test(relativePath)) return source;
|
|
193
|
+
const chars = source.split("");
|
|
194
|
+
let activeFence = null;
|
|
195
|
+
for (const line of source.matchAll(/.*(?:\r?\n|$)/g)) {
|
|
196
|
+
if (line[0] === "") continue;
|
|
197
|
+
const lineStart = line.index ?? 0;
|
|
198
|
+
const stripped = stripMarkdownContainerPrefix(line[0].replace(/\r?\n$/, ""));
|
|
199
|
+
const fenceContent = activeFence && stripped.content.startsWith(" ".repeat(activeFence.continuationIndent)) ? stripped.content.slice(activeFence.continuationIndent) : stripped.content;
|
|
200
|
+
const opening = activeFence ? null : /^ {0,3}(`{3,}|~{3,})/.exec(fenceContent);
|
|
201
|
+
const closing = activeFence ? new RegExp(`^ {0,3}\\${activeFence.character}{${activeFence.length},}[ \\t]*$`).test(fenceContent) : false;
|
|
202
|
+
if (activeFence || opening) for (let offset = 0; offset < line[0].length; offset += 1) {
|
|
203
|
+
const index = lineStart + offset;
|
|
204
|
+
if (chars[index] !== "\n" && chars[index] !== "\r") chars[index] = " ";
|
|
205
|
+
}
|
|
206
|
+
if (closing) activeFence = null;
|
|
207
|
+
else if (opening) activeFence = {
|
|
208
|
+
character: opening[1][0],
|
|
209
|
+
continuationIndent: stripped.continuationIndent,
|
|
210
|
+
length: opening[1].length
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return chars.join("");
|
|
214
|
+
}
|
|
215
|
+
function stripMarkdownContainerPrefix(line) {
|
|
216
|
+
let content = line;
|
|
217
|
+
let continuationIndent = 0;
|
|
218
|
+
while (true) {
|
|
219
|
+
const quote = /^ {0,3}> ?/.exec(content);
|
|
220
|
+
if (quote) {
|
|
221
|
+
content = content.slice(quote[0].length);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const list = /^ {0,3}(?:[-+*]|\d{1,9}[.)])[ \t]+/.exec(content);
|
|
225
|
+
if (!list) return {
|
|
226
|
+
content,
|
|
227
|
+
continuationIndent
|
|
228
|
+
};
|
|
229
|
+
continuationIndent += list[0].length;
|
|
230
|
+
content = content.slice(list[0].length);
|
|
231
|
+
}
|
|
171
232
|
}
|
|
172
233
|
function generatePagesManifestSource(pages, options) {
|
|
173
234
|
const pagesDir = options.pagesDir;
|
|
@@ -175,16 +236,20 @@ function generatePagesManifestSource(pages, options) {
|
|
|
175
236
|
const prefix = options.pagesDirPrefix;
|
|
176
237
|
const useImport = options.useImportSyntax ?? false;
|
|
177
238
|
const appFile = scanAllFiles(pagesDir).find((f) => basename(f, extname(f)) === "_app" && SHELL_EXTENSIONS.has(extname(f)));
|
|
178
|
-
const lines = [
|
|
239
|
+
const lines = [`import { ${pages.some((page) => page.revalidateSeconds !== void 0) ? "defineApp, group, route, timeRevalidate" : "defineApp, group, route"} } from "@pracht/core/manifest";`, ""];
|
|
179
240
|
const routeEntries = [];
|
|
180
241
|
const notFoundPage = pages.find((page) => page.routePath === "/404");
|
|
242
|
+
if (notFoundPage?.hasRevalidateExport) throw new Error(`[pracht] Pages not-found module ${JSON.stringify(notFoundPage.relativePath)} exports REVALIDATE, but not-found responses are never ISG routes.`);
|
|
181
243
|
for (const page of pages) {
|
|
182
244
|
if (page === notFoundPage) continue;
|
|
183
245
|
const render = page.renderMode ?? defaultRender;
|
|
246
|
+
if (render === "isg" && page.revalidateSeconds === void 0) throw new Error(`[pracht] Pages route ${JSON.stringify(page.relativePath)} uses render mode "isg" but does not export a revalidation policy. Add \`export const REVALIDATE = 60\` with a positive integer number of seconds, or use another render mode.`);
|
|
247
|
+
if (render !== "isg" && page.hasRevalidateExport) throw new Error(`[pracht] Pages route ${JSON.stringify(page.relativePath)} exports REVALIDATE but its effective render mode is ${JSON.stringify(render)}. REVALIDATE is only valid with \`RENDER_MODE = "isg"\` (or \`pagesDefaultRender: "isg"\`).`);
|
|
184
248
|
const filePath = prefix ? `${prefix}/${page.relativePath.replace(/\\/g, "/")}` : `./${page.relativePath.replace(/\\/g, "/")}`;
|
|
185
249
|
const fileRef = useImport ? `() => import(${JSON.stringify(filePath)})` : JSON.stringify(filePath);
|
|
186
250
|
const metaParts = [`render: ${JSON.stringify(render)}`, `hasLoader: ${page.hasLoader ? "true" : "false"}`];
|
|
187
251
|
if (page.hydrationMode) metaParts.push(`hydration: ${JSON.stringify(page.hydrationMode)}`);
|
|
252
|
+
if (page.revalidateSeconds !== void 0) metaParts.push(`revalidate: timeRevalidate(${page.revalidateSeconds})`);
|
|
188
253
|
routeEntries.push(` route(${JSON.stringify(page.routePath)}, ${fileRef}, { ${metaParts.join(", ")} })`);
|
|
189
254
|
}
|
|
190
255
|
const notFoundEntry = notFoundPage ? buildNotFoundEntry(notFoundPage, {
|
package/dist/pages-router.d.mts
CHANGED
package/dist/pages-router.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as sortRoutes, i as scanPagesDirectory, n as generatePagesManifestSource, r as generateRoutesFile, t as filePathToRoutePath } from "./pages-router-
|
|
1
|
+
import { a as sortRoutes, i as scanPagesDirectory, n as generatePagesManifestSource, r as generateRoutesFile, t as filePathToRoutePath } from "./pages-router-BQlG21oC.mjs";
|
|
2
2
|
export { filePathToRoutePath, generatePagesManifestSource, generateRoutesFile, scanPagesDirectory, sortRoutes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Vite plugin for Pracht apps with virtual modules, dev SSR, prerendering, route inspection, and multi-adapter builds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pracht",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@preact/preset-vite": "^2.10.5",
|
|
46
46
|
"@prefresh/vite": "^2.0.0",
|
|
47
|
-
"@pracht/
|
|
48
|
-
"@pracht/
|
|
49
|
-
"@pracht/
|
|
50
|
-
"@pracht/
|
|
47
|
+
"@pracht/adapter-node": "0.3.9",
|
|
48
|
+
"@pracht/capabilities": "0.2.0",
|
|
49
|
+
"@pracht/core": "0.13.0",
|
|
50
|
+
"@pracht/preact-ssr-precompile": "0.1.3"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"vite": "^8.0.0"
|