@pracht/vite-plugin 0.6.2 → 0.7.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/dist/index.d.mts +79 -2
- package/dist/index.mjs +393 -13
- package/package.json +5 -4
package/dist/index.d.mts
CHANGED
|
@@ -6,6 +6,8 @@ import { RenderMode, RenderMode as RenderMode$1 } from "@pracht/core";
|
|
|
6
6
|
declare const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
|
|
7
7
|
declare const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
|
|
8
8
|
declare const PRACHT_ISLANDS_CLIENT_MODULE_ID = "virtual:pracht/islands-client";
|
|
9
|
+
declare const PRACHT_CAPABILITIES_MODULE_ID = "virtual:pracht/capabilities";
|
|
10
|
+
declare const PRACHT_WEBMCP_MODULE_ID = "virtual:pracht/webmcp";
|
|
9
11
|
//#endregion
|
|
10
12
|
//#region src/env-safety.d.ts
|
|
11
13
|
/**
|
|
@@ -84,6 +86,23 @@ interface PrachtAdapter {
|
|
|
84
86
|
}
|
|
85
87
|
//#endregion
|
|
86
88
|
//#region src/plugin-options.d.ts
|
|
89
|
+
type LlmsTxtSection = "pages" | "api" | "capabilities";
|
|
90
|
+
interface PrachtLlmsTxtOptions {
|
|
91
|
+
/** H1 title. Defaults to the app's package.json `name`. */
|
|
92
|
+
title?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Blockquote summary under the title. Defaults to the app's package.json
|
|
95
|
+
* `description`; omitted when neither is set.
|
|
96
|
+
*/
|
|
97
|
+
description?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Origin (e.g. "https://example.com") prepended to every link so llms.txt
|
|
100
|
+
* contains absolute URLs. Links stay root-relative when omitted.
|
|
101
|
+
*/
|
|
102
|
+
origin?: string;
|
|
103
|
+
/** Sections to emit. Defaults to ["pages", "api", "capabilities"]. */
|
|
104
|
+
include?: LlmsTxtSection[];
|
|
105
|
+
}
|
|
87
106
|
interface PrachtPluginOptions {
|
|
88
107
|
appFile?: string;
|
|
89
108
|
routesDir?: string;
|
|
@@ -96,6 +115,11 @@ interface PrachtPluginOptions {
|
|
|
96
115
|
* `hydration: "islands"` routes. Defaults to "/src/islands".
|
|
97
116
|
*/
|
|
98
117
|
islandsDir?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Directory containing capability modules registered in the app manifest
|
|
120
|
+
* via `capabilities: { ... }`. Defaults to "/src/capabilities".
|
|
121
|
+
*/
|
|
122
|
+
capabilitiesDir?: string;
|
|
99
123
|
adapter?: PrachtAdapter;
|
|
100
124
|
/** Enable file-system pages routing by pointing to the pages directory (e.g. "/src/pages"). */
|
|
101
125
|
pagesDir?: string;
|
|
@@ -125,6 +149,12 @@ interface PrachtPluginOptions {
|
|
|
125
149
|
* variables, or `false` to disable the check entirely.
|
|
126
150
|
*/
|
|
127
151
|
envSafety?: false | EnvSafetyOptions;
|
|
152
|
+
/**
|
|
153
|
+
* Opt into emitting an llms.txt file (https://llmstxt.org) generated from
|
|
154
|
+
* the resolved app graph. `pracht build` writes `dist/client/llms.txt` and
|
|
155
|
+
* the dev server serves `/llms.txt` live. Disabled by default.
|
|
156
|
+
*/
|
|
157
|
+
llmsTxt?: false | PrachtLlmsTxtOptions;
|
|
128
158
|
}
|
|
129
159
|
//#endregion
|
|
130
160
|
//#region src/plugin-codegen.d.ts
|
|
@@ -137,14 +167,61 @@ declare function createPrachtClientModuleSource(options?: PrachtPluginOptions, b
|
|
|
137
167
|
* manifest, the router, or the full client runtime: it only scans the DOM
|
|
138
168
|
* for island markers and hydrates the islands present on the page.
|
|
139
169
|
*/
|
|
140
|
-
declare function createPrachtIslandsClientModuleSource(options?: PrachtPluginOptions
|
|
170
|
+
declare function createPrachtIslandsClientModuleSource(options?: PrachtPluginOptions, buildOptions?: {
|
|
171
|
+
root?: string;
|
|
172
|
+
}): string;
|
|
141
173
|
declare function createPrachtServerModuleSource(options?: PrachtPluginOptions, buildOptions?: {
|
|
142
174
|
root?: string;
|
|
143
175
|
isBuild?: boolean;
|
|
144
176
|
}): string;
|
|
145
177
|
declare function createPrachtRegistryModuleSource(options?: PrachtPluginOptions): string;
|
|
146
178
|
//#endregion
|
|
179
|
+
//#region src/plugin-capabilities.d.ts
|
|
180
|
+
interface ExtractedCapability {
|
|
181
|
+
name: string;
|
|
182
|
+
/** Manifest-relative module path, e.g. "./capabilities/notes-search.ts". */
|
|
183
|
+
file: string;
|
|
184
|
+
description: string;
|
|
185
|
+
effect: string | null;
|
|
186
|
+
httpPath: string | null;
|
|
187
|
+
webmcp: boolean;
|
|
188
|
+
inputSchema: Record<string, unknown> | null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Extract capability registrations (name → module path) from the app
|
|
192
|
+
* manifest source and their exposure metadata from each capability source.
|
|
193
|
+
* Pages-router apps have no manifest, so capabilities are manifest-mode only.
|
|
194
|
+
*/
|
|
195
|
+
declare function extractCapabilities(options?: PrachtPluginOptions, root?: string): ExtractedCapability[];
|
|
196
|
+
/**
|
|
197
|
+
* Generate `virtual:pracht/capabilities` — the browser-side `callCapability`
|
|
198
|
+
* helper plus the endpoint map for http-exposed capabilities. Side-effect
|
|
199
|
+
* free, so it costs zero bytes unless application code imports it.
|
|
200
|
+
*
|
|
201
|
+
* After every call settles, the helper announces itself on
|
|
202
|
+
* CAPABILITY_SETTLED_EVENT with the capability's effect class; the framework
|
|
203
|
+
* runtime revalidates route data for successful non-`read` calls (opt out
|
|
204
|
+
* per call via `{ revalidate: false }`).
|
|
205
|
+
*/
|
|
206
|
+
declare function createPrachtCapabilitiesClientModuleSource(options?: PrachtPluginOptions, buildOptions?: {
|
|
207
|
+
root?: string;
|
|
208
|
+
}): string;
|
|
209
|
+
/**
|
|
210
|
+
* Generate `virtual:pracht/webmcp` — the disposable WebMCP registration shim.
|
|
211
|
+
* One page tool per `expose.webmcp` capability; `execute` dispatches through
|
|
212
|
+
* `callCapability`, so the user's session authenticates the call and all
|
|
213
|
+
* validation/middleware/policy stays server-side. Each dispatch carries the
|
|
214
|
+
* transport marker header so audit events can attribute it to WebMCP.
|
|
215
|
+
*
|
|
216
|
+
* Targets the Chrome origin-trial API: `document.modelContext.registerTool()`
|
|
217
|
+
* (Chrome 150+; `navigator.modelContext` is the deprecated pre-150 location
|
|
218
|
+
* and is kept as a fallback). No-ops silently when the API is absent.
|
|
219
|
+
*/
|
|
220
|
+
declare function createPrachtWebmcpModuleSource(options?: PrachtPluginOptions, buildOptions?: {
|
|
221
|
+
root?: string;
|
|
222
|
+
}): string;
|
|
223
|
+
//#endregion
|
|
147
224
|
//#region src/index.d.ts
|
|
148
225
|
declare function pracht(options?: PrachtPluginOptions): Plugin[];
|
|
149
226
|
//#endregion
|
|
150
|
-
export { type EnvLeakReference, type EnvSafetyOptions, PRACHT_CLIENT_MODULE_ID, PRACHT_ISLANDS_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, PUBLIC_ENV_PREFIX, type PrachtAdapter, type PrachtPluginOptions, type RenderMode, VITE_BUILTIN_ENV_VARS, createEnvSafetyPlugin, createPrachtClientModuleSource, createPrachtIslandsClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, formatEnvLeakError, pracht, scanCodeForEnvLeaks };
|
|
227
|
+
export { type EnvLeakReference, type EnvSafetyOptions, type LlmsTxtSection, PRACHT_CAPABILITIES_MODULE_ID, PRACHT_CLIENT_MODULE_ID, PRACHT_ISLANDS_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, PRACHT_WEBMCP_MODULE_ID, PUBLIC_ENV_PREFIX, type PrachtAdapter, type PrachtLlmsTxtOptions, type PrachtPluginOptions, type RenderMode, VITE_BUILTIN_ENV_VARS, createEnvSafetyPlugin, createPrachtCapabilitiesClientModuleSource, createPrachtClientModuleSource, createPrachtIslandsClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, createPrachtWebmcpModuleSource, extractCapabilities, formatEnvLeakError, pracht, scanCodeForEnvLeaks };
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,10 @@ import preact from "@preact/preset-vite";
|
|
|
5
5
|
import { dirname, extname, join, resolve } from "node:path";
|
|
6
6
|
import { parseAst } from "vite";
|
|
7
7
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
+
import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER, capabilityHttpPath, isValidCapabilityHttpPath } from "@pracht/capabilities";
|
|
9
|
+
import { evaluateLiteral, extractCapabilityRegistrations, extractDefineCapabilityArgs, scanTopLevelProperties } from "@pracht/capabilities/static";
|
|
8
10
|
import { createNodeServerEntryModule } from "@pracht/adapter-node";
|
|
11
|
+
import { resolveRegistryModule } from "@pracht/core";
|
|
9
12
|
//#region src/client-module-query.ts
|
|
10
13
|
const CLIENT_MODULE_QUERY = "pracht-client";
|
|
11
14
|
const PRACHT_CLIENT_MODULE_QUERY = `?${CLIENT_MODULE_QUERY}`;
|
|
@@ -1191,6 +1194,8 @@ function createEnvSafetyPlugin(envSafety) {
|
|
|
1191
1194
|
const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
|
|
1192
1195
|
const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
|
|
1193
1196
|
const PRACHT_ISLANDS_CLIENT_MODULE_ID = "virtual:pracht/islands-client";
|
|
1197
|
+
const PRACHT_CAPABILITIES_MODULE_ID = "virtual:pracht/capabilities";
|
|
1198
|
+
const PRACHT_WEBMCP_MODULE_ID = "virtual:pracht/webmcp";
|
|
1194
1199
|
const CLIENT_BROWSER_PATH = "/@pracht/client.js";
|
|
1195
1200
|
const ISLANDS_CLIENT_BROWSER_PATH = "/@pracht/islands.js";
|
|
1196
1201
|
function readClientBuildAssets(root = process.cwd()) {
|
|
@@ -1256,6 +1261,12 @@ function isServerModule(id) {
|
|
|
1256
1261
|
function isIslandsClientModule(id) {
|
|
1257
1262
|
return id === "virtual:pracht/islands-client" || id === "/@pracht/islands.js" || id.endsWith("virtual:pracht/islands-client");
|
|
1258
1263
|
}
|
|
1264
|
+
function isCapabilitiesModule(id) {
|
|
1265
|
+
return id === "virtual:pracht/capabilities" || id.endsWith("virtual:pracht/capabilities");
|
|
1266
|
+
}
|
|
1267
|
+
function isWebmcpModule(id) {
|
|
1268
|
+
return id === "virtual:pracht/webmcp" || id.endsWith("virtual:pracht/webmcp");
|
|
1269
|
+
}
|
|
1259
1270
|
//#endregion
|
|
1260
1271
|
//#region src/plugin-adapter.ts
|
|
1261
1272
|
function createDefaultNodeAdapter() {
|
|
@@ -1277,6 +1288,7 @@ const DEFAULTS = {
|
|
|
1277
1288
|
apiDir: "/src/api",
|
|
1278
1289
|
serverDir: "/src/server",
|
|
1279
1290
|
islandsDir: "/src/islands",
|
|
1291
|
+
capabilitiesDir: "/src/capabilities",
|
|
1280
1292
|
adapter: createDefaultNodeAdapter(),
|
|
1281
1293
|
pagesDir: "",
|
|
1282
1294
|
pagesDefaultRender: "ssr",
|
|
@@ -1284,18 +1296,33 @@ const DEFAULTS = {
|
|
|
1284
1296
|
maxBodySize: 1024 * 1024,
|
|
1285
1297
|
budgets: {},
|
|
1286
1298
|
precompileSsrJsx: false,
|
|
1287
|
-
envSafety: {}
|
|
1299
|
+
envSafety: {},
|
|
1300
|
+
llmsTxt: false
|
|
1288
1301
|
};
|
|
1289
1302
|
function resolveOptions(options) {
|
|
1290
1303
|
const resolved = {
|
|
1291
1304
|
...DEFAULTS,
|
|
1292
1305
|
...options
|
|
1293
1306
|
};
|
|
1307
|
+
if (resolved.llmsTxt === void 0) resolved.llmsTxt = false;
|
|
1294
1308
|
if (!Number.isInteger(resolved.prerenderConcurrency) || resolved.prerenderConcurrency <= 0) throw new Error("pracht({ prerenderConcurrency }) expects a positive integer.");
|
|
1295
1309
|
if (!Number.isInteger(resolved.maxBodySize) || resolved.maxBodySize <= 0) throw new Error("pracht({ maxBodySize }) expects a positive integer number of bytes.");
|
|
1296
1310
|
validateBudgets(resolved.budgets);
|
|
1311
|
+
validateLlmsTxt(resolved.llmsTxt);
|
|
1297
1312
|
return resolved;
|
|
1298
1313
|
}
|
|
1314
|
+
const LLMS_TXT_SECTIONS = new Set([
|
|
1315
|
+
"pages",
|
|
1316
|
+
"api",
|
|
1317
|
+
"capabilities"
|
|
1318
|
+
]);
|
|
1319
|
+
function validateLlmsTxt(llmsTxt) {
|
|
1320
|
+
if (llmsTxt === false) return;
|
|
1321
|
+
if (typeof llmsTxt !== "object" || llmsTxt === null) throw new Error("pracht({ llmsTxt }) expects false or an options object.");
|
|
1322
|
+
if (llmsTxt.include !== void 0) {
|
|
1323
|
+
if (!(Array.isArray(llmsTxt.include) && llmsTxt.include.every((section) => LLMS_TXT_SECTIONS.has(section)))) throw new Error(`pracht({ llmsTxt: { include } }) expects an array of "pages", "api", and/or "capabilities", got ${JSON.stringify(llmsTxt.include)}.`);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1299
1326
|
function validateBudgets(budgets) {
|
|
1300
1327
|
for (const [key, value] of Object.entries(budgets)) {
|
|
1301
1328
|
if (key !== "*" && !key.startsWith("/")) throw new Error(`pracht({ budgets }) keys must be "*" or a route path starting with "/", got ${JSON.stringify(key)}.`);
|
|
@@ -1305,6 +1332,288 @@ function validateBudgets(budgets) {
|
|
|
1305
1332
|
}
|
|
1306
1333
|
}
|
|
1307
1334
|
//#endregion
|
|
1335
|
+
//#region src/plugin-capabilities.ts
|
|
1336
|
+
/**
|
|
1337
|
+
* Build-time capability projection for the browser.
|
|
1338
|
+
*
|
|
1339
|
+
* The client never loads capability modules (they are server-only), so the
|
|
1340
|
+
* `virtual:pracht/capabilities` and `virtual:pracht/webmcp` modules are
|
|
1341
|
+
* generated from static analysis of the app manifest and the registered
|
|
1342
|
+
* capability sources — the same approach the plugin already uses for
|
|
1343
|
+
* hydration-mode excludes. Only serializable metadata crosses the boundary:
|
|
1344
|
+
* capability names, HTTP endpoints, effects, and (for WebMCP tools)
|
|
1345
|
+
* description and input schema.
|
|
1346
|
+
*
|
|
1347
|
+
* The static analyzer itself lives in `@pracht/capabilities/static` and is
|
|
1348
|
+
* shared with `pracht verify`, so the build and verification can never
|
|
1349
|
+
* disagree about what is analyzable. Constraint it imposes: a capability's
|
|
1350
|
+
* `expose`, HTTP-projected `effect`, and WebMCP `input` values must be inline
|
|
1351
|
+
* literals (no imported constants or spreads) — the extractor parses the
|
|
1352
|
+
* literal text as data.
|
|
1353
|
+
* Extraction failures fail the build with a pointer to the offending file
|
|
1354
|
+
* rather than silently dropping an endpoint.
|
|
1355
|
+
*/
|
|
1356
|
+
/**
|
|
1357
|
+
* Extract capability registrations (name → module path) from the app
|
|
1358
|
+
* manifest source and their exposure metadata from each capability source.
|
|
1359
|
+
* Pages-router apps have no manifest, so capabilities are manifest-mode only.
|
|
1360
|
+
*/
|
|
1361
|
+
function extractCapabilities(options = {}, root = process.cwd()) {
|
|
1362
|
+
const resolved = resolveOptions(options);
|
|
1363
|
+
if (resolved.pagesDir) return [];
|
|
1364
|
+
const appFileAbs = resolve(root, resolved.appFile.replace(/^\//, ""));
|
|
1365
|
+
let manifestSource;
|
|
1366
|
+
try {
|
|
1367
|
+
manifestSource = readFileSync(appFileAbs, "utf-8");
|
|
1368
|
+
} catch {
|
|
1369
|
+
return [];
|
|
1370
|
+
}
|
|
1371
|
+
const registrations = extractCapabilityRegistrations(manifestSource);
|
|
1372
|
+
if (registrations.length === 0) return [];
|
|
1373
|
+
const appDir = dirname(appFileAbs);
|
|
1374
|
+
return registrations.map(({ name, file }) => {
|
|
1375
|
+
const capabilityFileAbs = file.startsWith("/") ? resolve(root, file.replace(/^\//, "")) : resolve(appDir, file);
|
|
1376
|
+
let source;
|
|
1377
|
+
try {
|
|
1378
|
+
source = readFileSync(capabilityFileAbs, "utf-8");
|
|
1379
|
+
} catch {
|
|
1380
|
+
throw new Error(`[pracht] Capability "${name}" references missing file ${JSON.stringify(file)}.`);
|
|
1381
|
+
}
|
|
1382
|
+
return extractCapabilityMetadata(name, file, source);
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
function extractCapabilityMetadata(name, file, source) {
|
|
1386
|
+
const args = extractDefineCapabilityArgs(source);
|
|
1387
|
+
if (!args) throw new Error(`[pracht] Capability "${name}" (${file}) does not contain a defineCapability({ ... }) call the build can analyze.`);
|
|
1388
|
+
const properties = scanTopLevelProperties(args);
|
|
1389
|
+
const exposeText = properties.get("expose");
|
|
1390
|
+
if (!exposeText) return {
|
|
1391
|
+
name,
|
|
1392
|
+
file,
|
|
1393
|
+
description: "",
|
|
1394
|
+
effect: null,
|
|
1395
|
+
httpPath: null,
|
|
1396
|
+
webmcp: false,
|
|
1397
|
+
inputSchema: null
|
|
1398
|
+
};
|
|
1399
|
+
const expose = evaluateLiteral(exposeText);
|
|
1400
|
+
if (!isPlainObject(expose)) throw new Error(`[pracht] Capability "${name}" (${file}): "expose" must be an inline object literal so the client projection can be generated at build time.`);
|
|
1401
|
+
const http = expose.http;
|
|
1402
|
+
let httpPath = null;
|
|
1403
|
+
if (http === true) httpPath = capabilityHttpPath(name);
|
|
1404
|
+
else if (isPlainObject(http)) httpPath = typeof http.path === "string" ? http.path : capabilityHttpPath(name);
|
|
1405
|
+
if (httpPath && !isValidCapabilityHttpPath(httpPath)) throw new Error(`[pracht] Capability "${name}" (${file}): HTTP exposure "path" must be an exact same-origin pathname starting with "/".`);
|
|
1406
|
+
const webmcp = expose.webmcp === true;
|
|
1407
|
+
if (webmcp && !httpPath) throw new Error(`[pracht] Capability "${name}" (${file}): expose.webmcp requires expose.http.`);
|
|
1408
|
+
let description = "";
|
|
1409
|
+
const descriptionText = properties.get("description");
|
|
1410
|
+
if (descriptionText) {
|
|
1411
|
+
const value = evaluateLiteral(descriptionText);
|
|
1412
|
+
if (typeof value === "string") description = value;
|
|
1413
|
+
}
|
|
1414
|
+
let effect = null;
|
|
1415
|
+
const effectText = properties.get("effect");
|
|
1416
|
+
if (effectText) {
|
|
1417
|
+
const value = evaluateLiteral(effectText);
|
|
1418
|
+
if (typeof value === "string") effect = value;
|
|
1419
|
+
}
|
|
1420
|
+
if (httpPath && effect !== "read" && effect !== "write" && effect !== "destructive") throw new Error(`[pracht] Capability "${name}" (${file}) is exposed via HTTP, but its "effect" could not be extracted at build time. HTTP-exposed capabilities must declare "effect" as an inline "read", "write", or "destructive" string literal.`);
|
|
1421
|
+
let inputSchema = null;
|
|
1422
|
+
if (webmcp) {
|
|
1423
|
+
const inputText = properties.get("input");
|
|
1424
|
+
const value = inputText ? evaluateLiteral(inputText) : void 0;
|
|
1425
|
+
if (!isPlainObject(value)) throw new Error(`[pracht] Capability "${name}" (${file}) is exposed via WebMCP, but its "input" schema could not be extracted at build time. WebMCP-exposed capabilities must declare their input schema as an inline object literal.`);
|
|
1426
|
+
inputSchema = value;
|
|
1427
|
+
}
|
|
1428
|
+
return {
|
|
1429
|
+
name,
|
|
1430
|
+
file,
|
|
1431
|
+
description,
|
|
1432
|
+
effect,
|
|
1433
|
+
httpPath,
|
|
1434
|
+
webmcp,
|
|
1435
|
+
inputSchema
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Generate `virtual:pracht/capabilities` — the browser-side `callCapability`
|
|
1440
|
+
* helper plus the endpoint map for http-exposed capabilities. Side-effect
|
|
1441
|
+
* free, so it costs zero bytes unless application code imports it.
|
|
1442
|
+
*
|
|
1443
|
+
* After every call settles, the helper announces itself on
|
|
1444
|
+
* CAPABILITY_SETTLED_EVENT with the capability's effect class; the framework
|
|
1445
|
+
* runtime revalidates route data for successful non-`read` calls (opt out
|
|
1446
|
+
* per call via `{ revalidate: false }`).
|
|
1447
|
+
*/
|
|
1448
|
+
function createPrachtCapabilitiesClientModuleSource(options = {}, buildOptions = {}) {
|
|
1449
|
+
const capabilities = extractCapabilities(options, buildOptions.root);
|
|
1450
|
+
const endpoints = {};
|
|
1451
|
+
for (const capability of capabilities) if (capability.httpPath) endpoints[capability.name] = {
|
|
1452
|
+
method: "POST",
|
|
1453
|
+
path: capability.httpPath,
|
|
1454
|
+
effect: capability.effect
|
|
1455
|
+
};
|
|
1456
|
+
return [
|
|
1457
|
+
"// Generated by @pracht/vite-plugin from the app manifest capability registrations.",
|
|
1458
|
+
"// Contains only http-exposed capability names, endpoints, and effects —",
|
|
1459
|
+
"// capability modules themselves are server-only and never reach the client graph.",
|
|
1460
|
+
`const endpoints = ${JSON.stringify(endpoints)};`,
|
|
1461
|
+
"",
|
|
1462
|
+
"export const capabilityEndpoints = endpoints;",
|
|
1463
|
+
"",
|
|
1464
|
+
"async function dispatchCapability(endpoint, input, opts) {",
|
|
1465
|
+
" let response;",
|
|
1466
|
+
" try {",
|
|
1467
|
+
" const headers = new Headers(opts && opts.headers);",
|
|
1468
|
+
" headers.set(\"content-type\", \"application/json\");",
|
|
1469
|
+
" if (opts && opts.confirm) {",
|
|
1470
|
+
` headers.set(${JSON.stringify(CONFIRMATION_HEADER)}, opts.confirm);`,
|
|
1471
|
+
" }",
|
|
1472
|
+
" response = await fetch(endpoint.path, {",
|
|
1473
|
+
" method: endpoint.method,",
|
|
1474
|
+
" headers,",
|
|
1475
|
+
" body: JSON.stringify(input === undefined ? {} : input),",
|
|
1476
|
+
" credentials: \"same-origin\",",
|
|
1477
|
+
" signal: opts && opts.signal,",
|
|
1478
|
+
" });",
|
|
1479
|
+
" } catch (error) {",
|
|
1480
|
+
" return {",
|
|
1481
|
+
" ok: false,",
|
|
1482
|
+
" error: { code: \"network_error\", message: String((error && error.message) || error) },",
|
|
1483
|
+
" };",
|
|
1484
|
+
" }",
|
|
1485
|
+
" try {",
|
|
1486
|
+
" return await response.json();",
|
|
1487
|
+
" } catch {",
|
|
1488
|
+
" return {",
|
|
1489
|
+
" ok: false,",
|
|
1490
|
+
" error: {",
|
|
1491
|
+
" code: \"invalid_response\",",
|
|
1492
|
+
" message: `Capability endpoint returned a non-JSON response (status ${response.status}).`,",
|
|
1493
|
+
" },",
|
|
1494
|
+
" };",
|
|
1495
|
+
" }",
|
|
1496
|
+
"}",
|
|
1497
|
+
"",
|
|
1498
|
+
"export async function callCapability(name, input, opts) {",
|
|
1499
|
+
" const endpoint = endpoints[name];",
|
|
1500
|
+
" if (!endpoint) {",
|
|
1501
|
+
" return {",
|
|
1502
|
+
" ok: false,",
|
|
1503
|
+
" error: {",
|
|
1504
|
+
" code: \"unknown_capability\",",
|
|
1505
|
+
" message: `No HTTP-exposed capability named \"${name}\" is registered.`,",
|
|
1506
|
+
" },",
|
|
1507
|
+
" };",
|
|
1508
|
+
" }",
|
|
1509
|
+
" const result = await dispatchCapability(endpoint, input, opts);",
|
|
1510
|
+
" // Announce the settled call so the route runtime can revalidate after",
|
|
1511
|
+
" // successful non-read effects. Best-effort — never breaks the call.",
|
|
1512
|
+
" try {",
|
|
1513
|
+
" if (typeof window !== \"undefined\") {",
|
|
1514
|
+
` window.dispatchEvent(new CustomEvent(${JSON.stringify(CAPABILITY_SETTLED_EVENT)}, {`,
|
|
1515
|
+
" detail: {",
|
|
1516
|
+
" name,",
|
|
1517
|
+
" effect: endpoint.effect,",
|
|
1518
|
+
" ok: result && result.ok === true,",
|
|
1519
|
+
" revalidate: opts && opts.revalidate === false ? false : undefined,",
|
|
1520
|
+
" },",
|
|
1521
|
+
" }));",
|
|
1522
|
+
" }",
|
|
1523
|
+
" } catch {}",
|
|
1524
|
+
" return result;",
|
|
1525
|
+
"}",
|
|
1526
|
+
""
|
|
1527
|
+
].join("\n");
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Generate `virtual:pracht/webmcp` — the disposable WebMCP registration shim.
|
|
1531
|
+
* One page tool per `expose.webmcp` capability; `execute` dispatches through
|
|
1532
|
+
* `callCapability`, so the user's session authenticates the call and all
|
|
1533
|
+
* validation/middleware/policy stays server-side. Each dispatch carries the
|
|
1534
|
+
* transport marker header so audit events can attribute it to WebMCP.
|
|
1535
|
+
*
|
|
1536
|
+
* Targets the Chrome origin-trial API: `document.modelContext.registerTool()`
|
|
1537
|
+
* (Chrome 150+; `navigator.modelContext` is the deprecated pre-150 location
|
|
1538
|
+
* and is kept as a fallback). No-ops silently when the API is absent.
|
|
1539
|
+
*/
|
|
1540
|
+
function createPrachtWebmcpModuleSource(options = {}, buildOptions = {}) {
|
|
1541
|
+
const tools = extractCapabilities(options, buildOptions.root).filter((capability) => capability.webmcp).map((capability) => ({
|
|
1542
|
+
name: capability.name,
|
|
1543
|
+
description: capability.description,
|
|
1544
|
+
effect: capability.effect,
|
|
1545
|
+
inputSchema: capability.inputSchema
|
|
1546
|
+
}));
|
|
1547
|
+
return [
|
|
1548
|
+
"// Generated by @pracht/vite-plugin — WebMCP page-tool registration shim.",
|
|
1549
|
+
"import { callCapability } from \"virtual:pracht/capabilities\";",
|
|
1550
|
+
"",
|
|
1551
|
+
`const tools = ${JSON.stringify(tools)};`,
|
|
1552
|
+
`const transportHeaders = { ${JSON.stringify(CAPABILITY_TRANSPORT_HEADER)}: "webmcp" };`,
|
|
1553
|
+
"",
|
|
1554
|
+
"export function registerPrachtWebmcpTools() {",
|
|
1555
|
+
" const modelContext =",
|
|
1556
|
+
" (typeof document !== \"undefined\" && document.modelContext) ||",
|
|
1557
|
+
" (typeof navigator !== \"undefined\" && navigator.modelContext) ||",
|
|
1558
|
+
" null;",
|
|
1559
|
+
" if (!modelContext || typeof modelContext.registerTool !== \"function\") {",
|
|
1560
|
+
" return false;",
|
|
1561
|
+
" }",
|
|
1562
|
+
" for (const tool of tools) {",
|
|
1563
|
+
" try {",
|
|
1564
|
+
" const registration = modelContext.registerTool({",
|
|
1565
|
+
" name: tool.name,",
|
|
1566
|
+
" description: tool.description,",
|
|
1567
|
+
" inputSchema: tool.inputSchema,",
|
|
1568
|
+
" annotations: { readOnlyHint: tool.effect === \"read\" },",
|
|
1569
|
+
" async execute(input) {",
|
|
1570
|
+
" const result = await callCapability(tool.name, input, { headers: transportHeaders });",
|
|
1571
|
+
" return { content: [{ type: \"text\", text: JSON.stringify(result) }] };",
|
|
1572
|
+
" },",
|
|
1573
|
+
" });",
|
|
1574
|
+
" if (registration && typeof registration.catch === \"function\") {",
|
|
1575
|
+
" registration.catch(() => {});",
|
|
1576
|
+
" }",
|
|
1577
|
+
" } catch {",
|
|
1578
|
+
" // Origin-trial API surface may shift; a failed registration must",
|
|
1579
|
+
" // never break the page.",
|
|
1580
|
+
" }",
|
|
1581
|
+
" }",
|
|
1582
|
+
" return true;",
|
|
1583
|
+
"}",
|
|
1584
|
+
"",
|
|
1585
|
+
"registerPrachtWebmcpTools();",
|
|
1586
|
+
""
|
|
1587
|
+
].join("\n");
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Snippet appended to the client entry / islands bootstrap when at least one
|
|
1591
|
+
* capability opts into WebMCP. Feature-detects before importing so browsers
|
|
1592
|
+
* without the origin trial never pay for the shim chunk.
|
|
1593
|
+
*/
|
|
1594
|
+
function createWebmcpBootstrapSource() {
|
|
1595
|
+
return [
|
|
1596
|
+
"// WebMCP page tools — loaded only when the browser exposes the API.",
|
|
1597
|
+
"if (",
|
|
1598
|
+
" typeof document !== \"undefined\" &&",
|
|
1599
|
+
" (document.modelContext || (typeof navigator !== \"undefined\" && navigator.modelContext))",
|
|
1600
|
+
") {",
|
|
1601
|
+
" import(\"virtual:pracht/webmcp\").catch(() => {});",
|
|
1602
|
+
"}",
|
|
1603
|
+
""
|
|
1604
|
+
];
|
|
1605
|
+
}
|
|
1606
|
+
function hasWebmcpCapabilities(options = {}, root = process.cwd()) {
|
|
1607
|
+
try {
|
|
1608
|
+
return extractCapabilities(options, root).some((capability) => capability.webmcp);
|
|
1609
|
+
} catch {
|
|
1610
|
+
return true;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
function isPlainObject(value) {
|
|
1614
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1615
|
+
}
|
|
1616
|
+
//#endregion
|
|
1308
1617
|
//#region src/plugin-codegen.ts
|
|
1309
1618
|
const ROUTE_MODULE_EXTENSIONS = new Set([
|
|
1310
1619
|
".ts",
|
|
@@ -1500,7 +1809,8 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1500
1809
|
" findModuleKey,",
|
|
1501
1810
|
" });",
|
|
1502
1811
|
"}",
|
|
1503
|
-
""
|
|
1812
|
+
"",
|
|
1813
|
+
...hasWebmcpCapabilities(resolved, buildOptions.root) ? createWebmcpBootstrapSource() : []
|
|
1504
1814
|
].join("\n");
|
|
1505
1815
|
}
|
|
1506
1816
|
/**
|
|
@@ -1509,15 +1819,17 @@ function createPrachtClientModuleSource(options = {}, buildOptions = {}) {
|
|
|
1509
1819
|
* manifest, the router, or the full client runtime: it only scans the DOM
|
|
1510
1820
|
* for island markers and hydrates the islands present on the page.
|
|
1511
1821
|
*/
|
|
1512
|
-
function createPrachtIslandsClientModuleSource(options = {}) {
|
|
1513
|
-
const
|
|
1822
|
+
function createPrachtIslandsClientModuleSource(options = {}, buildOptions = {}) {
|
|
1823
|
+
const resolved = resolveOptions(options);
|
|
1824
|
+
const islandsGlob = `${resolved.islandsDir}/**/*.{ts,tsx,js,jsx}`;
|
|
1514
1825
|
return [
|
|
1515
1826
|
"import { hydrateIslands } from \"@pracht/core/islands-client\";",
|
|
1516
1827
|
"",
|
|
1517
1828
|
`const islandModules = import.meta.glob(${JSON.stringify(islandsGlob)});`,
|
|
1518
1829
|
"",
|
|
1519
1830
|
"hydrateIslands({ modules: islandModules });",
|
|
1520
|
-
""
|
|
1831
|
+
"",
|
|
1832
|
+
...hasWebmcpCapabilities(resolved, buildOptions.root) ? createWebmcpBootstrapSource() : []
|
|
1521
1833
|
].join("\n");
|
|
1522
1834
|
}
|
|
1523
1835
|
function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
@@ -1532,7 +1844,9 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1532
1844
|
jsManifest: {}
|
|
1533
1845
|
};
|
|
1534
1846
|
const adapter = resolved.adapter;
|
|
1535
|
-
const
|
|
1847
|
+
const llmsTxtConfig = resolveLlmsTxtConfig(resolved, buildOptions.root);
|
|
1848
|
+
let prachtImports = adapter?.serverImports ? adapter.serverImports + "\nimport { prerenderApp } from \"@pracht/core/server\";" : "import { resolveApp, resolveApiRoutes, prerenderApp } from \"@pracht/core/server\";";
|
|
1849
|
+
if (llmsTxtConfig) prachtImports += "\nimport { buildLlmsTxt } from \"@pracht/core/server\";";
|
|
1536
1850
|
const appImport = isPagesMode ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`;
|
|
1537
1851
|
const islandsEntryUrl = buildOptions.isBuild ? clientBuild.islandsEntryUrl : ISLANDS_CLIENT_BROWSER_PATH;
|
|
1538
1852
|
const islandsGlob = `${resolved.islandsDir}/**/*.{ts,tsx,js,jsx}`;
|
|
@@ -1563,11 +1877,37 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
|
|
|
1563
1877
|
`export const prerenderConcurrency = ${JSON.stringify(resolved.prerenderConcurrency)};`,
|
|
1564
1878
|
`export const budgets = ${JSON.stringify(resolved.budgets)};`,
|
|
1565
1879
|
"export { prerenderApp };",
|
|
1880
|
+
...llmsTxtConfig ? [
|
|
1881
|
+
"// llms.txt (https://llmstxt.org) generated from the resolved app graph.",
|
|
1882
|
+
"// `pracht build` writes it to dist/client/llms.txt; the dev SSR",
|
|
1883
|
+
"// middleware serves it at /llms.txt.",
|
|
1884
|
+
`const llmsTxtConfig = ${JSON.stringify(llmsTxtConfig)};`,
|
|
1885
|
+
"export const generateLlmsTxt = () =>",
|
|
1886
|
+
" buildLlmsTxt({ ...llmsTxtConfig, apiRoutes, app: resolvedApp, registry });"
|
|
1887
|
+
] : [],
|
|
1566
1888
|
""
|
|
1567
1889
|
];
|
|
1568
1890
|
if (adapter) source.push(adapter.createServerEntryModule());
|
|
1569
1891
|
return source.join("\n");
|
|
1570
1892
|
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Fill llms.txt title/description from the app's package.json when the user
|
|
1895
|
+
* did not set them explicitly. Returns null when the feature is disabled so
|
|
1896
|
+
* the server module codegen stays byte-for-byte unchanged.
|
|
1897
|
+
*/
|
|
1898
|
+
function resolveLlmsTxtConfig(resolved, root = process.cwd()) {
|
|
1899
|
+
if (!resolved.llmsTxt) return null;
|
|
1900
|
+
let pkg = {};
|
|
1901
|
+
try {
|
|
1902
|
+
pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf-8"));
|
|
1903
|
+
} catch {}
|
|
1904
|
+
const config = { title: resolved.llmsTxt.title ?? (typeof pkg.name === "string" && pkg.name ? pkg.name : "App") };
|
|
1905
|
+
const description = resolved.llmsTxt.description ?? (typeof pkg.description === "string" && pkg.description ? pkg.description : void 0);
|
|
1906
|
+
if (description) config.description = description;
|
|
1907
|
+
if (resolved.llmsTxt.origin) config.origin = resolved.llmsTxt.origin;
|
|
1908
|
+
if (resolved.llmsTxt.include) config.include = resolved.llmsTxt.include;
|
|
1909
|
+
return config;
|
|
1910
|
+
}
|
|
1571
1911
|
function createApplyRouteLoaderHintsSource() {
|
|
1572
1912
|
return [
|
|
1573
1913
|
"function applyRouteLoaderHints(resolvedApp, routeLoaderHints) {",
|
|
@@ -1618,6 +1958,7 @@ function createPrachtRegistryModuleSource(options = {}) {
|
|
|
1618
1958
|
`export const middlewareModules = import.meta.glob(${JSON.stringify(`${resolved.middlewareDir}/**/*.{ts,tsx,js,jsx}`)});`,
|
|
1619
1959
|
`export const apiModules = import.meta.glob(${JSON.stringify(`${resolved.apiDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
1620
1960
|
`export const dataModules = import.meta.glob(${JSON.stringify(`${resolved.serverDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
1961
|
+
`export const capabilityModules = import.meta.glob(${JSON.stringify(`${resolved.capabilitiesDir}/**/*.{ts,js,tsx,jsx}`)});`,
|
|
1621
1962
|
"",
|
|
1622
1963
|
"export const registry = {",
|
|
1623
1964
|
" routeModules,",
|
|
@@ -1625,6 +1966,7 @@ function createPrachtRegistryModuleSource(options = {}) {
|
|
|
1625
1966
|
" middlewareModules,",
|
|
1626
1967
|
" apiModules,",
|
|
1627
1968
|
" dataModules,",
|
|
1969
|
+
" capabilityModules,",
|
|
1628
1970
|
"};"
|
|
1629
1971
|
].join("\n");
|
|
1630
1972
|
}
|
|
@@ -1654,9 +1996,14 @@ function generatePagesAppInlineSource(options, root = process.cwd()) {
|
|
|
1654
1996
|
const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
|
|
1655
1997
|
const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
|
|
1656
1998
|
const DEVTOOLS_JSON_PATH = "/_pracht.json";
|
|
1999
|
+
const LLMS_TXT_PATH = "/llms.txt";
|
|
1657
2000
|
function createDevSSRMiddleware(server, options = {}) {
|
|
1658
2001
|
const maxBodySize = options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE;
|
|
1659
2002
|
let warnedDevtoolsCollision = false;
|
|
2003
|
+
let warnedLlmsTxtCollision = false;
|
|
2004
|
+
if (options.llmsTxt && typeof server.config.publicDir === "string") {
|
|
2005
|
+
if (existsSync(join(server.config.publicDir, "llms.txt"))) server.config.logger.warn("[pracht] Both public/llms.txt and the pracht({ llmsTxt }) option are present. Dev serves the static public/llms.txt, but \"pracht build\" overwrites it with the generated content. Remove one to avoid a dev/production mismatch.");
|
|
2006
|
+
}
|
|
1660
2007
|
return async (req, res, next) => {
|
|
1661
2008
|
const url = req.url ?? "/";
|
|
1662
2009
|
const requestUrl = new URL(url, "http://localhost");
|
|
@@ -1681,6 +2028,17 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
1681
2028
|
});
|
|
1682
2029
|
return;
|
|
1683
2030
|
}
|
|
2031
|
+
if (options.llmsTxt && requestUrl.pathname === "/llms.txt" && BODYLESS_METHODS.has((req.method ?? "GET").toUpperCase()) && typeof serverMod.generateLlmsTxt === "function") {
|
|
2032
|
+
if (!warnedLlmsTxtCollision && matchesResolvedRoute("/llms.txt", routeMatchers)) {
|
|
2033
|
+
warnedLlmsTxtCollision = true;
|
|
2034
|
+
server.config.logger.warn(`[pracht] An app route matches ${LLMS_TXT_PATH}, which is reserved by the pracht({ llmsTxt }) option. The generated llms.txt wins; disable the option to serve the app route instead.`);
|
|
2035
|
+
}
|
|
2036
|
+
const llmsTxt = await serverMod.generateLlmsTxt();
|
|
2037
|
+
res.statusCode = 200;
|
|
2038
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
2039
|
+
res.end(llmsTxt);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
1684
2042
|
if (shouldBypassDevSSR(requestUrl, req, routeMatchers)) return next();
|
|
1685
2043
|
if (isDevNotFoundRequest(requestUrl, req, routeMatchers)) return serveDevNotFound(server, res, next, url, requestUrl.pathname, routeMatchers);
|
|
1686
2044
|
let webRequest;
|
|
@@ -1704,7 +2062,8 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
1704
2062
|
apiRoutes: serverMod.apiRoutes,
|
|
1705
2063
|
timings
|
|
1706
2064
|
});
|
|
1707
|
-
|
|
2065
|
+
const responseContentType = response.headers.get("content-type") ?? "";
|
|
2066
|
+
if (response.status === 404 && !responseContentType.includes("application/json") && !routeMatchers.app?.notFound) return next();
|
|
1708
2067
|
const contentType = response.headers.get("content-type") ?? "text/html";
|
|
1709
2068
|
let body = await response.text();
|
|
1710
2069
|
if (contentType.includes("text/html")) body = await server.transformIndexHtml(url, body);
|
|
@@ -1726,10 +2085,13 @@ function createDevSSRMiddleware(server, options = {}) {
|
|
|
1726
2085
|
*/
|
|
1727
2086
|
async function serveDevtools(server, res, options) {
|
|
1728
2087
|
const devtools = await server.ssrLoadModule("@pracht/core/devtools");
|
|
2088
|
+
const capabilityModules = (await server.ssrLoadModule(PRACHT_SERVER_MODULE_ID)).registry?.capabilityModules;
|
|
1729
2089
|
const graph = await devtools.buildAppGraph({
|
|
1730
2090
|
apiRoutes: options.apiRoutes,
|
|
1731
2091
|
app: options.app,
|
|
1732
|
-
loadModule: (file) =>
|
|
2092
|
+
loadModule: async (file) => {
|
|
2093
|
+
return await resolveRegistryModule(capabilityModules, file) ?? server.ssrLoadModule(file);
|
|
2094
|
+
},
|
|
1733
2095
|
readSource: (file) => readFileSync(resolve(server.config.root, `.${file}`), "utf-8")
|
|
1734
2096
|
});
|
|
1735
2097
|
if (options.wantsJson) {
|
|
@@ -1960,16 +2322,20 @@ function pracht(options = {}) {
|
|
|
1960
2322
|
if (isIslandsClientModule(id)) return PRACHT_ISLANDS_CLIENT_MODULE_ID;
|
|
1961
2323
|
if (isClientModule(id)) return PRACHT_CLIENT_MODULE_ID;
|
|
1962
2324
|
if (isServerModule(id)) return PRACHT_SERVER_MODULE_ID;
|
|
2325
|
+
if (isCapabilitiesModule(id)) return PRACHT_CAPABILITIES_MODULE_ID;
|
|
2326
|
+
if (isWebmcpModule(id)) return PRACHT_WEBMCP_MODULE_ID;
|
|
1963
2327
|
if (id === "@pracht/core/env/server" && !resolveIdOptions?.ssr && !resolveIdOptions?.scan) throw new Error(`[pracht] ${JSON.stringify(SERVER_ENV_MODULE_ID)} was imported by ${JSON.stringify(importer ?? "unknown module")} in client code. serverEnv is server-only — read it inside loaders, middleware, or API routes, or use publicEnv (PRACHT_PUBLIC_-prefixed variables) from "@pracht/core" instead.`);
|
|
1964
2328
|
return null;
|
|
1965
2329
|
},
|
|
1966
2330
|
load(id) {
|
|
1967
|
-
if (isIslandsClientModule(id)) return createPrachtIslandsClientModuleSource(resolved);
|
|
2331
|
+
if (isIslandsClientModule(id)) return createPrachtIslandsClientModuleSource(resolved, { root });
|
|
1968
2332
|
if (isClientModule(id)) return createPrachtClientModuleSource(resolved, { root });
|
|
1969
2333
|
if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
|
|
1970
2334
|
root,
|
|
1971
2335
|
isBuild
|
|
1972
2336
|
});
|
|
2337
|
+
if (isCapabilitiesModule(id)) return createPrachtCapabilitiesClientModuleSource(resolved, { root });
|
|
2338
|
+
if (isWebmcpModule(id)) return createPrachtWebmcpModuleSource(resolved, { root });
|
|
1973
2339
|
return null;
|
|
1974
2340
|
},
|
|
1975
2341
|
transform(code, id) {
|
|
@@ -1986,7 +2352,10 @@ function pracht(options = {}) {
|
|
|
1986
2352
|
if (isPagesMode) watchPagesDirectory(server, resolved, root);
|
|
1987
2353
|
if (resolved.adapter.ownsDevServer) return;
|
|
1988
2354
|
return () => {
|
|
1989
|
-
server.middlewares.use(createDevSSRMiddleware(server, {
|
|
2355
|
+
server.middlewares.use(createDevSSRMiddleware(server, {
|
|
2356
|
+
llmsTxt: !!resolved.llmsTxt,
|
|
2357
|
+
maxBodySize: resolved.maxBodySize
|
|
2358
|
+
}));
|
|
1990
2359
|
};
|
|
1991
2360
|
},
|
|
1992
2361
|
handleHotUpdate({ file, server }) {
|
|
@@ -2008,7 +2377,8 @@ function pracht(options = {}) {
|
|
|
2008
2377
|
resolved.middlewareDir,
|
|
2009
2378
|
resolved.apiDir,
|
|
2010
2379
|
resolved.serverDir,
|
|
2011
|
-
resolved.islandsDir
|
|
2380
|
+
resolved.islandsDir,
|
|
2381
|
+
resolved.capabilitiesDir
|
|
2012
2382
|
].some((dir) => relative.startsWith(dir))) {
|
|
2013
2383
|
const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
|
|
2014
2384
|
if (serverMod) server.moduleGraph.invalidateModule(serverMod);
|
|
@@ -2020,6 +2390,15 @@ function pracht(options = {}) {
|
|
|
2020
2390
|
const islandsMod = server.moduleGraph.getModuleById(PRACHT_ISLANDS_CLIENT_MODULE_ID);
|
|
2021
2391
|
if (islandsMod) server.moduleGraph.invalidateModule(islandsMod);
|
|
2022
2392
|
}
|
|
2393
|
+
if (relative.startsWith(resolved.capabilitiesDir)) for (const moduleId of [
|
|
2394
|
+
PRACHT_CAPABILITIES_MODULE_ID,
|
|
2395
|
+
PRACHT_WEBMCP_MODULE_ID,
|
|
2396
|
+
PRACHT_CLIENT_MODULE_ID,
|
|
2397
|
+
PRACHT_ISLANDS_CLIENT_MODULE_ID
|
|
2398
|
+
]) {
|
|
2399
|
+
const capabilityMod = server.moduleGraph.getModuleById(moduleId);
|
|
2400
|
+
if (capabilityMod) server.moduleGraph.invalidateModule(capabilityMod);
|
|
2401
|
+
}
|
|
2023
2402
|
}
|
|
2024
2403
|
}
|
|
2025
2404
|
};
|
|
@@ -2112,7 +2491,8 @@ function createPrachtOptimizeDepsEntries(resolved) {
|
|
|
2112
2491
|
`${toOptimizeDepsEntry(resolved.middlewareDir)}/**/*.${scriptExtensions}`,
|
|
2113
2492
|
`${toOptimizeDepsEntry(resolved.apiDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
2114
2493
|
`${toOptimizeDepsEntry(resolved.serverDir)}/**/*.{ts,js,tsx,jsx}`,
|
|
2115
|
-
`${toOptimizeDepsEntry(resolved.islandsDir)}/**/*.${scriptExtensions}
|
|
2494
|
+
`${toOptimizeDepsEntry(resolved.islandsDir)}/**/*.${scriptExtensions}`,
|
|
2495
|
+
`${toOptimizeDepsEntry(resolved.capabilitiesDir)}/**/*.{ts,js,tsx,jsx}`
|
|
2116
2496
|
];
|
|
2117
2497
|
return [...new Set(entries.filter(Boolean))];
|
|
2118
2498
|
}
|
|
@@ -2180,4 +2560,4 @@ function withTrailingSep(p) {
|
|
|
2180
2560
|
return p.endsWith("/") ? p : `${p}/`;
|
|
2181
2561
|
}
|
|
2182
2562
|
//#endregion
|
|
2183
|
-
export { PRACHT_CLIENT_MODULE_ID, PRACHT_ISLANDS_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, PUBLIC_ENV_PREFIX, VITE_BUILTIN_ENV_VARS, createEnvSafetyPlugin, createPrachtClientModuleSource, createPrachtIslandsClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, formatEnvLeakError, pracht, scanCodeForEnvLeaks };
|
|
2563
|
+
export { PRACHT_CAPABILITIES_MODULE_ID, PRACHT_CLIENT_MODULE_ID, PRACHT_ISLANDS_CLIENT_MODULE_ID, PRACHT_SERVER_MODULE_ID, PRACHT_WEBMCP_MODULE_ID, PUBLIC_ENV_PREFIX, VITE_BUILTIN_ENV_VARS, createEnvSafetyPlugin, createPrachtCapabilitiesClientModuleSource, createPrachtClientModuleSource, createPrachtIslandsClientModuleSource, createPrachtRegistryModuleSource, createPrachtServerModuleSource, createPrachtWebmcpModuleSource, extractCapabilities, formatEnvLeakError, pracht, scanCodeForEnvLeaks };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,9 +44,10 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@preact/preset-vite": "^2.10.5",
|
|
46
46
|
"@prefresh/vite": "^2.0.0",
|
|
47
|
-
"@pracht/adapter-node": "0.3.
|
|
48
|
-
"@pracht/core": "0.
|
|
49
|
-
"@pracht/preact-ssr-precompile": "0.1.2"
|
|
47
|
+
"@pracht/adapter-node": "0.3.3",
|
|
48
|
+
"@pracht/core": "0.11.0",
|
|
49
|
+
"@pracht/preact-ssr-precompile": "0.1.2",
|
|
50
|
+
"@pracht/capabilities": "0.1.0"
|
|
50
51
|
},
|
|
51
52
|
"peerDependencies": {
|
|
52
53
|
"vite": "^8.0.0"
|