pagegraph 0.11.0 → 0.13.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.
Files changed (100) hide show
  1. package/README.md +403 -148
  2. package/dist/audit.d.ts +2 -2
  3. package/dist/audit.js +4 -4
  4. package/dist/build-only/audit.d.ts +1 -0
  5. package/dist/build-only/audit.js +6 -0
  6. package/dist/build-only/audit.js.map +1 -0
  7. package/dist/build-only/claims.d.ts +1 -0
  8. package/dist/build-only/claims.js +6 -0
  9. package/dist/build-only/claims.js.map +1 -0
  10. package/dist/build-only/config.d.ts +1 -0
  11. package/dist/build-only/config.js +6 -0
  12. package/dist/build-only/config.js.map +1 -0
  13. package/dist/build-only/oxlint.d.ts +1 -0
  14. package/dist/build-only/oxlint.js +6 -0
  15. package/dist/build-only/oxlint.js.map +1 -0
  16. package/dist/build-only/tanstack-start.d.ts +1 -0
  17. package/dist/build-only/tanstack-start.js +6 -0
  18. package/dist/build-only/tanstack-start.js.map +1 -0
  19. package/dist/build-only/vite.d.ts +1 -0
  20. package/dist/build-only/vite.js +6 -0
  21. package/dist/build-only/vite.js.map +1 -0
  22. package/dist/{checks-nk8ZRRIA.js → checks-BS-aDXB_.js} +4 -142
  23. package/dist/checks-BS-aDXB_.js.map +1 -0
  24. package/dist/claims-Bch7Lxlo.d.ts +92 -0
  25. package/dist/claims-Bv7voeEV.js +431 -0
  26. package/dist/claims-Bv7voeEV.js.map +1 -0
  27. package/dist/claims.d.ts +94 -0
  28. package/dist/claims.js +2 -0
  29. package/dist/cli.js +10368 -8485
  30. package/dist/cli.js.map +1 -1
  31. package/dist/config.d.ts +4 -83
  32. package/dist/config.js +3 -122
  33. package/dist/config.js.map +1 -1
  34. package/dist/{content-CB_kHmJ3.js → content-CHd0X4s6.js} +3 -2
  35. package/dist/{content-CB_kHmJ3.js.map → content-CHd0X4s6.js.map} +1 -1
  36. package/dist/{content-DVL2iyGU.d.ts → content-DgNr7apS.d.ts} +5 -130
  37. package/dist/declare-D5o7hwmv.js +9 -0
  38. package/dist/declare-D5o7hwmv.js.map +1 -0
  39. package/dist/document-CztEu3Vd.d.ts +109 -0
  40. package/dist/document-Dvemcv7V.js +379 -0
  41. package/dist/document-Dvemcv7V.js.map +1 -0
  42. package/dist/facts-1OKclPOk.js +115 -0
  43. package/dist/facts-1OKclPOk.js.map +1 -0
  44. package/dist/graph-D1PIndsC.d.ts +12238 -0
  45. package/dist/graph-DMNOLXLu.js +172 -0
  46. package/dist/graph-DMNOLXLu.js.map +1 -0
  47. package/dist/index-C5w9ripz.d.ts +204 -0
  48. package/dist/index.d.ts +6 -196
  49. package/dist/index.js +55 -134
  50. package/dist/index.js.map +1 -1
  51. package/dist/inline-BbS93GAT.js +682 -0
  52. package/dist/inline-BbS93GAT.js.map +1 -0
  53. package/dist/llms-Dhr_5u2O.js +19 -0
  54. package/dist/llms-Dhr_5u2O.js.map +1 -0
  55. package/dist/markdown-C6dRE9zD.js +168 -0
  56. package/dist/markdown-C6dRE9zD.js.map +1 -0
  57. package/dist/markdown-path-lJB9BAVZ.js +6 -0
  58. package/dist/markdown-path-lJB9BAVZ.js.map +1 -0
  59. package/dist/message-CuBjgZ74.js +12 -0
  60. package/dist/message-CuBjgZ74.js.map +1 -0
  61. package/dist/oxlint.d.ts +58 -0
  62. package/dist/oxlint.js +202 -0
  63. package/dist/oxlint.js.map +1 -0
  64. package/dist/page-heads-BfjWQvau.d.ts +108 -0
  65. package/dist/page-heads-BgbaIjj9.js +25 -0
  66. package/dist/page-heads-BgbaIjj9.js.map +1 -0
  67. package/dist/projections-Bk-qOOsR.d.ts +78 -0
  68. package/dist/projections-D0oOFKiY.js +142 -0
  69. package/dist/projections-D0oOFKiY.js.map +1 -0
  70. package/dist/react.d.ts +27 -12051
  71. package/dist/react.js +14 -4
  72. package/dist/react.js.map +1 -1
  73. package/dist/route-config-35ZEzkNC.js +308 -0
  74. package/dist/route-config-35ZEzkNC.js.map +1 -0
  75. package/dist/route-config-wneg6ktn.d.ts +29 -0
  76. package/dist/tanstack-start/index.d.ts +56 -0
  77. package/dist/tanstack-start/index.js +717 -0
  78. package/dist/tanstack-start/index.js.map +1 -0
  79. package/dist/tanstack-start/markdown.d.ts +12 -0
  80. package/dist/tanstack-start/markdown.js +149 -0
  81. package/dist/tanstack-start/markdown.js.map +1 -0
  82. package/dist/tanstack-start/prerender-worker.d.ts +2 -0
  83. package/dist/tanstack-start/prerender-worker.js +7 -0
  84. package/dist/tanstack-start/prerender-worker.js.map +1 -0
  85. package/dist/tanstack-start/react.d.ts +7 -0
  86. package/dist/tanstack-start/react.js +12 -0
  87. package/dist/tanstack-start/react.js.map +1 -0
  88. package/dist/tanstack-start/server.d.ts +20 -0
  89. package/dist/tanstack-start/server.js +77 -0
  90. package/dist/tanstack-start/server.js.map +1 -0
  91. package/dist/vite-graph-loader-CsGiYNWE.js +105 -0
  92. package/dist/vite-graph-loader-CsGiYNWE.js.map +1 -0
  93. package/dist/vite-graph-loader-DZCwejuJ.d.ts +133 -0
  94. package/dist/vite.d.ts +2 -28
  95. package/dist/vite.js +1 -307
  96. package/dist/wire-B4RQMc4e.js +26 -0
  97. package/dist/wire-B4RQMc4e.js.map +1 -0
  98. package/package.json +70 -7
  99. package/dist/checks-nk8ZRRIA.js.map +0 -1
  100. package/dist/vite.js.map +0 -1
@@ -0,0 +1,717 @@
1
+ import { t as buildSeoGraph } from "../graph-DMNOLXLu.js";
2
+ import { n as graphToJson } from "../wire-B4RQMc4e.js";
3
+ import { a as globToRegExp } from "../checks-BS-aDXB_.js";
4
+ import { o as hashDocument } from "../inline-BbS93GAT.js";
5
+ import { t as decodeFacts } from "../facts-1OKclPOk.js";
6
+ import { n as documentMarkdown } from "../markdown-C6dRE9zD.js";
7
+ import { i as seoRouteConfig, n as inMemoryWriteFs, r as resolveOptions, t as deriveRouteConfig } from "../route-config-35ZEzkNC.js";
8
+ import { n as withCleanStdout } from "../vite-graph-loader-CsGiYNWE.js";
9
+ import { r as replayClaims } from "../claims-Bv7voeEV.js";
10
+ import { t as MARKDOWN_CAPTURE_PATH } from "../markdown-path-lJB9BAVZ.js";
11
+ import { existsSync } from "node:fs";
12
+ import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
13
+ import { dirname, join, relative, resolve, sep } from "node:path";
14
+ import { Generator, getConfig } from "@tanstack/router-generator";
15
+ import { createRunnableDevEnvironment, parseAst, resolveConfig } from "vite";
16
+ import * as Effect from "effect/Effect";
17
+ import * as Schema from "effect/Schema";
18
+ import { pathToFileURL } from "node:url";
19
+ //#region src/tanstack-start/routes.ts
20
+ /**
21
+ * Route discovery for the graph: the router's own generator parses the routes
22
+ * directory (the same parse `routeTree.gen.ts` comes from), into memory only.
23
+ */
24
+ async function scanRoutes(root, routesDirectory) {
25
+ const cacheDirectory = resolve(root, "node_modules/.cache/pagegraph");
26
+ let scanned;
27
+ const config = getConfig({
28
+ routesDirectory: resolve(root, routesDirectory),
29
+ generatedRouteTree: resolve(cacheDirectory, "route-tree.gen.ts"),
30
+ tmpDir: resolve(cacheDirectory, "tmp"),
31
+ disableTypes: true,
32
+ enableRouteTreeFormatting: false,
33
+ disableLogging: true,
34
+ plugins: [{
35
+ name: "pagegraph:scan",
36
+ onRouteTreeChanged({ routeTree, routeNodes, rootRouteNode }) {
37
+ scanned = {
38
+ root: rootRouteNode,
39
+ tree: routeTree,
40
+ nodes: routeNodes
41
+ };
42
+ }
43
+ }]
44
+ }, root);
45
+ const memory = inMemoryWriteFs(config.generatedRouteTree);
46
+ await new Generator({
47
+ config,
48
+ root,
49
+ fs: memory.fs
50
+ }).run();
51
+ if (scanned === void 0) throw new Error(`pagegraph found no route tree in ${resolve(root, routesDirectory)}`);
52
+ return scanned;
53
+ }
54
+ //#endregion
55
+ //#region src/tanstack-start/graph.ts
56
+ /**
57
+ * Build the graph from the app's own modules, evaluated in the `pagegraph`
58
+ * Vite environment: one module per route file that declares `staticData`, plus
59
+ * the app's collections module. Never the route tree, which reaches every
60
+ * route — including app routes that only resolve inside the server runtime.
61
+ */
62
+ const GRAPH_ROUTE_TYPES = /* @__PURE__ */ new Set([
63
+ "__root",
64
+ "static",
65
+ "layout",
66
+ "pathless_layout"
67
+ ]);
68
+ const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
69
+ const routeStaticData = (module, file) => {
70
+ const route = module["Route"];
71
+ if (route?.options === void 0) throw new Error(`${file} has no \`Route\` export`);
72
+ const data = route.options.staticData;
73
+ return data === void 0 ? void 0 : {
74
+ seo: data.seo,
75
+ markdown: data.markdown,
76
+ llms: data.llms
77
+ };
78
+ };
79
+ const loadCollections = async (runner, root, fullPath) => {
80
+ const path = relative(root, fullPath);
81
+ const exported = (await runner.import(fullPath).catch((cause) => {
82
+ throw new Error(`pagegraph could not evaluate ${path}: ${messageOf(cause)}`, { cause });
83
+ }))["collections"];
84
+ if (exported === void 0) throw new Error(`${path} has no \`collections\` export`);
85
+ const collections = typeof exported === "function" ? await exported() : exported;
86
+ if (!Array.isArray(collections)) throw new Error(`${path} \`collections\` must be an array or return one`);
87
+ return collections;
88
+ };
89
+ /** Scan the routes directory and pick the modules an evaluation loads. */
90
+ async function planGraph(root, options) {
91
+ const scan = await scanRoutes(root, options.routesDirectory ?? "src/routes");
92
+ const excluded = (options.exclude ?? []).map(globToRegExp);
93
+ const participates = (node) => GRAPH_ROUTE_TYPES.has(node._fsRouteType) && (node._fsRouteType === "__root" || node.createFileRouteProps?.has("staticData") === true) && !excluded.some((pattern) => pattern.test(node.filePath));
94
+ return {
95
+ root,
96
+ options,
97
+ scan,
98
+ routeFiles: [scan.root, ...scan.nodes].filter(participates).map((node) => node.fullPath),
99
+ collectionsFile: options.collections === void 0 ? void 0 : resolve(root, options.collections),
100
+ factsFile: options.facts === void 0 ? void 0 : resolve(root, options.facts)
101
+ };
102
+ }
103
+ /**
104
+ * A route node's path relative to its parent, as the router matches it: route
105
+ * groups, pathless layouts, and trailing underscores contribute no segment.
106
+ */
107
+ const publicSegment = (node) => node.isNonPath === true || node.cleanedPath === void 0 || node.cleanedPath === "" ? void 0 : node.cleanedPath;
108
+ /** Evaluate a plan's modules through `runner`, then build the graph and the site runtime. */
109
+ async function evaluateGraph(runner, plan) {
110
+ const { root, options, scan } = plan;
111
+ const staticData = new Map(await Promise.all(plan.routeFiles.map(async (fullPath) => {
112
+ const file = relative(root, fullPath);
113
+ const routeFile = relative(resolve(root, options.routesDirectory ?? "src/routes"), fullPath);
114
+ const module = await runner.import(fullPath).catch((cause) => {
115
+ throw new Error(`pagegraph could not evaluate ${file} in the graph environment: ${messageOf(cause)}\nIf it is an app surface rather than a public page, add "${routeFile}" to pagegraph({ exclude }).`, { cause });
116
+ });
117
+ return [fullPath, routeStaticData(module, file)];
118
+ })));
119
+ const walkable = (node) => ({
120
+ options: {
121
+ path: publicSegment(node),
122
+ staticData: staticData.get(node.fullPath)
123
+ },
124
+ children: (node.children ?? []).map(walkable)
125
+ });
126
+ const routeTree = {
127
+ options: { staticData: staticData.get(scan.root.fullPath) },
128
+ children: scan.tree.map(walkable)
129
+ };
130
+ const collections = plan.collectionsFile === void 0 ? [] : await loadCollections(runner, root, plan.collectionsFile);
131
+ const graph = buildSeoGraph({
132
+ routeTree,
133
+ collections
134
+ });
135
+ const exclusions = options.routeConfig === void 0 ? [] : deriveRouteConfig(scan.nodes, resolveOptions({
136
+ ...options.routeConfig,
137
+ routesDirectory: options.routesDirectory
138
+ })).robotsExclusions;
139
+ const site = {
140
+ origin: options.origin,
141
+ indexable: options.indexable ?? true,
142
+ robots: {
143
+ disallow: [.../* @__PURE__ */ new Set([...exclusions, ...options.robots?.disallow ?? []])].sort(),
144
+ contentSignal: options.robots?.contentSignal,
145
+ directives: options.robots?.directives
146
+ }
147
+ };
148
+ let facts;
149
+ if (plan.factsFile !== void 0) {
150
+ const path = relative(root, plan.factsFile);
151
+ try {
152
+ const module = await runner.import(plan.factsFile);
153
+ if (module["facts"] === void 0) throw new Error(`${path} has no \`facts\` export`);
154
+ facts = decodeFacts(module["facts"]);
155
+ } catch (cause) {
156
+ throw new Error(`pagegraph could not evaluate ${path}: ${messageOf(cause)}`, { cause });
157
+ }
158
+ }
159
+ return {
160
+ graph,
161
+ site,
162
+ markdown: options.markdown === void 0 ? null : { origin: options.markdown.origin },
163
+ facts
164
+ };
165
+ }
166
+ //#endregion
167
+ //#region src/markdown/schema.ts
168
+ const InlineNode = Schema.suspend(() => Schema.Union([
169
+ Schema.String,
170
+ Schema.Struct({
171
+ fact: Schema.String,
172
+ text: Schema.String
173
+ }),
174
+ Schema.Struct({
175
+ tag: Schema.Literals([
176
+ "em",
177
+ "strong",
178
+ "a",
179
+ "code",
180
+ "br"
181
+ ]),
182
+ href: Schema.optionalKey(Schema.String),
183
+ children: Schema.Array(InlineNode)
184
+ })
185
+ ]));
186
+ const Message = Schema.Struct({
187
+ hash: Schema.String,
188
+ tree: Schema.Array(InlineNode),
189
+ text: Schema.String,
190
+ source: Schema.String,
191
+ facts: Schema.Array(Schema.String),
192
+ audience: Schema.Literals([
193
+ "all",
194
+ "agents",
195
+ "humans"
196
+ ])
197
+ });
198
+ const BodyEntry = Schema.Union([Schema.Struct({
199
+ type: Schema.Literal("message"),
200
+ message: Message
201
+ }), Schema.Struct({
202
+ type: Schema.Literal("visual"),
203
+ text: Schema.String,
204
+ source: Schema.String,
205
+ audience: Schema.Literals([
206
+ "all",
207
+ "agents",
208
+ "humans"
209
+ ])
210
+ })]);
211
+ const PageDocument = Schema.Struct({
212
+ path: Schema.String,
213
+ hash: Schema.String,
214
+ title: Schema.String,
215
+ description: Schema.String,
216
+ sections: Schema.Array(Schema.Struct({
217
+ claimsMarkdown: Schema.optionalKey(Schema.String),
218
+ evidence: Schema.optionalKey(Schema.Boolean),
219
+ kind: Schema.String,
220
+ id: Schema.String,
221
+ title: Schema.optionalKey(Message),
222
+ language: Schema.optionalKey(Schema.String),
223
+ body: Schema.Array(BodyEntry),
224
+ items: Schema.Array(Schema.Struct({
225
+ title: Schema.optionalKey(Message),
226
+ href: Schema.optionalKey(Schema.String),
227
+ body: Schema.Array(BodyEntry),
228
+ cells: Schema.Array(Message),
229
+ source: Schema.String,
230
+ audience: Schema.Literals([
231
+ "all",
232
+ "agents",
233
+ "humans"
234
+ ])
235
+ })),
236
+ source: Schema.String,
237
+ audience: Schema.Literals([
238
+ "all",
239
+ "agents",
240
+ "humans"
241
+ ])
242
+ })),
243
+ messages: Schema.Array(Message),
244
+ facts: Schema.Array(Schema.String)
245
+ });
246
+ const CaptureBundle = Schema.Struct({
247
+ origin: Schema.String,
248
+ documents: Schema.Array(PageDocument),
249
+ heads: Schema.Array(Schema.Struct({
250
+ path: Schema.String,
251
+ title: Schema.String,
252
+ description: Schema.String
253
+ }))
254
+ });
255
+ //#endregion
256
+ //#region src/tanstack-start/persist-markdown.ts
257
+ /** File names must remain inside the package's capture and the client's output trees. */
258
+ function documentName(path) {
259
+ if (!path.startsWith("/") || path.startsWith("//") || /[?#\\$\u0000-\u001f\u007f]/.test(path) || path !== "/" && path.endsWith("/") || path.slice(1).split("/").some((part) => part === "." || part === ".." || part === "*" || part === "" && path !== "/")) throw new Error(`Invalid captured document path "${path}"`);
260
+ return path === "/" ? "index" : path.slice(1);
261
+ }
262
+ function verifyDocument(document) {
263
+ const name = documentName(document.path);
264
+ const { hash, ...content } = document;
265
+ if (!/^[0-9a-f]{64}$/.test(hash) || hashDocument(content) !== hash) throw new Error(`Invalid captured document hash for "${document.path}"`);
266
+ return name;
267
+ }
268
+ /** Only valid captures at their derived paths belong to pagegraph; preserve unrelated files. */
269
+ async function pruneDocuments(directory, paths) {
270
+ const visit = async (folder) => {
271
+ const entries = await readdir(folder, { withFileTypes: true });
272
+ for (const entry of entries) {
273
+ const file = resolve(folder, entry.name);
274
+ if (entry.isDirectory()) {
275
+ await visit(file);
276
+ continue;
277
+ }
278
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
279
+ const contents = await readFile(file, "utf8");
280
+ let document;
281
+ let name;
282
+ try {
283
+ document = Schema.decodeUnknownSync(Schema.fromJsonString(PageDocument))(contents, { onExcessProperty: "error" });
284
+ name = verifyDocument(document);
285
+ } catch {
286
+ continue;
287
+ }
288
+ if (file === resolve(directory, `${name}.json`) && !paths.has(document.path)) await unlink(file);
289
+ }
290
+ };
291
+ await visit(directory);
292
+ }
293
+ /** Documents, twins, and graph heads are persisted before prerender onSuccess returns. */
294
+ async function persistMarkdownCapture(html, root, clientOutDir, claims, facts = {}) {
295
+ const bundle = Schema.decodeUnknownSync(Schema.fromJsonString(CaptureBundle))(html, { onExcessProperty: "error" });
296
+ const origin = new URL(bundle.origin);
297
+ if (origin.protocol !== "https:" && origin.protocol !== "http:" || origin.origin !== bundle.origin) throw new Error(`Invalid captured markdown origin "${bundle.origin}"`);
298
+ const active = /* @__PURE__ */ new Set();
299
+ const destinations = /* @__PURE__ */ new Set();
300
+ const names = bundle.documents.map((document) => {
301
+ if (active.has(document.path)) throw new Error(`Duplicate captured document path "${document.path}"`);
302
+ active.add(document.path);
303
+ const name = verifyDocument(document);
304
+ if (destinations.has(name)) throw new Error(`Captured document paths share the output name "${name}"`);
305
+ destinations.add(name);
306
+ return name;
307
+ });
308
+ await unlink(resolve(clientOutDir, MARKDOWN_CAPTURE_PATH.slice(1)));
309
+ const directory = resolve(root, ".pagegraph/documents");
310
+ await mkdir(directory, { recursive: true });
311
+ for (const [index, document] of bundle.documents.entries()) {
312
+ const name = names[index];
313
+ const destination = resolve(directory, `${name}.json`);
314
+ const twin = resolve(clientOutDir, `${name}.md`);
315
+ await mkdir(dirname(destination), { recursive: true });
316
+ await writeFile(destination, JSON.stringify(document));
317
+ await mkdir(dirname(twin), { recursive: true });
318
+ await writeFile(twin, documentMarkdown(document, bundle.origin));
319
+ }
320
+ await pruneDocuments(directory, active);
321
+ await writeFile(resolve(root, ".pagegraph/heads.json"), `${JSON.stringify(bundle.heads)}\n`);
322
+ if (claims !== void 0) await Effect.runPromise(replayClaims(root, bundle.documents, bundle.heads, facts, claims));
323
+ }
324
+ //#endregion
325
+ //#region src/tanstack-start/source.ts
326
+ var DocumentSourceError = class extends Schema.TaggedError()("DocumentSourceError", { message: Schema.String }) {};
327
+ const SOURCE_COMPONENTS = /* @__PURE__ */ new Set([
328
+ "T",
329
+ "Title",
330
+ "Section",
331
+ "Visual",
332
+ "ForAgents",
333
+ "ForHumans"
334
+ ]);
335
+ const parse = Effect.fn("DocumentSource.parse")(function* (code, id) {
336
+ return yield* Effect.try({
337
+ try: () => parseAst(code, { lang: id.endsWith(".tsx") ? "tsx" : id.endsWith(".ts") ? "ts" : id.endsWith(".jsx") ? "jsx" : "js" }, id),
338
+ catch: (cause) => new DocumentSourceError({ message: `Cannot parse document source ${id}: ${cause instanceof Error ? cause.message : String(cause)}` })
339
+ });
340
+ });
341
+ function componentNames(program) {
342
+ const named = /* @__PURE__ */ new Set();
343
+ const sections = /* @__PURE__ */ new Set();
344
+ const namespaces = /* @__PURE__ */ new Set();
345
+ for (const node of program.body) {
346
+ if (node.type !== "ImportDeclaration" || node.source.value !== "pagegraph/react" || node.importKind === "type") continue;
347
+ for (const specifier of node.specifiers) if (specifier.type === "ImportSpecifier") {
348
+ if (specifier.importKind === "type") continue;
349
+ const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
350
+ if (SOURCE_COMPONENTS.has(imported)) named.add(specifier.local.name);
351
+ if (imported === "Section") sections.add(specifier.local.name);
352
+ } else if (specifier.type === "ImportNamespaceSpecifier") namespaces.add(specifier.local.name);
353
+ }
354
+ return {
355
+ named,
356
+ sections,
357
+ namespaces
358
+ };
359
+ }
360
+ function memberParts(name) {
361
+ if (name.type === "JSXIdentifier") return [name.name];
362
+ if (name.type !== "JSXMemberExpression") return [];
363
+ return [...memberParts(name.object), name.property.name];
364
+ }
365
+ function isSourceComponent(name, names) {
366
+ const [base, ...members] = memberParts(name);
367
+ if (base === void 0) return false;
368
+ if (members.length === 0) return names.named.has(base);
369
+ if (names.namespaces.has(base)) return members.length === 1 && members[0] !== void 0 && SOURCE_COMPONENTS.has(members[0]) || members.length === 2 && members[0] === "Section" && members[1] === "Item";
370
+ return names.sections.has(base) && members.length === 1 && members[0] === "Item";
371
+ }
372
+ function walk(value, visit) {
373
+ if (Array.isArray(value)) for (const child of value) walk(child, visit);
374
+ else if (typeof value === "object" && value !== null) {
375
+ if (isOpeningElement(value)) visit(value);
376
+ for (const child of Object.values(value)) walk(child, visit);
377
+ }
378
+ }
379
+ function isOpeningElement(node) {
380
+ return "type" in node && node.type === "JSXOpeningElement";
381
+ }
382
+ function transformDocumentSource(code, id, root) {
383
+ const program = Effect.runSync(parse(code, id));
384
+ const names = componentNames(program);
385
+ const edits = [];
386
+ const file = relative(resolve(root), resolve(id)).split(sep).join("/");
387
+ walk(program, (node) => {
388
+ if (!isSourceComponent(node.name, names)) return;
389
+ if (node.attributes.some((attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" && attribute.name.name === "source")) return;
390
+ const line = code.slice(0, node.start).split("\n").length;
391
+ edits.push({
392
+ start: node.name.end,
393
+ end: node.name.end,
394
+ value: ` source=${JSON.stringify(`${file}:${line}`)}`
395
+ });
396
+ });
397
+ if (edits.length === 0) return void 0;
398
+ edits.sort((a, b) => b.start - a.start);
399
+ let transformed = code;
400
+ for (const edit of edits) transformed = transformed.slice(0, edit.start) + edit.value + transformed.slice(edit.end);
401
+ return { code: transformed };
402
+ }
403
+ //#endregion
404
+ //#region src/tanstack-start/plugin.ts
405
+ /**
406
+ * The `pagegraph()` Vite plugin for TanStack Start.
407
+ *
408
+ * The graph is built once per build (and on change in dev) inside the app's own
409
+ * Vite pipeline, then shipped to the server runtime as `virtual:pagegraph/runtime`.
410
+ * The Worker never rebuilds it and never imports build code.
411
+ *
412
+ * Route modules and content collections are evaluated in a dedicated Node
413
+ * environment, `pagegraph`, that shares the app's plugins, aliases, and
414
+ * `define`s. Two settings make that safe:
415
+ * - its `resolve.noExternal` is cleared, so TanStack Start's packages load from
416
+ * node_modules, where their virtual router entry is an inert stub. Bundled (as
417
+ * the app's server bundles them), they would pull in the whole route tree.
418
+ * - only route files that declare `staticData` are evaluated, one by one, never
419
+ * `routeTree.gen.ts`.
420
+ *
421
+ * Dev imports those modules through the environment's module runner and
422
+ * re-evaluates after a change reaches it. A build builds the environment first
423
+ * (a `buildApp` hook that runs before the framework's) with the build's own
424
+ * config and plugins, then imports the output. The CLI and `loadPageHeads` use
425
+ * {@link evaluateAppGraph}, which runs the dev path without a server.
426
+ */
427
+ const GRAPH_ENVIRONMENT$1 = "pagegraph";
428
+ const PLUGIN_NAME = "pagegraph";
429
+ const RESOLVED_RUNTIME_ID = `\0virtual:pagegraph/runtime`;
430
+ const ENTRY_ID = "virtual:pagegraph/entry";
431
+ const RESOLVED_ENTRY_ID = `\0${ENTRY_ID}`;
432
+ /** The runtime entry imports the virtual module, so dep pre-bundling must leave it to this plugin. */
433
+ const RUNTIME_ENTRIES = [
434
+ "pagegraph/tanstack-start/server",
435
+ "pagegraph/tanstack-start/markdown",
436
+ "pagegraph/tanstack-start/react"
437
+ ];
438
+ /** Where a build writes the graph environment's output, relative to the Vite root. */
439
+ const BUILD_DIRECTORY = "node_modules/.cache/pagegraph/build";
440
+ const runtimeModulesPlugin = {
441
+ name: "pagegraph:runtime-modules",
442
+ enforce: "post",
443
+ applyToEnvironment: (environment) => environment.name === GRAPH_ENVIRONMENT$1,
444
+ resolveId: {
445
+ filter: { id: /^(?!node:|data:|file:|virtual:)[a-z][a-z0-9+.-]*:/ },
446
+ handler: (id) => ({
447
+ id,
448
+ external: true
449
+ })
450
+ }
451
+ };
452
+ const runtimeModule = ({ graph, site, markdown, facts }) => `export const graph = ${JSON.stringify(graphToJson(graph))};\nexport const site = ${JSON.stringify(site)};\nexport const markdown = ${JSON.stringify(markdown)};\nexport const facts = ${JSON.stringify(facts) ?? "undefined"};\n`;
453
+ /** The graph environment's build entry: a loader per module the plan evaluates. */
454
+ const entryModule = (plan) => {
455
+ return `export const modules = {\n${[
456
+ ...plan.routeFiles,
457
+ ...plan.collectionsFile === void 0 ? [] : [plan.collectionsFile],
458
+ ...plan.factsFile === void 0 ? [] : [plan.factsFile]
459
+ ].map((file) => ` ${JSON.stringify(file)}: () => import(${JSON.stringify(file)}),`).join("\n")}\n};\n`;
460
+ };
461
+ /** Build the graph environment with the build's config, import its output, and evaluate. */
462
+ const buildGraph = async (builder, environment, plan) => {
463
+ await builder.build(environment);
464
+ const entry = join(resolve(environment.config.root, environment.config.build.outDir), "entry.mjs");
465
+ const { modules } = await import(pathToFileURL(entry).href);
466
+ return evaluateGraph({ import: (file) => {
467
+ const load = modules[file];
468
+ if (load === void 0) throw new Error(`${file} is not in the graph build`);
469
+ return load();
470
+ } }, plan);
471
+ };
472
+ function pagegraph(options) {
473
+ if (options.claims !== void 0 && options.markdown === void 0) throw new Error("Claims require pagegraph({ markdown: { origin }, claims: { rules, model } }) so the build captures documents.");
474
+ const api = { options };
475
+ let root = process.cwd();
476
+ let clientOutDir = resolve(root, "dist/client");
477
+ let server;
478
+ let devEnvironment;
479
+ /** The plan the graph environment's build entry is generated from. */
480
+ let buildPlan;
481
+ let current;
482
+ /** Bumped by every change that reaches the graph; an evaluation that spans one is discarded. */
483
+ let generation = 0;
484
+ const evaluateInDev = async (environment) => {
485
+ for (;;) {
486
+ const started = generation;
487
+ environment.runner.clearCache();
488
+ const result = await evaluateGraph(environment.runner, await planGraph(root, options));
489
+ if (started === generation) return result;
490
+ }
491
+ };
492
+ return Object.assign([
493
+ {
494
+ name: PLUGIN_NAME,
495
+ api,
496
+ sharedDuringBuild: true,
497
+ config() {
498
+ return { environments: { [GRAPH_ENVIRONMENT$1]: {
499
+ consumer: "server",
500
+ dev: { createEnvironment: (name, environmentConfig) => createRunnableDevEnvironment(name, environmentConfig) },
501
+ build: {
502
+ outDir: BUILD_DIRECTORY,
503
+ emptyOutDir: true,
504
+ copyPublicDir: false,
505
+ minify: false,
506
+ rolldownOptions: {
507
+ input: { entry: ENTRY_ID },
508
+ output: {
509
+ entryFileNames: "[name].mjs",
510
+ chunkFileNames: "chunks/[name]-[hash].mjs"
511
+ }
512
+ }
513
+ }
514
+ } } };
515
+ },
516
+ configEnvironment(name, environmentConfig) {
517
+ if (name === "pagegraph") {
518
+ environmentConfig.resolve ??= {};
519
+ environmentConfig.resolve.noExternal = [];
520
+ return;
521
+ }
522
+ environmentConfig.optimizeDeps ??= {};
523
+ environmentConfig.optimizeDeps.exclude = [...environmentConfig.optimizeDeps.exclude ?? [], ...RUNTIME_ENTRIES];
524
+ },
525
+ configResolved(resolved) {
526
+ root = resolved.root;
527
+ clientOutDir = resolve(root, resolved.environments.client?.build.outDir ?? "dist/client");
528
+ },
529
+ configureServer: {
530
+ order: "pre",
531
+ handler(devServer) {
532
+ server = devServer;
533
+ devEnvironment = devServer.environments[GRAPH_ENVIRONMENT$1];
534
+ }
535
+ },
536
+ hotUpdate({ file, modules }) {
537
+ if (this.environment.name !== "pagegraph") return;
538
+ const routesDirectory = resolve(root, options.routesDirectory ?? "src/routes");
539
+ if (modules.length === 0 && !file.startsWith(`${routesDirectory}/`)) return;
540
+ generation += 1;
541
+ current = void 0;
542
+ for (const other of Object.values(server?.environments ?? {})) {
543
+ if (other.name === "pagegraph") continue;
544
+ const runtime = other.moduleGraph.getModuleById(RESOLVED_RUNTIME_ID);
545
+ if (runtime !== void 0) other.reloadModule(runtime);
546
+ }
547
+ },
548
+ buildApp: {
549
+ order: "pre",
550
+ async handler(builder) {
551
+ const environment = builder.environments[GRAPH_ENVIRONMENT$1];
552
+ if (environment === void 0) return;
553
+ buildPlan = await planGraph(environment.config.root, options);
554
+ const built = buildGraph(builder, environment, buildPlan);
555
+ current = built;
556
+ await built;
557
+ environment.isBuilt = false;
558
+ }
559
+ },
560
+ resolveId: {
561
+ filter: { id: /^virtual:pagegraph\/(runtime|entry)$/ },
562
+ handler: (id) => `\0${id}`
563
+ },
564
+ load: {
565
+ filter: { id: /^\0virtual:pagegraph\/(runtime|entry)$/ },
566
+ async handler(id) {
567
+ if (id === RESOLVED_ENTRY_ID) {
568
+ if (buildPlan === void 0) throw new Error(`[${PLUGIN_NAME}] ${ENTRY_ID} is only built by the pagegraph buildApp hook`);
569
+ return entryModule(buildPlan);
570
+ }
571
+ if (this.environment.name === "pagegraph") return "export const graph = null;\nexport const site = null;\nexport const markdown = null;\nexport const facts = undefined;\n";
572
+ if (current === void 0) {
573
+ if (devEnvironment === void 0) throw new Error(`[${PLUGIN_NAME}] the graph was not built before ${this.environment.name} imported it; build with Vite's app builder (\`vite build\`).`);
574
+ current = evaluateInDev(devEnvironment);
575
+ }
576
+ return runtimeModule(await current);
577
+ }
578
+ }
579
+ },
580
+ {
581
+ name: "pagegraph:markdown",
582
+ enforce: "pre",
583
+ applyToEnvironment: (environment) => environment.name !== GRAPH_ENVIRONMENT$1,
584
+ resolveId: {
585
+ filter: { id: /^virtual:pagegraph\/prerender-server$/ },
586
+ handler: (id) => `\0${id}`
587
+ },
588
+ load: {
589
+ filter: { id: /^\0virtual:pagegraph\/prerender-server$/ },
590
+ handler: () => `export { default } from ${JSON.stringify(resolve(root, options.markdown?.serverEntry ?? "dist/server/index.js"))};`
591
+ },
592
+ transform: {
593
+ filter: {
594
+ id: /\.[jt]sx?(\?|$)/,
595
+ code: /pagegraph\/react/
596
+ },
597
+ handler(code, id) {
598
+ const file = id.split("?")[0];
599
+ if (file === void 0 || file.includes("/node_modules/") || file.endsWith(".gen.ts") || !file.startsWith(`${root}/`)) return;
600
+ return transformDocumentSource(code, file, root);
601
+ }
602
+ },
603
+ configurePreviewServer(previewServer) {
604
+ previewServer.middlewares.use((_request, response, next) => {
605
+ response.on("finish", () => {
606
+ const error = response.getHeader("x-pagegraph-error");
607
+ if (typeof error === "string") previewServer.config.logger.error(decodeURIComponent(error));
608
+ });
609
+ next();
610
+ });
611
+ }
612
+ },
613
+ runtimeModulesPlugin,
614
+ ...options.routeConfig === void 0 ? [] : [seoRouteConfig({
615
+ ...options.routeConfig,
616
+ routesDirectory: options.routesDirectory
617
+ })]
618
+ ], { prerenderPages: options.markdown === void 0 ? [] : [{
619
+ path: MARKDOWN_CAPTURE_PATH,
620
+ sitemap: { exclude: true },
621
+ prerender: { onSuccess: async ({ html }) => {
622
+ if (current === void 0) throw new Error("The pagegraph graph must be evaluated before Markdown persistence.");
623
+ const app = await current;
624
+ await persistMarkdownCapture(html, root, clientOutDir, options.claims, app.facts);
625
+ } }
626
+ }] });
627
+ }
628
+ //#endregion
629
+ //#region src/tanstack-start/load.ts
630
+ /**
631
+ * Evaluate an app's graph outside a running dev server — for a build, the
632
+ * CLI (`pagegraph.config.ts`), and build-time head loading — through the same
633
+ * `pagegraph` environment the dev server uses.
634
+ */
635
+ const GRAPH_ENVIRONMENT = "pagegraph";
636
+ const cliEnvironmentOverride = (command) => command === "build" ? { [GRAPH_ENVIRONMENT]: { isBundled: false } } : void 0;
637
+ /** Locate the config without overriding its authored root with an inline root. */
638
+ const appConfig = (options) => ({
639
+ configFile: options.configFile ?? [
640
+ "js",
641
+ "mjs",
642
+ "ts",
643
+ "cjs",
644
+ "mts",
645
+ "cts"
646
+ ].map((extension) => resolve(options.root, `vite.config.${extension}`)).find(existsSync) ?? false,
647
+ logLevel: "error",
648
+ plugins: [{
649
+ name: "pagegraph:loader-root",
650
+ enforce: "pre",
651
+ config(config) {
652
+ config.root ??= options.root;
653
+ }
654
+ }]
655
+ });
656
+ const pluginApi = (plugins, configFile) => {
657
+ const plugin = plugins.find((candidate) => candidate.name === "pagegraph");
658
+ if (plugin?.api === void 0) throw new Error(`${configFile ?? "The Vite config"} does not register pagegraph() from "pagegraph/tanstack-start".`);
659
+ return plugin.api;
660
+ };
661
+ /**
662
+ * Resolve the app's Vite config, evaluate the graph in its `pagegraph`
663
+ * environment, and release it. The command defaults to `serve`; callers can
664
+ * request `build` to match the deployed site configuration.
665
+ */
666
+ async function evaluateAppGraph(options) {
667
+ const command = options.command ?? "serve";
668
+ const config = await resolveConfig({
669
+ ...appConfig(options),
670
+ mode: options.mode,
671
+ environments: cliEnvironmentOverride(command)
672
+ }, command);
673
+ const api = pluginApi(config.plugins, config.configFile);
674
+ const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });
675
+ await environment.init();
676
+ try {
677
+ await environment.pluginContainer.buildStart({});
678
+ const app = await evaluateGraph(environment.runner, await planGraph(config.root, api.options));
679
+ return {
680
+ ...app,
681
+ pagegraph: {
682
+ root: config.root,
683
+ options: api.options,
684
+ facts: app.facts ?? {}
685
+ }
686
+ };
687
+ } finally {
688
+ await environment.close();
689
+ }
690
+ }
691
+ /**
692
+ * A `pagegraph.config.ts` graph loader for a TanStack Start app. It uses the
693
+ * built site's identity by default; pass `command: "serve"` for a dev host.
694
+ *
695
+ * ```ts
696
+ * export default defineSeoConfig({
697
+ * loadGraph: tanstackStartGraph({ root: import.meta.dirname }),
698
+ * });
699
+ * ```
700
+ */
701
+ const tanstackStartGraph = (options) => async (_input, environment) => {
702
+ const { graph, site, pagegraph } = await withCleanStdout(() => evaluateAppGraph({
703
+ ...options,
704
+ command: environment?.command ?? options.command ?? "build",
705
+ mode: environment?.mode ?? options.mode ?? "production"
706
+ }));
707
+ return {
708
+ graph,
709
+ site,
710
+ pagegraph,
711
+ dispose: async () => {}
712
+ };
713
+ };
714
+ //#endregion
715
+ export { evaluateAppGraph, pagegraph, tanstackStartGraph };
716
+
717
+ //# sourceMappingURL=index.js.map