pagegraph 0.14.1 → 0.15.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 +169 -10
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +1 -1
- package/dist/{checks-BS-aDXB_.js → checks-BEhJPNzN.js} +12 -1
- package/dist/checks-BEhJPNzN.js.map +1 -0
- package/dist/{claims-Bch7Lxlo.d.ts → claims-CCf2_Jox.d.ts} +2 -2
- package/dist/{claims-Bv7voeEV.js → claims-CQlzze1K.js} +2 -2
- package/dist/{claims-Bv7voeEV.js.map → claims-CQlzze1K.js.map} +1 -1
- package/dist/claims.d.ts +2 -2
- package/dist/claims.js +1 -1
- package/dist/cli.js +1355 -616
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +7 -3
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/{content-CHd0X4s6.js → content-D5jZprMq.js} +2 -2
- package/dist/{content-CHd0X4s6.js.map → content-D5jZprMq.js.map} +1 -1
- package/dist/{content-DgNr7apS.d.ts → content-DerWgQy9.d.ts} +11 -3
- package/dist/{dist-cFoAzruL.js → dist-Br2oOB2Q.js} +2 -2
- package/dist/{dist-cFoAzruL.js.map → dist-Br2oOB2Q.js.map} +1 -1
- package/dist/{document-CztEu3Vd.d.ts → document-D9P8yY5g.d.ts} +4 -4
- package/dist/{graph-D1PIndsC.d.ts → graph-ChrvU-6X.d.ts} +27 -4
- package/dist/{graph-DMNOLXLu.js → graph-UONEaqIX.js} +43 -5
- package/dist/graph-UONEaqIX.js.map +1 -0
- package/dist/{index-C5w9ripz.d.ts → index-DzjUmbHY.d.ts} +5 -5
- package/dist/index.d.ts +6 -6
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/{page-heads-BfjWQvau.d.ts → page-heads-DDz_2_hq.d.ts} +3 -2
- package/dist/{page-heads-BgbaIjj9.js → page-heads-YL8nyYjF.js} +4 -3
- package/dist/page-heads-YL8nyYjF.js.map +1 -0
- package/dist/{pi-D3mMmYey.js → pi-Bz4BgZdC.js} +184 -154
- package/dist/pi-Bz4BgZdC.js.map +1 -0
- package/dist/{projections-Bk-qOOsR.d.ts → projections-Dqo_ih-J.d.ts} +2 -2
- package/dist/react.d.ts +5 -3
- package/dist/react.js +43 -19
- package/dist/react.js.map +1 -1
- package/dist/{state-BuJP_3Lr.js → state-2ikjo7uI.js} +638 -25
- package/dist/state-2ikjo7uI.js.map +1 -0
- package/dist/tanstack-start/index.d.ts +1 -1
- package/dist/tanstack-start/index.js +6 -4
- package/dist/tanstack-start/index.js.map +1 -1
- package/dist/tanstack-start/react.d.ts +1 -1
- package/dist/tanstack-start/server.d.ts +2 -2
- package/dist/tanstack-start/server.js +1 -1
- package/dist/tanstack-start/server.js.map +1 -1
- package/dist/{vite-graph-loader-DZCwejuJ.d.ts → vite-graph-loader-BtLgkN0V.d.ts} +7 -5
- package/package.json +1 -1
- package/dist/checks-BS-aDXB_.js.map +0 -1
- package/dist/declare-D5o7hwmv.js +0 -9
- package/dist/declare-D5o7hwmv.js.map +0 -1
- package/dist/graph-DMNOLXLu.js.map +0 -1
- package/dist/page-heads-BgbaIjj9.js.map +0 -1
- package/dist/pi-D3mMmYey.js.map +0 -1
- package/dist/state-BuJP_3Lr.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as SiteRuntime, c as AppGraph, i as SeoGraphLoader, l as PagegraphOptions, r as LoadedSeoGraph, u as RobotsPolicy } from "../vite-graph-loader-
|
|
1
|
+
import { a as SiteRuntime, c as AppGraph, i as SeoGraphLoader, l as PagegraphOptions, r as LoadedSeoGraph, u as RobotsPolicy } from "../vite-graph-loader-BtLgkN0V.js";
|
|
2
2
|
import { Plugin } from "vite";
|
|
3
3
|
//#region src/tanstack-start/plugin.d.ts
|
|
4
4
|
/** Start prerenders this private endpoint once; its callback writes public Markdown twins. */
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { t as buildSeoGraph } from "../graph-
|
|
1
|
+
import { t as buildSeoGraph } from "../graph-UONEaqIX.js";
|
|
2
2
|
import { n as graphToJson } from "../wire-B4RQMc4e.js";
|
|
3
|
-
import { a as globToRegExp } from "../checks-
|
|
3
|
+
import { a as globToRegExp } from "../checks-BEhJPNzN.js";
|
|
4
4
|
import { o as hashDocument } from "../inline-BbS93GAT.js";
|
|
5
5
|
import { t as decodeFacts } from "../facts-1OKclPOk.js";
|
|
6
6
|
import { n as documentMarkdown } from "../markdown-C6dRE9zD.js";
|
|
7
7
|
import { i as seoRouteConfig, n as inMemoryWriteFs, r as resolveOptions, t as deriveRouteConfig } from "../route-config-35ZEzkNC.js";
|
|
8
8
|
import { n as withCleanStdout } from "../vite-graph-loader-CsGiYNWE.js";
|
|
9
|
-
import { r as replayClaims } from "../claims-
|
|
9
|
+
import { r as replayClaims } from "../claims-CQlzze1K.js";
|
|
10
10
|
import { t as MARKDOWN_CAPTURE_PATH } from "../markdown-path-lJB9BAVZ.js";
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
|
|
@@ -130,7 +130,9 @@ async function evaluateGraph(runner, plan) {
|
|
|
130
130
|
const collections = plan.collectionsFile === void 0 ? [] : await loadCollections(runner, root, plan.collectionsFile);
|
|
131
131
|
const graph = buildSeoGraph({
|
|
132
132
|
routeTree,
|
|
133
|
-
collections
|
|
133
|
+
collections,
|
|
134
|
+
origin: options.origin,
|
|
135
|
+
ogImage: options.ogImage
|
|
134
136
|
});
|
|
135
137
|
const exclusions = options.routeConfig === void 0 ? [] : deriveRouteConfig(scan.nodes, resolveOptions({
|
|
136
138
|
...options.routeConfig,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["PageDocumentSchema","GRAPH_ENVIRONMENT"],"sources":["../../src/tanstack-start/routes.ts","../../src/tanstack-start/graph.ts","../../src/markdown/schema.ts","../../src/tanstack-start/persist-markdown.ts","../../src/tanstack-start/source.ts","../../src/tanstack-start/plugin.ts","../../src/tanstack-start/load.ts"],"sourcesContent":["/**\n * Route discovery for the graph: the router's own generator parses the routes\n * directory (the same parse `routeTree.gen.ts` comes from), into memory only.\n */\n\nimport { resolve } from \"node:path\";\n\nimport { Generator, getConfig, type RouteNode } from \"@tanstack/router-generator\";\n\nimport { inMemoryWriteFs } from \"../vite/route-config\";\n\nexport interface ScannedRoutes {\n /** `__root.tsx`. */\n readonly root: RouteNode;\n /** Top-level route nodes, each with its `children`. */\n readonly tree: ReadonlyArray<RouteNode>;\n /** Every route node, flat. */\n readonly nodes: ReadonlyArray<RouteNode>;\n}\n\nexport async function scanRoutes(root: string, routesDirectory: string): Promise<ScannedRoutes> {\n const cacheDirectory = resolve(root, \"node_modules/.cache/pagegraph\");\n let scanned: ScannedRoutes | undefined;\n const config = getConfig(\n {\n routesDirectory: resolve(root, routesDirectory),\n generatedRouteTree: resolve(cacheDirectory, \"route-tree.gen.ts\"),\n tmpDir: resolve(cacheDirectory, \"tmp\"),\n disableTypes: true,\n enableRouteTreeFormatting: false,\n disableLogging: true,\n plugins: [\n {\n name: \"pagegraph:scan\",\n onRouteTreeChanged({ routeTree, routeNodes, rootRouteNode }) {\n scanned = { root: rootRouteNode, tree: routeTree, nodes: routeNodes };\n },\n },\n ],\n },\n root,\n );\n const memory = inMemoryWriteFs(config.generatedRouteTree);\n await new Generator({ config, root, fs: memory.fs }).run();\n if (scanned === undefined) {\n throw new Error(`pagegraph found no route tree in ${resolve(root, routesDirectory)}`);\n }\n return scanned;\n}\n","/**\n * Build the graph from the app's own modules, evaluated in the `pagegraph`\n * Vite environment: one module per route file that declares `staticData`, plus\n * the app's collections module. Never the route tree, which reaches every\n * route — including app routes that only resolve inside the server runtime.\n */\n\nimport { relative, resolve } from \"node:path\";\n\nimport type { ClaimsOptions } from \"../decide/families/claims\";\n\nimport type { RouteNode } from \"@tanstack/router-generator\";\n\nimport { globToRegExp } from \"../core/checks\";\nimport { buildSeoGraph, type SeoCollection, type SeoGraph, type SeoRouteNode } from \"../core/graph\";\nimport type { RobotsConfig } from \"../core/projections\";\nimport type { SiteRuntime } from \"../config/vite-graph-loader\";\nimport { deriveRouteConfig, resolveOptions, type SeoRouteConfigOptions } from \"../vite/route-config\";\nimport { scanRoutes, type ScannedRoutes } from \"./routes\";\nimport { decodeFacts, type Facts } from \"../markdown/facts\";\n\n/** The robots.txt policy a build serves; `origin` and indexability come from {@link SiteRuntime}. */\nexport type RobotsPolicy = Pick<RobotsConfig, \"disallow\" | \"contentSignal\" | \"directives\">;\n\n/** The graph and the site runtime one evaluation produces. */\nexport interface AppGraph {\n readonly graph: SeoGraph;\n readonly site: SiteRuntime;\n readonly markdown: { readonly origin: string } | null;\n readonly facts: Facts | undefined;\n}\n\nexport type { SiteRuntime };\n\nexport interface PagegraphOptions {\n /** This build's canonical origin, no trailing slash, e.g. `https://example.com`. */\n readonly origin: string;\n /**\n * Whether this build's host may be indexed. A non-indexable build serves a\n * disallow-all robots.txt with no Sitemap line. Defaults to `true`.\n */\n readonly indexable?: boolean | undefined;\n readonly robots?:\n | {\n /** Path prefixes disallowed on an indexable host, beyond the route-config exclusions. */\n readonly disallow?: ReadonlyArray<string> | undefined;\n /** `Content-Signal` value for an indexable host, e.g. `\"search=yes, ai-input=yes, ai-train=yes\"`. */\n readonly contentSignal?: string | undefined;\n /** Extra lines in the `User-agent: *` group on an indexable host. */\n readonly directives?: ReadonlyArray<string> | undefined;\n }\n | undefined;\n /**\n * Root-relative module whose `collections` export is a `SeoCollection[]`, or a\n * function returning one (sync or async). It runs in the app's Vite pipeline,\n * so content virtual modules (Fumadocs, MDX) and path aliases resolve.\n */\n readonly collections?: string | undefined;\n /** Canonical origin for rendered documents; serverEntry is relative to the Vite root. */\n readonly markdown?: { readonly origin: string; readonly serverEntry?: string | undefined } | undefined;\n /** Committed claims answers gate the rendered documents and graph heads at build time. */\n readonly claims?: ClaimsOptions | undefined;\n /** Root-relative module exporting the code-owned document facts. */\n readonly facts?: string | undefined;\n /**\n * Route files (globs relative to the routes directory) never evaluated for the\n * graph: app surfaces that declare `staticData` but import server-runtime-only\n * modules. Their paths stay out of the graph.\n */\n readonly exclude?: ReadonlyArray<string> | undefined;\n /** Relative to the Vite root. Defaults to `src/routes`. */\n readonly routesDirectory?: string | undefined;\n /**\n * Generate the route-config module and enforce per-group SEO coverage (the\n * `seoRouteConfig` gate). Its robots exclusions join `robots.disallow`.\n */\n readonly routeConfig?: Omit<SeoRouteConfigOptions, \"routesDirectory\"> | undefined;\n}\n\n/** Imports a module through the `pagegraph` environment's module runner. */\nexport interface GraphModuleRunner {\n import(url: string): Promise<Record<string, unknown>>;\n}\n\n\nconst GRAPH_ROUTE_TYPES = new Set([\"__root\", \"static\", \"layout\", \"pathless_layout\"]);\n\nconst messageOf = (cause: unknown): string => (cause instanceof Error ? cause.message : String(cause));\n\nconst routeStaticData = (module: Record<string, unknown>, file: string): SeoRouteNode[\"options\"][\"staticData\"] => {\n const route = module[\"Route\"] as { options?: SeoRouteNode[\"options\"] } | undefined;\n if (route?.options === undefined) throw new Error(`${file} has no \\`Route\\` export`);\n const data = route.options.staticData;\n return data === undefined ? undefined : { seo: data.seo, markdown: data.markdown, llms: data.llms };\n};\n\nconst loadCollections = async (\n runner: GraphModuleRunner,\n root: string,\n fullPath: string,\n): Promise<ReadonlyArray<SeoCollection>> => {\n const path = relative(root, fullPath);\n const module = await runner.import(fullPath).catch((cause: unknown) => {\n throw new Error(`pagegraph could not evaluate ${path}: ${messageOf(cause)}`, { cause });\n });\n const exported = module[\"collections\"];\n if (exported === undefined) throw new Error(`${path} has no \\`collections\\` export`);\n const collections: unknown = typeof exported === \"function\" ? await (exported as () => unknown)() : exported;\n if (!Array.isArray(collections)) throw new Error(`${path} \\`collections\\` must be an array or return one`);\n return collections as ReadonlyArray<SeoCollection>;\n};\n\n/** What one evaluation loads: the scanned route tree and the modules that feed the graph. */\nexport interface GraphPlan {\n readonly root: string;\n readonly options: PagegraphOptions;\n readonly scan: ScannedRoutes;\n /** Absolute paths of the route files that declare `staticData`, plus `__root`. */\n readonly routeFiles: ReadonlyArray<string>;\n /** Absolute path of the collections module, when configured. */\n readonly collectionsFile: string | undefined;\n readonly factsFile: string | undefined;\n}\n\n/** Scan the routes directory and pick the modules an evaluation loads. */\nexport async function planGraph(root: string, options: PagegraphOptions): Promise<GraphPlan> {\n const scan = await scanRoutes(root, options.routesDirectory ?? \"src/routes\");\n const excluded = (options.exclude ?? []).map(globToRegExp);\n const participates = (node: RouteNode): boolean =>\n GRAPH_ROUTE_TYPES.has(node._fsRouteType) &&\n (node._fsRouteType === \"__root\" || node.createFileRouteProps?.has(\"staticData\") === true) &&\n !excluded.some((pattern) => pattern.test(node.filePath));\n return {\n root,\n options,\n scan,\n routeFiles: [scan.root, ...scan.nodes].filter(participates).map((node) => node.fullPath),\n collectionsFile: options.collections === undefined ? undefined : resolve(root, options.collections),\n factsFile: options.facts === undefined ? undefined : resolve(root, options.facts),\n };\n}\n\n/**\n * A route node's path relative to its parent, as the router matches it: route\n * groups, pathless layouts, and trailing underscores contribute no segment.\n */\nconst publicSegment = (node: RouteNode): string | undefined =>\n node.isNonPath === true || node.cleanedPath === undefined || node.cleanedPath === \"\" ? undefined : node.cleanedPath;\n\n/** Evaluate a plan's modules through `runner`, then build the graph and the site runtime. */\nexport async function evaluateGraph(runner: GraphModuleRunner, plan: GraphPlan): Promise<AppGraph> {\n const { root, options, scan } = plan;\n const staticData = new Map(\n await Promise.all(\n plan.routeFiles.map(async (fullPath) => {\n const file = relative(root, fullPath);\n const routeFile = relative(resolve(root, options.routesDirectory ?? \"src/routes\"), fullPath);\n const module = await runner.import(fullPath).catch((cause: unknown) => {\n throw new Error(\n `pagegraph could not evaluate ${file} in the graph environment: ${messageOf(cause)}\\n` +\n `If it is an app surface rather than a public page, add \"${routeFile}\" to pagegraph({ exclude }).`,\n { cause },\n );\n });\n return [fullPath, routeStaticData(module, file)] as const;\n }),\n ),\n );\n\n const walkable = (node: RouteNode): SeoRouteNode => ({\n options: { path: publicSegment(node), staticData: staticData.get(node.fullPath) },\n children: (node.children ?? []).map(walkable),\n });\n const routeTree: SeoRouteNode = {\n options: { staticData: staticData.get(scan.root.fullPath) },\n children: scan.tree.map(walkable),\n };\n\n const collections =\n plan.collectionsFile === undefined ? [] : await loadCollections(runner, root, plan.collectionsFile);\n const graph = buildSeoGraph({ routeTree, collections });\n\n const exclusions =\n options.routeConfig === undefined\n ? []\n : deriveRouteConfig(\n scan.nodes,\n resolveOptions({ ...options.routeConfig, routesDirectory: options.routesDirectory }),\n ).robotsExclusions;\n const site: SiteRuntime = {\n origin: options.origin,\n indexable: options.indexable ?? true,\n robots: {\n disallow: [...new Set([...exclusions, ...(options.robots?.disallow ?? [])])].sort(),\n contentSignal: options.robots?.contentSignal,\n directives: options.robots?.directives,\n },\n };\n let facts: Facts | undefined;\n if (plan.factsFile !== undefined) {\n const path = relative(root, plan.factsFile);\n try {\n const module = await runner.import(plan.factsFile);\n if (module[\"facts\"] === undefined) throw new Error(`${path} has no \\`facts\\` export`);\n facts = decodeFacts(module[\"facts\"]);\n } catch (cause) {\n throw new Error(`pagegraph could not evaluate ${path}: ${messageOf(cause)}`, { cause });\n }\n }\n return { graph, site, markdown: options.markdown === undefined ? null : { origin: options.markdown.origin }, facts };\n}\n","import * as Schema from \"effect/Schema\";\n\nimport type { InlineNode as InlineNodeType, PageDocument as PageDocumentType } from \"./document\";\n\nconst InlineNode: Schema.Codec<InlineNodeType> = Schema.suspend(() => Schema.Union([\n Schema.String,\n Schema.Struct({ fact: Schema.String, text: Schema.String }),\n Schema.Struct({\n tag: Schema.Literals([\"em\", \"strong\", \"a\", \"code\", \"br\"]),\n href: Schema.optionalKey(Schema.String),\n children: Schema.Array(InlineNode),\n }),\n]));\nconst Message = Schema.Struct({\n hash: Schema.String, tree: Schema.Array(InlineNode), text: Schema.String,\n source: Schema.String, facts: Schema.Array(Schema.String),\n audience: Schema.Literals([\"all\", \"agents\", \"humans\"]),\n});\nconst BodyEntry = Schema.Union([\n Schema.Struct({ type: Schema.Literal(\"message\"), message: Message }),\n Schema.Struct({ type: Schema.Literal(\"visual\"), text: Schema.String, source: Schema.String,\n audience: Schema.Literals([\"all\", \"agents\", \"humans\"]) }),\n]);\nexport const PageDocument: Schema.Codec<PageDocumentType> = Schema.Struct({\n path: Schema.String, hash: Schema.String, title: Schema.String, description: Schema.String,\n sections: Schema.Array(Schema.Struct({\n claimsMarkdown: Schema.optionalKey(Schema.String), evidence: Schema.optionalKey(Schema.Boolean),\n kind: Schema.String, id: Schema.String, title: Schema.optionalKey(Message),\n language: Schema.optionalKey(Schema.String), body: Schema.Array(BodyEntry),\n items: Schema.Array(Schema.Struct({\n title: Schema.optionalKey(Message), href: Schema.optionalKey(Schema.String),\n body: Schema.Array(BodyEntry), cells: Schema.Array(Message), source: Schema.String,\n audience: Schema.Literals([\"all\", \"agents\", \"humans\"]),\n })), source: Schema.String, audience: Schema.Literals([\"all\", \"agents\", \"humans\"]),\n })), messages: Schema.Array(Message), facts: Schema.Array(Schema.String),\n});\nexport const CaptureBundle = Schema.Struct({\n origin: Schema.String,\n documents: Schema.Array(PageDocument),\n heads: Schema.Array(Schema.Struct({ path: Schema.String, title: Schema.String, description: Schema.String })),\n});\n","import { mkdir, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { replayClaims, type ClaimsOptions } from \"../claims\";\nimport type { Facts } from \"../markdown/facts\";\n\nimport type { PageDocument } from \"../markdown/document\";\nimport { hashDocument } from \"../markdown/document\";\nimport { CaptureBundle, PageDocument as PageDocumentSchema } from \"../markdown/schema\";\nimport { documentMarkdown } from \"../markdown/markdown\";\n\nimport { MARKDOWN_CAPTURE_PATH } from \"./markdown-path\";\n\n/** File names must remain inside the package's capture and the client's output trees. */\nfunction documentName(path: string): string {\n if (!path.startsWith(\"/\") || path.startsWith(\"//\") || /[?#\\\\$\\u0000-\\u001f\\u007f]/.test(path) ||\n (path !== \"/\" && path.endsWith(\"/\")) || path.slice(1).split(\"/\").some((part) => part === \".\" || part === \"..\" || part === \"*\" || (part === \"\" && path !== \"/\"))) {\n throw new Error(`Invalid captured document path \"${path}\"`);\n }\n return path === \"/\" ? \"index\" : path.slice(1);\n}\n\nfunction verifyDocument(document: PageDocument): string {\n const name = documentName(document.path);\n const { hash, ...content } = document;\n if (!/^[0-9a-f]{64}$/.test(hash) || hashDocument(content) !== hash)\n throw new Error(`Invalid captured document hash for \"${document.path}\"`);\n return name;\n}\n\n/** Only valid captures at their derived paths belong to pagegraph; preserve unrelated files. */\nasync function pruneDocuments(directory: string, paths: ReadonlySet<string>): Promise<void> {\n const visit = async (folder: string): Promise<void> => {\n const entries = await readdir(folder, { withFileTypes: true });\n for (const entry of entries) {\n const file = resolve(folder, entry.name);\n if (entry.isDirectory()) { await visit(file); continue; }\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue;\n const contents = await readFile(file, \"utf8\");\n let document: PageDocument;\n let name: string;\n try {\n document = Schema.decodeUnknownSync(Schema.fromJsonString(PageDocumentSchema))(contents, { onExcessProperty: \"error\" });\n name = verifyDocument(document);\n } catch { continue; }\n if (file === resolve(directory, `${name}.json`) && !paths.has(document.path)) await unlink(file);\n }\n };\n await visit(directory);\n}\n\n/** Documents, twins, and graph heads are persisted before prerender onSuccess returns. */\nexport async function persistMarkdownCapture(\n html: string, root: string, clientOutDir: string, claims?: ClaimsOptions, facts: Facts = {},\n): Promise<void> {\n const bundle = Schema.decodeUnknownSync(Schema.fromJsonString(CaptureBundle))(html, { onExcessProperty: \"error\" });\n const origin = new URL(bundle.origin);\n if ((origin.protocol !== \"https:\" && origin.protocol !== \"http:\") || origin.origin !== bundle.origin)\n throw new Error(`Invalid captured markdown origin \"${bundle.origin}\"`);\n const active = new Set<string>();\n const destinations = new Set<string>();\n const names = bundle.documents.map((document) => {\n if (active.has(document.path)) throw new Error(`Duplicate captured document path \"${document.path}\"`);\n active.add(document.path);\n const name = verifyDocument(document);\n if (destinations.has(name)) throw new Error(`Captured document paths share the output name \"${name}\"`);\n destinations.add(name);\n return name;\n });\n await unlink(resolve(clientOutDir, MARKDOWN_CAPTURE_PATH.slice(1)));\n const directory = resolve(root, \".pagegraph/documents\");\n await mkdir(directory, { recursive: true });\n for (const [index, document] of bundle.documents.entries()) {\n const name = names[index];\n const destination = resolve(directory, `${name}.json`);\n const twin = resolve(clientOutDir, `${name}.md`);\n await mkdir(dirname(destination), { recursive: true });\n await writeFile(destination, JSON.stringify(document));\n await mkdir(dirname(twin), { recursive: true });\n await writeFile(twin, documentMarkdown(document, bundle.origin));\n }\n await pruneDocuments(directory, active);\n await writeFile(resolve(root, \".pagegraph/heads.json\"), `${JSON.stringify(bundle.heads)}\\n`);\n if (claims !== undefined)\n await Effect.runPromise(replayClaims(root, bundle.documents, bundle.heads, facts, claims));\n}\n","import { relative, resolve, sep } from \"node:path\";\n\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { parseAst } from \"vite\";\n\ntype Program = ReturnType<typeof parseAst>;\ntype Expression = Extract<Program[\"body\"][number], { type: \"ExpressionStatement\" }>[\"expression\"];\ntype JSXOpeningElement = Extract<Expression, { type: \"JSXElement\" }>[\"openingElement\"];\n\nclass DocumentSourceError extends Schema.TaggedError<DocumentSourceError>()(\"DocumentSourceError\", {\n message: Schema.String,\n}) {}\n\nconst SOURCE_COMPONENTS = new Set([\"T\", \"Title\", \"Section\", \"Visual\", \"ForAgents\", \"ForHumans\"]);\n\nconst parse = Effect.fn(\"DocumentSource.parse\")(function* (code: string, id: string) {\n return yield* Effect.try({\n try: () => parseAst(code, { lang: id.endsWith(\".tsx\") ? \"tsx\" : id.endsWith(\".ts\") ? \"ts\" : id.endsWith(\".jsx\") ? \"jsx\" : \"js\" }, id),\n catch: (cause) => new DocumentSourceError({\n message: `Cannot parse document source ${id}: ${cause instanceof Error ? cause.message : String(cause)}`,\n }),\n });\n});\n\nfunction componentNames(program: Program): {\n named: Set<string>;\n sections: Set<string>;\n namespaces: Set<string>;\n} {\n const named = new Set<string>();\n const sections = new Set<string>();\n const namespaces = new Set<string>();\n for (const node of program.body) {\n if (\n node.type !== \"ImportDeclaration\" ||\n node.source.value !== \"pagegraph/react\" ||\n node.importKind === \"type\"\n )\n continue;\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportSpecifier\") {\n if (specifier.importKind === \"type\") continue;\n const imported =\n specifier.imported.type === \"Identifier\"\n ? specifier.imported.name\n : specifier.imported.value;\n if (SOURCE_COMPONENTS.has(imported)) named.add(specifier.local.name);\n if (imported === \"Section\") sections.add(specifier.local.name);\n } else if (specifier.type === \"ImportNamespaceSpecifier\") {\n namespaces.add(specifier.local.name);\n }\n }\n }\n return { named, sections, namespaces };\n}\n\nfunction memberParts(name: JSXOpeningElement[\"name\"]): Array<string> {\n if (name.type === \"JSXIdentifier\") return [name.name];\n if (name.type !== \"JSXMemberExpression\") return [];\n return [...memberParts(name.object), name.property.name];\n}\n\nfunction isSourceComponent(\n name: JSXOpeningElement[\"name\"],\n names: ReturnType<typeof componentNames>,\n): boolean {\n const parts = memberParts(name);\n const [base, ...members] = parts;\n if (base === undefined) return false;\n if (members.length === 0) return names.named.has(base);\n if (names.namespaces.has(base))\n return (\n (members.length === 1 && members[0] !== undefined && SOURCE_COMPONENTS.has(members[0])) ||\n (members.length === 2 && members[0] === \"Section\" && members[1] === \"Item\")\n );\n return names.sections.has(base) && members.length === 1 && members[0] === \"Item\";\n}\n\nfunction walk(value: unknown, visit: (node: JSXOpeningElement) => void): void {\n if (Array.isArray(value)) {\n for (const child of value) walk(child, visit);\n } else if (typeof value === \"object\" && value !== null) {\n if (isOpeningElement(value)) visit(value);\n for (const child of Object.values(value)) walk(child, visit);\n }\n}\n\nfunction isOpeningElement(node: object): node is JSXOpeningElement {\n return \"type\" in node && node.type === \"JSXOpeningElement\";\n}\n\nexport function transformDocumentSource(\n code: string,\n id: string,\n root: string,\n): { code: string } | undefined {\n // oxlint-disable-next-line eslint/no-restricted-properties -- Vite's document transform exposes a synchronous source API.\n const program = Effect.runSync(parse(code, id));\n const names = componentNames(program);\n const edits: Array<{ start: number; end: number; value: string }> = [];\n const file = relative(resolve(root), resolve(id)).split(sep).join(\"/\");\n\n walk(program, (node) => {\n if (!isSourceComponent(node.name, names)) return;\n if (\n node.attributes.some(\n (attribute) =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.type === \"JSXIdentifier\" &&\n attribute.name.name === \"source\",\n )\n )\n return;\n const line = code.slice(0, node.start).split(\"\\n\").length;\n // Generated defaults precede attributes so authored and spread props retain precedence.\n edits.push({\n start: node.name.end,\n end: node.name.end,\n value: ` source=${JSON.stringify(`${file}:${line}`)}`,\n });\n });\n\n if (edits.length === 0) return undefined;\n edits.sort((a, b) => b.start - a.start);\n let transformed = code;\n for (const edit of edits)\n transformed = transformed.slice(0, edit.start) + edit.value + transformed.slice(edit.end);\n return { code: transformed };\n}\n","/**\n * The `pagegraph()` Vite plugin for TanStack Start.\n *\n * The graph is built once per build (and on change in dev) inside the app's own\n * Vite pipeline, then shipped to the server runtime as `virtual:pagegraph/runtime`.\n * The Worker never rebuilds it and never imports build code.\n *\n * Route modules and content collections are evaluated in a dedicated Node\n * environment, `pagegraph`, that shares the app's plugins, aliases, and\n * `define`s. Two settings make that safe:\n * - its `resolve.noExternal` is cleared, so TanStack Start's packages load from\n * node_modules, where their virtual router entry is an inert stub. Bundled (as\n * the app's server bundles them), they would pull in the whole route tree.\n * - only route files that declare `staticData` are evaluated, one by one, never\n * `routeTree.gen.ts`.\n *\n * Dev imports those modules through the environment's module runner and\n * re-evaluates after a change reaches it. A build builds the environment first\n * (a `buildApp` hook that runs before the framework's) with the build's own\n * config and plugins, then imports the output. The CLI and `loadPageHeads` use\n * {@link evaluateAppGraph}, which runs the dev path without a server.\n */\n\nimport { join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport {\n createRunnableDevEnvironment,\n type BuildEnvironment,\n type Plugin,\n type RunnableDevEnvironment,\n type ViteBuilder,\n type ViteDevServer,\n} from \"vite\";\n\nimport { graphToJson } from \"../core/wire\";\nimport { seoRouteConfig } from \"../vite/route-config\";\nimport { evaluateGraph, planGraph, type AppGraph, type GraphModuleRunner, type GraphPlan, type PagegraphOptions } from \"./graph\";\nimport { MARKDOWN_CAPTURE_PATH } from \"./markdown-path\";\nimport { persistMarkdownCapture } from \"./persist-markdown\";\nimport { transformDocumentSource } from \"./source\";\n\nexport const GRAPH_ENVIRONMENT = \"pagegraph\";\nexport const PLUGIN_NAME = \"pagegraph\";\nconst RUNTIME_ID = \"virtual:pagegraph/runtime\";\nconst RESOLVED_RUNTIME_ID = `\\0${RUNTIME_ID}`;\nconst ENTRY_ID = \"virtual:pagegraph/entry\";\nconst RESOLVED_ENTRY_ID = `\\0${ENTRY_ID}`;\n/** The runtime entry imports the virtual module, so dep pre-bundling must leave it to this plugin. */\nconst RUNTIME_ENTRIES = [\"pagegraph/tanstack-start/server\", \"pagegraph/tanstack-start/markdown\", \"pagegraph/tanstack-start/react\"];\n/** Where a build writes the graph environment's output, relative to the Vite root. */\nconst BUILD_DIRECTORY = \"node_modules/.cache/pagegraph/build\";\n/**\n * Runtime-provided modules (`cloudflare:workers`, `bun:sqlite`) that no plugin\n * resolves stay external in the graph environment, so an unexcluded route that\n * imports one fails at evaluation with its file name rather than as an\n * unresolved import. Plugin-owned schemes (`fumadocs-mdx:`) resolve first.\n */\nconst RUNTIME_SCHEME = /^(?!node:|data:|file:|virtual:)[a-z][a-z0-9+.-]*:/;\n\nconst runtimeModulesPlugin: Plugin = {\n name: \"pagegraph:runtime-modules\",\n enforce: \"post\",\n applyToEnvironment: (environment) => environment.name === GRAPH_ENVIRONMENT,\n resolveId: {\n filter: { id: RUNTIME_SCHEME },\n handler: (id) => ({ id, external: true }),\n },\n};\n\n/** What the plugin exposes to {@link evaluateAppGraph} through `plugin.api`. */\nexport interface PagegraphPluginApi {\n readonly options: PagegraphOptions;\n}\n\nconst runtimeModule = ({ graph, site, markdown, facts }: AppGraph): string =>\n `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`;\n\n/** The graph environment's build entry: a loader per module the plan evaluates. */\nconst entryModule = (plan: GraphPlan): string => {\n const files = [...plan.routeFiles, ...(plan.collectionsFile === undefined ? [] : [plan.collectionsFile]), ...(plan.factsFile === undefined ? [] : [plan.factsFile])];\n const loaders = files.map((file) => ` ${JSON.stringify(file)}: () => import(${JSON.stringify(file)}),`);\n return `export const modules = {\\n${loaders.join(\"\\n\")}\\n};\\n`;\n};\n\n/** Build the graph environment with the build's config, import its output, and evaluate. */\nconst buildGraph = async (builder: ViteBuilder, environment: BuildEnvironment, plan: GraphPlan): Promise<AppGraph> => {\n await builder.build(environment);\n const entry = join(resolve(environment.config.root, environment.config.build.outDir), \"entry.mjs\");\n const { modules } = (await import(pathToFileURL(entry).href)) as {\n modules: Record<string, () => Promise<Record<string, unknown>>>;\n };\n const runner: GraphModuleRunner = {\n import: (file) => {\n const load = modules[file];\n if (load === undefined) throw new Error(`${file} is not in the graph build`);\n return load();\n },\n };\n return evaluateGraph(runner, plan);\n};\n\n/** Start prerenders this private endpoint once; its callback writes public Markdown twins. */\nexport interface MarkdownPrerenderPage {\n readonly path: string;\n readonly sitemap: { readonly exclude: true };\n readonly prerender: { readonly onSuccess: (result: { html: string }) => Promise<void> };\n}\nexport type PagegraphPlugins = Array<Plugin> & { readonly prerenderPages: Array<MarkdownPrerenderPage> };\n\nexport function pagegraph(options: PagegraphOptions): PagegraphPlugins {\n if (options.claims !== undefined && options.markdown === undefined)\n throw new Error(\"Claims require pagegraph({ markdown: { origin }, claims: { rules, model } }) so the build captures documents.\");\n const api: PagegraphPluginApi = { options };\n let root = process.cwd();\n let clientOutDir = resolve(root, \"dist/client\");\n let server: ViteDevServer | undefined;\n let devEnvironment: RunnableDevEnvironment | undefined;\n /** The plan the graph environment's build entry is generated from. */\n let buildPlan: GraphPlan | undefined;\n let current: Promise<AppGraph> | undefined;\n /** Bumped by every change that reaches the graph; an evaluation that spans one is discarded. */\n let generation = 0;\n\n const evaluateInDev = async (environment: RunnableDevEnvironment): Promise<AppGraph> => {\n for (;;) {\n const started = generation;\n // Re-evaluate from scratch: content and route modules may have changed.\n environment.runner.clearCache();\n const result = await evaluateGraph(environment.runner, await planGraph(root, options));\n if (started === generation) return result;\n }\n };\n\n const graphPlugin: Plugin & { api: PagegraphPluginApi } = {\n name: PLUGIN_NAME,\n api,\n // One instance across a build's environments: the graph built in buildApp\n // is the one every environment's runtime module serves.\n sharedDuringBuild: true,\n config() {\n return {\n environments: {\n [GRAPH_ENVIRONMENT]: {\n consumer: \"server\",\n dev: {\n createEnvironment: (name, environmentConfig) => createRunnableDevEnvironment(name, environmentConfig),\n },\n build: {\n outDir: BUILD_DIRECTORY,\n emptyOutDir: true,\n copyPublicDir: false,\n minify: false,\n rolldownOptions: {\n input: { entry: ENTRY_ID },\n output: { entryFileNames: \"[name].mjs\", chunkFileNames: \"chunks/[name]-[hash].mjs\" },\n },\n },\n },\n },\n };\n },\n configEnvironment(name, environmentConfig) {\n if (name === GRAPH_ENVIRONMENT) {\n environmentConfig.resolve ??= {};\n environmentConfig.resolve.noExternal = [];\n return;\n }\n environmentConfig.optimizeDeps ??= {};\n environmentConfig.optimizeDeps.exclude = [...(environmentConfig.optimizeDeps.exclude ?? []), ...RUNTIME_ENTRIES];\n },\n configResolved(resolved) {\n root = resolved.root;\n clientOutDir = resolve(root, resolved.environments.client?.build.outDir ?? \"dist/client\");\n },\n configureServer: {\n // Cloudflare inspects SSR exports in its server hook, which can import the graph runtime.\n order: \"pre\",\n handler(devServer) {\n server = devServer;\n devEnvironment = devServer.environments[GRAPH_ENVIRONMENT] as RunnableDevEnvironment | undefined;\n },\n },\n // Runs after Vite invalidates the changed file in each environment, so the\n // next evaluation reads fresh modules.\n hotUpdate({ file, modules }) {\n if (this.environment.name !== GRAPH_ENVIRONMENT) return;\n const routesDirectory = resolve(root, options.routesDirectory ?? \"src/routes\");\n if (modules.length === 0 && !file.startsWith(`${routesDirectory}/`)) return;\n generation += 1;\n current = undefined;\n for (const other of Object.values(server?.environments ?? {})) {\n if (other.name === GRAPH_ENVIRONMENT) continue;\n const runtime = other.moduleGraph.getModuleById(RESOLVED_RUNTIME_ID);\n if (runtime !== undefined) void other.reloadModule(runtime);\n }\n },\n buildApp: {\n order: \"pre\",\n async handler(builder) {\n const environment = builder.environments[GRAPH_ENVIRONMENT];\n if (environment === undefined) return;\n buildPlan = await planGraph(environment.config.root, options);\n const built = buildGraph(builder, environment, buildPlan);\n current = built;\n await built;\n // Vite builds every environment when a build's app builder builds none;\n // the graph environment must not count, or a plain app would skip the rest.\n environment.isBuilt = false;\n },\n },\n resolveId: {\n filter: { id: /^virtual:pagegraph\\/(runtime|entry)$/ },\n handler: (id) => `\\0${id}`,\n },\n load: {\n filter: { id: /^\\0virtual:pagegraph\\/(runtime|entry)$/ },\n async handler(id) {\n if (id === RESOLVED_ENTRY_ID) {\n if (buildPlan === undefined) throw new Error(`[${PLUGIN_NAME}] ${ENTRY_ID} is only built by the pagegraph buildApp hook`);\n return entryModule(buildPlan);\n }\n // A page evaluated for the graph may import the runtime; it never serves from it.\n if (this.environment.name === GRAPH_ENVIRONMENT) return \"export const graph = null;\\nexport const site = null;\\nexport const markdown = null;\\nexport const facts = undefined;\\n\";\n if (current === undefined) {\n if (devEnvironment === undefined) {\n throw new Error(\n `[${PLUGIN_NAME}] the graph was not built before ${this.environment.name} imported it; build with Vite's app builder (\\`vite build\\`).`,\n );\n }\n current = evaluateInDev(devEnvironment);\n }\n return runtimeModule(await current);\n },\n },\n };\n\n const markdownPlugin: Plugin = {\n name: \"pagegraph:markdown\",\n enforce: \"pre\",\n applyToEnvironment: (environment) => environment.name !== GRAPH_ENVIRONMENT,\n resolveId: {\n filter: { id: /^virtual:pagegraph\\/prerender-server$/ },\n handler: (id) => `\\0${id}`,\n },\n load: {\n filter: { id: /^\\0virtual:pagegraph\\/prerender-server$/ },\n handler: () => `export { default } from ${JSON.stringify(resolve(root, options.markdown?.serverEntry ?? \"dist/server/index.js\"))};`,\n },\n transform: {\n filter: { id: /\\.[jt]sx?(\\?|$)/, code: /pagegraph\\/react/ },\n handler(code, id) {\n const file = id.split(\"?\")[0];\n if (file === undefined || file.includes(\"/node_modules/\") || file.endsWith(\".gen.ts\") || !file.startsWith(`${root}/`)) return;\n return transformDocumentSource(code, file, root);\n },\n },\n configurePreviewServer(previewServer) {\n // Start reports status only; retain the route/capture diagnostic across the Worker boundary.\n previewServer.middlewares.use((_request, response, next) => {\n response.on(\"finish\", () => {\n const error = response.getHeader(\"x-pagegraph-error\");\n if (typeof error === \"string\") previewServer.config.logger.error(decodeURIComponent(error));\n });\n next();\n });\n },\n };\n\n return Object.assign([\n graphPlugin,\n markdownPlugin,\n runtimeModulesPlugin,\n ...(options.routeConfig === undefined\n ? []\n : [seoRouteConfig({ ...options.routeConfig, routesDirectory: options.routesDirectory })]),\n ], {\n prerenderPages: options.markdown === undefined ? [] : [{\n path: MARKDOWN_CAPTURE_PATH,\n sitemap: { exclude: true as const },\n prerender: { onSuccess: async ({ html }: { html: string }) => {\n if (current === undefined) throw new Error(\"The pagegraph graph must be evaluated before Markdown persistence.\");\n const app = await current;\n await persistMarkdownCapture(html, root, clientOutDir, options.claims, app.facts);\n } },\n }],\n });\n}\n","/**\n * Evaluate an app's graph outside a running dev server — for a build, the\n * CLI (`pagegraph.config.ts`), and build-time head loading — through the same\n * `pagegraph` environment the dev server uses.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { decodeFacts, type Facts } from \"../markdown/facts\";\n\nimport { createRunnableDevEnvironment, resolveConfig, type Plugin, type InlineConfig } from \"vite\";\n\nimport type { LoadedSeoGraph, SeoGraphLoader } from \"../config/vite-graph-loader\";\nimport { withCleanStdout } from \"../config/vite-graph-loader\";\nimport { evaluateGraph, planGraph, type AppGraph } from \"./graph\";\nimport type { PagegraphPluginApi } from \"./plugin\";\n\nconst GRAPH_ENVIRONMENT = \"pagegraph\";\n\nconst cliEnvironmentOverride = (command: \"serve\" | \"build\") =>\n command === \"build\" ? { [GRAPH_ENVIRONMENT]: { isBundled: false } } : undefined;\n\nexport interface EvaluateAppGraphOptions {\n /** Directory used to find the Vite config and as its default root. */\n readonly root: string;\n /** Vite command to evaluate. Graph evaluation defaults to `serve`. */\n readonly command?: \"serve\" | \"build\" | undefined;\n /** The app's Vite config file. Defaults to Vite's lookup from `root`. */\n readonly configFile?: string | undefined;\n /**\n * Vite mode the config is evaluated in. Origins and other build constants come\n * from it, so the CLI defaults to `production` to describe the deployed site.\n */\n readonly mode?: string | undefined;\n}\n\n/** Locate the config without overriding its authored root with an inline root. */\nconst appConfig = (options: EvaluateAppGraphOptions): InlineConfig => ({\n configFile: options.configFile ?? [\"js\", \"mjs\", \"ts\", \"cjs\", \"mts\", \"cts\"]\n .map((extension) => resolve(options.root, `vite.config.${extension}`)).find(existsSync) ?? false,\n logLevel: \"error\",\n plugins: [{\n name: \"pagegraph:loader-root\",\n enforce: \"pre\",\n config(config) { config.root ??= options.root; },\n }],\n});\n\nconst pluginApi = (plugins: ReadonlyArray<Plugin>, configFile: string | undefined): PagegraphPluginApi => {\n const plugin = plugins.find((candidate) => candidate.name === \"pagegraph\") as (Plugin & { api?: PagegraphPluginApi }) | undefined;\n if (plugin?.api === undefined) {\n throw new Error(`${configFile ?? \"The Vite config\"} does not register pagegraph() from \"pagegraph/tanstack-start\".`);\n }\n return plugin.api;\n};\n\n/** Resolve settings, artifact root and facts together without route discovery. */\nexport async function loadPagegraphSettings(options: EvaluateAppGraphOptions): Promise<NonNullable<LoadedSeoGraph[\"pagegraph\"]>> {\n const command = options.command ?? \"build\";\n return withCleanStdout(async () => {\n const config = await resolveConfig(\n {\n ...appConfig(options),\n mode: options.mode ?? \"production\",\n environments: cliEnvironmentOverride(command),\n },\n command,\n );\n const settings = pluginApi(config.plugins, config.configFile).options;\n let facts: Facts = {};\n if (settings.facts !== undefined) {\n const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });\n await environment.init();\n try {\n await environment.pluginContainer.buildStart({});\n const module = await environment.runner.import(resolve(config.root, settings.facts));\n facts = decodeFacts(module[\"facts\"]);\n } finally { await environment.close(); }\n }\n return { root: config.root, options: settings, facts };\n });\n}\n\n/**\n * Resolve the app's Vite config, evaluate the graph in its `pagegraph`\n * environment, and release it. The command defaults to `serve`; callers can\n * request `build` to match the deployed site configuration.\n */\nexport async function evaluateAppGraph(options: EvaluateAppGraphOptions): Promise<AppGraph & { readonly pagegraph: NonNullable<LoadedSeoGraph[\"pagegraph\"]> }> {\n const command = options.command ?? \"serve\";\n const config = await resolveConfig(\n {\n ...appConfig(options),\n mode: options.mode,\n environments: cliEnvironmentOverride(command),\n },\n command,\n );\n const api = pluginApi(config.plugins, config.configFile);\n const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });\n await environment.init();\n try {\n // Content plugins (Fumadocs, MDX) generate their virtual sources at buildStart.\n await environment.pluginContainer.buildStart({});\n const app = await evaluateGraph(environment.runner, await planGraph(config.root, api.options));\n return { ...app, pagegraph: { root: config.root, options: api.options, facts: app.facts ?? {} } };\n } finally {\n await environment.close();\n }\n}\n\n/**\n * A `pagegraph.config.ts` graph loader for a TanStack Start app. It uses the\n * built site's identity by default; pass `command: \"serve\"` for a dev host.\n *\n * ```ts\n * export default defineSeoConfig({\n * loadGraph: tanstackStartGraph({ root: import.meta.dirname }),\n * });\n * ```\n */\nexport const tanstackStartGraph =\n (options: EvaluateAppGraphOptions): SeoGraphLoader =>\n async (_input, environment): Promise<LoadedSeoGraph> => {\n const { graph, site, pagegraph } = await withCleanStdout(() =>\n evaluateAppGraph({\n ...options,\n command: environment?.command ?? options.command ?? \"build\",\n mode: environment?.mode ?? options.mode ?? \"production\",\n }),\n );\n return { graph, site, pagegraph, dispose: async () => {} };\n };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAoBA,eAAsB,WAAW,MAAc,iBAAiD;CAC9F,MAAM,iBAAiB,QAAQ,MAAM,+BAA+B;CACpE,IAAI;CACJ,MAAM,SAAS,UACb;EACE,iBAAiB,QAAQ,MAAM,eAAe;EAC9C,oBAAoB,QAAQ,gBAAgB,mBAAmB;EAC/D,QAAQ,QAAQ,gBAAgB,KAAK;EACrC,cAAc;EACd,2BAA2B;EAC3B,gBAAgB;EAChB,SAAS,CACP;GACE,MAAM;GACN,mBAAmB,EAAE,WAAW,YAAY,iBAAiB;IAC3D,UAAU;KAAE,MAAM;KAAe,MAAM;KAAW,OAAO;IAAW;GACtE;EACF,CACF;CACF,GACA,IACF;CACA,MAAM,SAAS,gBAAgB,OAAO,kBAAkB;CACxD,MAAM,IAAI,UAAU;EAAE;EAAQ;EAAM,IAAI,OAAO;CAAG,CAAC,CAAC,CAAC,IAAI;CACzD,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,MAAM,oCAAoC,QAAQ,MAAM,eAAe,GAAG;CAEtF,OAAO;AACT;;;;;;;;;ACqCA,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAU;CAAU;CAAU;AAAiB,CAAC;AAEnF,MAAM,aAAa,UAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEpG,MAAM,mBAAmB,QAAiC,SAAwD;CAChH,MAAM,QAAQ,OAAO;CACrB,IAAI,OAAO,YAAY,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,yBAAyB;CACnF,MAAM,OAAO,MAAM,QAAQ;CAC3B,OAAO,SAAS,KAAA,IAAY,KAAA,IAAY;EAAE,KAAK,KAAK;EAAK,UAAU,KAAK;EAAU,MAAM,KAAK;CAAK;AACpG;AAEA,MAAM,kBAAkB,OACtB,QACA,MACA,aAC0C;CAC1C,MAAM,OAAO,SAAS,MAAM,QAAQ;CAIpC,MAAM,YAAW,MAHI,OAAO,OAAO,QAAQ,CAAC,CAAC,OAAO,UAAmB;EACrE,MAAM,IAAI,MAAM,gCAAgC,KAAK,IAAI,UAAU,KAAK,KAAK,EAAE,MAAM,CAAC;CACxF,CAAC,EAAA,CACuB;CACxB,IAAI,aAAa,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,+BAA+B;CACnF,MAAM,cAAuB,OAAO,aAAa,aAAa,MAAO,SAA2B,IAAI;CACpG,IAAI,CAAC,MAAM,QAAQ,WAAW,GAAG,MAAM,IAAI,MAAM,GAAG,KAAK,gDAAgD;CACzG,OAAO;AACT;;AAeA,eAAsB,UAAU,MAAc,SAA+C;CAC3F,MAAM,OAAO,MAAM,WAAW,MAAM,QAAQ,mBAAmB,YAAY;CAC3E,MAAM,YAAY,QAAQ,WAAW,CAAC,EAAA,CAAG,IAAI,YAAY;CACzD,MAAM,gBAAgB,SACpB,kBAAkB,IAAI,KAAK,YAAY,MACtC,KAAK,iBAAiB,YAAY,KAAK,sBAAsB,IAAI,YAAY,MAAM,SACpF,CAAC,SAAS,MAAM,YAAY,QAAQ,KAAK,KAAK,QAAQ,CAAC;CACzD,OAAO;EACL;EACA;EACA;EACA,YAAY,CAAC,KAAK,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,OAAO,YAAY,CAAC,CAAC,KAAK,SAAS,KAAK,QAAQ;EACvF,iBAAiB,QAAQ,gBAAgB,KAAA,IAAY,KAAA,IAAY,QAAQ,MAAM,QAAQ,WAAW;EAClG,WAAW,QAAQ,UAAU,KAAA,IAAY,KAAA,IAAY,QAAQ,MAAM,QAAQ,KAAK;CAClF;AACF;;;;;AAMA,MAAM,iBAAiB,SACrB,KAAK,cAAc,QAAQ,KAAK,gBAAgB,KAAA,KAAa,KAAK,gBAAgB,KAAK,KAAA,IAAY,KAAK;;AAG1G,eAAsB,cAAc,QAA2B,MAAoC;CACjG,MAAM,EAAE,MAAM,SAAS,SAAS;CAChC,MAAM,aAAa,IAAI,IACrB,MAAM,QAAQ,IACZ,KAAK,WAAW,IAAI,OAAO,aAAa;EACtC,MAAM,OAAO,SAAS,MAAM,QAAQ;EACpC,MAAM,YAAY,SAAS,QAAQ,MAAM,QAAQ,mBAAmB,YAAY,GAAG,QAAQ;EAC3F,MAAM,SAAS,MAAM,OAAO,OAAO,QAAQ,CAAC,CAAC,OAAO,UAAmB;GACrE,MAAM,IAAI,MACR,gCAAgC,KAAK,6BAA6B,UAAU,KAAK,EAAE,4DACtB,UAAU,+BACvE,EAAE,MAAM,CACV;EACF,CAAC;EACD,OAAO,CAAC,UAAU,gBAAgB,QAAQ,IAAI,CAAC;CACjD,CAAC,CACH,CACF;CAEA,MAAM,YAAY,UAAmC;EACnD,SAAS;GAAE,MAAM,cAAc,IAAI;GAAG,YAAY,WAAW,IAAI,KAAK,QAAQ;EAAE;EAChF,WAAW,KAAK,YAAY,CAAC,EAAA,CAAG,IAAI,QAAQ;CAC9C;CACA,MAAM,YAA0B;EAC9B,SAAS,EAAE,YAAY,WAAW,IAAI,KAAK,KAAK,QAAQ,EAAE;EAC1D,UAAU,KAAK,KAAK,IAAI,QAAQ;CAClC;CAEA,MAAM,cACJ,KAAK,oBAAoB,KAAA,IAAY,CAAC,IAAI,MAAM,gBAAgB,QAAQ,MAAM,KAAK,eAAe;CACpG,MAAM,QAAQ,cAAc;EAAE;EAAW;CAAY,CAAC;CAEtD,MAAM,aACJ,QAAQ,gBAAgB,KAAA,IACpB,CAAC,IACD,kBACE,KAAK,OACL,eAAe;EAAE,GAAG,QAAQ;EAAa,iBAAiB,QAAQ;CAAgB,CAAC,CACrF,CAAC,CAAC;CACR,MAAM,OAAoB;EACxB,QAAQ,QAAQ;EAChB,WAAW,QAAQ,aAAa;EAChC,QAAQ;GACN,UAAU,CAAC,mBAAG,IAAI,IAAI,CAAC,GAAG,YAAY,GAAI,QAAQ,QAAQ,YAAY,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,KAAK;GAClF,eAAe,QAAQ,QAAQ;GAC/B,YAAY,QAAQ,QAAQ;EAC9B;CACF;CACA,IAAI;CACJ,IAAI,KAAK,cAAc,KAAA,GAAW;EAChC,MAAM,OAAO,SAAS,MAAM,KAAK,SAAS;EAC1C,IAAI;GACF,MAAM,SAAS,MAAM,OAAO,OAAO,KAAK,SAAS;GACjD,IAAI,OAAO,aAAa,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,yBAAyB;GACpF,QAAQ,YAAY,OAAO,QAAQ;EACrC,SAAS,OAAO;GACd,MAAM,IAAI,MAAM,gCAAgC,KAAK,IAAI,UAAU,KAAK,KAAK,EAAE,MAAM,CAAC;EACxF;CACF;CACA,OAAO;EAAE;EAAO;EAAM,UAAU,QAAQ,aAAa,KAAA,IAAY,OAAO,EAAE,QAAQ,QAAQ,SAAS,OAAO;EAAG;CAAM;AACrH;;;AC9MA,MAAM,aAA2C,OAAO,cAAc,OAAO,MAAM;CACjF,OAAO;CACP,OAAO,OAAO;EAAE,MAAM,OAAO;EAAQ,MAAM,OAAO;CAAO,CAAC;CAC1D,OAAO,OAAO;EACZ,KAAK,OAAO,SAAS;GAAC;GAAM;GAAU;GAAK;GAAQ;EAAI,CAAC;EACxD,MAAM,OAAO,YAAY,OAAO,MAAM;EACtC,UAAU,OAAO,MAAM,UAAU;CACnC,CAAC;AACH,CAAC,CAAC;AACF,MAAM,UAAU,OAAO,OAAO;CAC5B,MAAM,OAAO;CAAQ,MAAM,OAAO,MAAM,UAAU;CAAG,MAAM,OAAO;CAClE,QAAQ,OAAO;CAAQ,OAAO,OAAO,MAAM,OAAO,MAAM;CACxD,UAAU,OAAO,SAAS;EAAC;EAAO;EAAU;CAAQ,CAAC;AACvD,CAAC;AACD,MAAM,YAAY,OAAO,MAAM,CAC7B,OAAO,OAAO;CAAE,MAAM,OAAO,QAAQ,SAAS;CAAG,SAAS;AAAQ,CAAC,GACnE,OAAO,OAAO;CAAE,MAAM,OAAO,QAAQ,QAAQ;CAAG,MAAM,OAAO;CAAQ,QAAQ,OAAO;CAClF,UAAU,OAAO,SAAS;EAAC;EAAO;EAAU;CAAQ,CAAC;AAAE,CAAC,CAC5D,CAAC;AACD,MAAa,eAA+C,OAAO,OAAO;CACxE,MAAM,OAAO;CAAQ,MAAM,OAAO;CAAQ,OAAO,OAAO;CAAQ,aAAa,OAAO;CACpF,UAAU,OAAO,MAAM,OAAO,OAAO;EACnC,gBAAgB,OAAO,YAAY,OAAO,MAAM;EAAG,UAAU,OAAO,YAAY,OAAO,OAAO;EAC9F,MAAM,OAAO;EAAQ,IAAI,OAAO;EAAQ,OAAO,OAAO,YAAY,OAAO;EACzE,UAAU,OAAO,YAAY,OAAO,MAAM;EAAG,MAAM,OAAO,MAAM,SAAS;EACzE,OAAO,OAAO,MAAM,OAAO,OAAO;GAChC,OAAO,OAAO,YAAY,OAAO;GAAG,MAAM,OAAO,YAAY,OAAO,MAAM;GAC1E,MAAM,OAAO,MAAM,SAAS;GAAG,OAAO,OAAO,MAAM,OAAO;GAAG,QAAQ,OAAO;GAC5E,UAAU,OAAO,SAAS;IAAC;IAAO;IAAU;GAAQ,CAAC;EACvD,CAAC,CAAC;EAAG,QAAQ,OAAO;EAAQ,UAAU,OAAO,SAAS;GAAC;GAAO;GAAU;EAAQ,CAAC;CACnF,CAAC,CAAC;CAAG,UAAU,OAAO,MAAM,OAAO;CAAG,OAAO,OAAO,MAAM,OAAO,MAAM;AACzE,CAAC;AACD,MAAa,gBAAgB,OAAO,OAAO;CACzC,QAAQ,OAAO;CACf,WAAW,OAAO,MAAM,YAAY;CACpC,OAAO,OAAO,MAAM,OAAO,OAAO;EAAE,MAAM,OAAO;EAAQ,OAAO,OAAO;EAAQ,aAAa,OAAO;CAAO,CAAC,CAAC;AAC9G,CAAC;;;;ACvBD,SAAS,aAAa,MAAsB;CAC1C,IAAI,CAAC,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,IAAI,KAAK,6BAA6B,KAAK,IAAI,KACvF,SAAS,OAAO,KAAK,SAAS,GAAG,KAAM,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,SAAS,SAAS,OAAO,SAAS,QAAQ,SAAS,OAAQ,SAAS,MAAM,SAAS,GAAI,GAChK,MAAM,IAAI,MAAM,mCAAmC,KAAK,EAAE;CAE5D,OAAO,SAAS,MAAM,UAAU,KAAK,MAAM,CAAC;AAC9C;AAEA,SAAS,eAAe,UAAgC;CACtD,MAAM,OAAO,aAAa,SAAS,IAAI;CACvC,MAAM,EAAE,MAAM,GAAG,YAAY;CAC7B,IAAI,CAAC,iBAAiB,KAAK,IAAI,KAAK,aAAa,OAAO,MAAM,MAC5D,MAAM,IAAI,MAAM,uCAAuC,SAAS,KAAK,EAAE;CACzE,OAAO;AACT;;AAGA,eAAe,eAAe,WAAmB,OAA2C;CAC1F,MAAM,QAAQ,OAAO,WAAkC;EACrD,MAAM,UAAU,MAAM,QAAQ,QAAQ,EAAE,eAAe,KAAK,CAAC;EAC7D,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,OAAO,QAAQ,QAAQ,MAAM,IAAI;GACvC,IAAI,MAAM,YAAY,GAAG;IAAE,MAAM,MAAM,IAAI;IAAG;GAAU;GACxD,IAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,OAAO,GAAG;GACtD,MAAM,WAAW,MAAM,SAAS,MAAM,MAAM;GAC5C,IAAI;GACJ,IAAI;GACJ,IAAI;IACF,WAAW,OAAO,kBAAkB,OAAO,eAAeA,YAAkB,CAAC,CAAC,CAAC,UAAU,EAAE,kBAAkB,QAAQ,CAAC;IACtH,OAAO,eAAe,QAAQ;GAChC,QAAQ;IAAE;GAAU;GACpB,IAAI,SAAS,QAAQ,WAAW,GAAG,KAAK,MAAM,KAAK,CAAC,MAAM,IAAI,SAAS,IAAI,GAAG,MAAM,OAAO,IAAI;EACjG;CACF;CACA,MAAM,MAAM,SAAS;AACvB;;AAGA,eAAsB,uBACpB,MAAc,MAAc,cAAsB,QAAwB,QAAe,CAAC,GAC3E;CACf,MAAM,SAAS,OAAO,kBAAkB,OAAO,eAAe,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,kBAAkB,QAAQ,CAAC;CACjH,MAAM,SAAS,IAAI,IAAI,OAAO,MAAM;CACpC,IAAK,OAAO,aAAa,YAAY,OAAO,aAAa,WAAY,OAAO,WAAW,OAAO,QAC5F,MAAM,IAAI,MAAM,qCAAqC,OAAO,OAAO,EAAE;CACvE,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,QAAQ,OAAO,UAAU,KAAK,aAAa;EAC/C,IAAI,OAAO,IAAI,SAAS,IAAI,GAAG,MAAM,IAAI,MAAM,qCAAqC,SAAS,KAAK,EAAE;EACpG,OAAO,IAAI,SAAS,IAAI;EACxB,MAAM,OAAO,eAAe,QAAQ;EACpC,IAAI,aAAa,IAAI,IAAI,GAAG,MAAM,IAAI,MAAM,kDAAkD,KAAK,EAAE;EACrG,aAAa,IAAI,IAAI;EACrB,OAAO;CACT,CAAC;CACD,MAAM,OAAO,QAAQ,cAAc,sBAAsB,MAAM,CAAC,CAAC,CAAC;CAClE,MAAM,YAAY,QAAQ,MAAM,sBAAsB;CACtD,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAC1C,KAAK,MAAM,CAAC,OAAO,aAAa,OAAO,UAAU,QAAQ,GAAG;EAC1D,MAAM,OAAO,MAAM;EACnB,MAAM,cAAc,QAAQ,WAAW,GAAG,KAAK,MAAM;EACrD,MAAM,OAAO,QAAQ,cAAc,GAAG,KAAK,IAAI;EAC/C,MAAM,MAAM,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;EACrD,MAAM,UAAU,aAAa,KAAK,UAAU,QAAQ,CAAC;EACrD,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,UAAU,MAAM,iBAAiB,UAAU,OAAO,MAAM,CAAC;CACjE;CACA,MAAM,eAAe,WAAW,MAAM;CACtC,MAAM,UAAU,QAAQ,MAAM,uBAAuB,GAAG,GAAG,KAAK,UAAU,OAAO,KAAK,EAAE,GAAG;CAC3F,IAAI,WAAW,KAAA,GACb,MAAM,OAAO,WAAW,aAAa,MAAM,OAAO,WAAW,OAAO,OAAO,OAAO,MAAM,CAAC;AAC7F;;;AC7EA,IAAM,sBAAN,cAAkC,OAAO,YAAiC,CAAC,CAAC,uBAAuB,EACjG,SAAS,OAAO,OAClB,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,oCAAoB,IAAI,IAAI;CAAC;CAAK;CAAS;CAAW;CAAU;CAAa;AAAW,CAAC;AAE/F,MAAM,QAAQ,OAAO,GAAG,sBAAsB,CAAC,CAAC,WAAW,MAAc,IAAY;CACnF,OAAO,OAAO,OAAO,IAAI;EACvB,WAAW,SAAS,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,IAAI,QAAQ,GAAG,SAAS,KAAK,IAAI,OAAO,GAAG,SAAS,MAAM,IAAI,QAAQ,KAAK,GAAG,EAAE;EACpI,QAAQ,UAAU,IAAI,oBAAoB,EACxC,SAAS,gCAAgC,GAAG,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,IACvG,CAAC;CACH,CAAC;AACH,CAAC;AAED,SAAS,eAAe,SAItB;CACA,MAAM,wBAAQ,IAAI,IAAY;CAC9B,MAAM,2BAAW,IAAI,IAAY;CACjC,MAAM,6BAAa,IAAI,IAAY;CACnC,KAAK,MAAM,QAAQ,QAAQ,MAAM;EAC/B,IACE,KAAK,SAAS,uBACd,KAAK,OAAO,UAAU,qBACtB,KAAK,eAAe,QAEpB;EACF,KAAK,MAAM,aAAa,KAAK,YAC3B,IAAI,UAAU,SAAS,mBAAmB;GACxC,IAAI,UAAU,eAAe,QAAQ;GACrC,MAAM,WACJ,UAAU,SAAS,SAAS,eACxB,UAAU,SAAS,OACnB,UAAU,SAAS;GACzB,IAAI,kBAAkB,IAAI,QAAQ,GAAG,MAAM,IAAI,UAAU,MAAM,IAAI;GACnE,IAAI,aAAa,WAAW,SAAS,IAAI,UAAU,MAAM,IAAI;EAC/D,OAAO,IAAI,UAAU,SAAS,4BAC5B,WAAW,IAAI,UAAU,MAAM,IAAI;CAGzC;CACA,OAAO;EAAE;EAAO;EAAU;CAAW;AACvC;AAEA,SAAS,YAAY,MAAgD;CACnE,IAAI,KAAK,SAAS,iBAAiB,OAAO,CAAC,KAAK,IAAI;CACpD,IAAI,KAAK,SAAS,uBAAuB,OAAO,CAAC;CACjD,OAAO,CAAC,GAAG,YAAY,KAAK,MAAM,GAAG,KAAK,SAAS,IAAI;AACzD;AAEA,SAAS,kBACP,MACA,OACS;CAET,MAAM,CAAC,MAAM,GAAG,WADF,YAAY,IACK;CAC/B,IAAI,SAAS,KAAA,GAAW,OAAO;CAC/B,IAAI,QAAQ,WAAW,GAAG,OAAO,MAAM,MAAM,IAAI,IAAI;CACrD,IAAI,MAAM,WAAW,IAAI,IAAI,GAC3B,OACG,QAAQ,WAAW,KAAK,QAAQ,OAAO,KAAA,KAAa,kBAAkB,IAAI,QAAQ,EAAE,KACpF,QAAQ,WAAW,KAAK,QAAQ,OAAO,aAAa,QAAQ,OAAO;CAExE,OAAO,MAAM,SAAS,IAAI,IAAI,KAAK,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAC5E;AAEA,SAAS,KAAK,OAAgB,OAAgD;CAC5E,IAAI,MAAM,QAAQ,KAAK,GACrB,KAAK,MAAM,SAAS,OAAO,KAAK,OAAO,KAAK;MACvC,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACtD,IAAI,iBAAiB,KAAK,GAAG,MAAM,KAAK;EACxC,KAAK,MAAM,SAAS,OAAO,OAAO,KAAK,GAAG,KAAK,OAAO,KAAK;CAC7D;AACF;AAEA,SAAS,iBAAiB,MAAyC;CACjE,OAAO,UAAU,QAAQ,KAAK,SAAS;AACzC;AAEA,SAAgB,wBACd,MACA,IACA,MAC8B;CAE9B,MAAM,UAAU,OAAO,QAAQ,MAAM,MAAM,EAAE,CAAC;CAC9C,MAAM,QAAQ,eAAe,OAAO;CACpC,MAAM,QAA8D,CAAC;CACrE,MAAM,OAAO,SAAS,QAAQ,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;CAErE,KAAK,UAAU,SAAS;EACtB,IAAI,CAAC,kBAAkB,KAAK,MAAM,KAAK,GAAG;EAC1C,IACE,KAAK,WAAW,MACb,cACC,UAAU,SAAS,kBACnB,UAAU,KAAK,SAAS,mBACxB,UAAU,KAAK,SAAS,QAC5B,GAEA;EACF,MAAM,OAAO,KAAK,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;EAEnD,MAAM,KAAK;GACT,OAAO,KAAK,KAAK;GACjB,KAAK,KAAK,KAAK;GACf,OAAO,WAAW,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM;EACpD,CAAC;CACH,CAAC;CAED,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;CAC/B,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CACtC,IAAI,cAAc;CAClB,KAAK,MAAM,QAAQ,OACjB,cAAc,YAAY,MAAM,GAAG,KAAK,KAAK,IAAI,KAAK,QAAQ,YAAY,MAAM,KAAK,GAAG;CAC1F,OAAO,EAAE,MAAM,YAAY;AAC7B;;;;;;;;;;;;;;;;;;;;;;;;;ACxFA,MAAaC,sBAAoB;AACjC,MAAa,cAAc;AAE3B,MAAM,sBAAsB;AAC5B,MAAM,WAAW;AACjB,MAAM,oBAAoB,KAAK;;AAE/B,MAAM,kBAAkB;CAAC;CAAmC;CAAqC;AAAgC;;AAEjI,MAAM,kBAAkB;AASxB,MAAM,uBAA+B;CACnC,MAAM;CACN,SAAS;CACT,qBAAqB,gBAAgB,YAAY,SAASA;CAC1D,WAAW;EACT,QAAQ,EAAE,IAAI,oDAAe;EAC7B,UAAU,QAAQ;GAAE;GAAI,UAAU;EAAK;CACzC;AACF;AAOA,MAAM,iBAAiB,EAAE,OAAO,MAAM,UAAU,YAC9C,wBAAwB,KAAK,UAAU,YAAY,KAAK,CAAC,EAAE,yBAAyB,KAAK,UAAU,IAAI,EAAE,6BAA6B,KAAK,UAAU,QAAQ,EAAE,0BAA0B,KAAK,UAAU,KAAK,KAAK,YAAY;;AAGhO,MAAM,eAAe,SAA4B;CAG/C,OAAO,6BADS;EADD,GAAG,KAAK;EAAY,GAAI,KAAK,oBAAoB,KAAA,IAAY,CAAC,IAAI,CAAC,KAAK,eAAe;EAAI,GAAI,KAAK,cAAc,KAAA,IAAY,CAAC,IAAI,CAAC,KAAK,SAAS;CAC7I,CAAC,CAAC,KAAK,SAAS,KAAK,KAAK,UAAU,IAAI,EAAE,iBAAiB,KAAK,UAAU,IAAI,EAAE,GAC1D,CAAC,CAAC,KAAK,IAAI,EAAE;AACzD;;AAGA,MAAM,aAAa,OAAO,SAAsB,aAA+B,SAAuC;CACpH,MAAM,QAAQ,MAAM,WAAW;CAC/B,MAAM,QAAQ,KAAK,QAAQ,YAAY,OAAO,MAAM,YAAY,OAAO,MAAM,MAAM,GAAG,WAAW;CACjG,MAAM,EAAE,YAAa,MAAM,OAAO,cAAc,KAAK,CAAC,CAAC;CAUvD,OAAO,cAAc,EANnB,SAAS,SAAS;EAChB,MAAM,OAAO,QAAQ;EACrB,IAAI,SAAS,KAAA,GAAW,MAAM,IAAI,MAAM,GAAG,KAAK,2BAA2B;EAC3E,OAAO,KAAK;CACd,EAEwB,GAAG,IAAI;AACnC;AAUA,SAAgB,UAAU,SAA6C;CACrE,IAAI,QAAQ,WAAW,KAAA,KAAa,QAAQ,aAAa,KAAA,GACvD,MAAM,IAAI,MAAM,+GAA+G;CACjI,MAAM,MAA0B,EAAE,QAAQ;CAC1C,IAAI,OAAO,QAAQ,IAAI;CACvB,IAAI,eAAe,QAAQ,MAAM,aAAa;CAC9C,IAAI;CACJ,IAAI;;CAEJ,IAAI;CACJ,IAAI;;CAEJ,IAAI,aAAa;CAEjB,MAAM,gBAAgB,OAAO,gBAA2D;EACtF,SAAS;GACP,MAAM,UAAU;GAEhB,YAAY,OAAO,WAAW;GAC9B,MAAM,SAAS,MAAM,cAAc,YAAY,QAAQ,MAAM,UAAU,MAAM,OAAO,CAAC;GACrF,IAAI,YAAY,YAAY,OAAO;EACrC;CACF;CAyIA,OAAO,OAAO,OAAO;EACnB;GAvIA,MAAM;GACN;GAGA,mBAAmB;GACnB,SAAS;IACP,OAAO,EACL,cAAc,GACXA,sBAAoB;KACnB,UAAU;KACV,KAAK,EACH,oBAAoB,MAAM,sBAAsB,6BAA6B,MAAM,iBAAiB,EACtG;KACA,OAAO;MACL,QAAQ;MACR,aAAa;MACb,eAAe;MACf,QAAQ;MACR,iBAAiB;OACf,OAAO,EAAE,OAAO,SAAS;OACzB,QAAQ;QAAE,gBAAgB;QAAc,gBAAgB;OAA2B;MACrF;KACF;IACF,EACF,EACF;GACF;GACA,kBAAkB,MAAM,mBAAmB;IACzC,IAAI,SAAA,aAA4B;KAC9B,kBAAkB,YAAY,CAAC;KAC/B,kBAAkB,QAAQ,aAAa,CAAC;KACxC;IACF;IACA,kBAAkB,iBAAiB,CAAC;IACpC,kBAAkB,aAAa,UAAU,CAAC,GAAI,kBAAkB,aAAa,WAAW,CAAC,GAAI,GAAG,eAAe;GACjH;GACA,eAAe,UAAU;IACvB,OAAO,SAAS;IAChB,eAAe,QAAQ,MAAM,SAAS,aAAa,QAAQ,MAAM,UAAU,aAAa;GAC1F;GACA,iBAAiB;IAEf,OAAO;IACP,QAAQ,WAAW;KACjB,SAAS;KACT,iBAAiB,UAAU,aAAaA;IAC1C;GACF;GAGA,UAAU,EAAE,MAAM,WAAW;IAC3B,IAAI,KAAK,YAAY,SAAA,aAA4B;IACjD,MAAM,kBAAkB,QAAQ,MAAM,QAAQ,mBAAmB,YAAY;IAC7E,IAAI,QAAQ,WAAW,KAAK,CAAC,KAAK,WAAW,GAAG,gBAAgB,EAAE,GAAG;IACrE,cAAc;IACd,UAAU,KAAA;IACV,KAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,gBAAgB,CAAC,CAAC,GAAG;KAC7D,IAAI,MAAM,SAAA,aAA4B;KACtC,MAAM,UAAU,MAAM,YAAY,cAAc,mBAAmB;KACnE,IAAI,YAAY,KAAA,GAAW,MAAW,aAAa,OAAO;IAC5D;GACF;GACA,UAAU;IACR,OAAO;IACP,MAAM,QAAQ,SAAS;KACrB,MAAM,cAAc,QAAQ,aAAaA;KACzC,IAAI,gBAAgB,KAAA,GAAW;KAC/B,YAAY,MAAM,UAAU,YAAY,OAAO,MAAM,OAAO;KAC5D,MAAM,QAAQ,WAAW,SAAS,aAAa,SAAS;KACxD,UAAU;KACV,MAAM;KAGN,YAAY,UAAU;IACxB;GACF;GACA,WAAW;IACT,QAAQ,EAAE,IAAI,uCAAuC;IACrD,UAAU,OAAO,KAAK;GACxB;GACA,MAAM;IACJ,QAAQ,EAAE,IAAI,yCAAyC;IACvD,MAAM,QAAQ,IAAI;KAChB,IAAI,OAAO,mBAAmB;MAC5B,IAAI,cAAc,KAAA,GAAW,MAAM,IAAI,MAAM,IAAI,YAAY,IAAI,SAAS,8CAA8C;MACxH,OAAO,YAAY,SAAS;KAC9B;KAEA,IAAI,KAAK,YAAY,SAAA,aAA4B,OAAO;KACxD,IAAI,YAAY,KAAA,GAAW;MACzB,IAAI,mBAAmB,KAAA,GACrB,MAAM,IAAI,MACR,IAAI,YAAY,mCAAmC,KAAK,YAAY,KAAK,8DAC3E;MAEF,UAAU,cAAc,cAAc;KACxC;KACA,OAAO,cAAc,MAAM,OAAO;IACpC;GACF;EAoCU;EACV;GAjCA,MAAM;GACN,SAAS;GACT,qBAAqB,gBAAgB,YAAY,SAASA;GAC1D,WAAW;IACT,QAAQ,EAAE,IAAI,wCAAwC;IACtD,UAAU,OAAO,KAAK;GACxB;GACA,MAAM;IACJ,QAAQ,EAAE,IAAI,0CAA0C;IACxD,eAAe,2BAA2B,KAAK,UAAU,QAAQ,MAAM,QAAQ,UAAU,eAAe,sBAAsB,CAAC,EAAE;GACnI;GACA,WAAW;IACT,QAAQ;KAAE,IAAI;KAAmB,MAAM;IAAmB;IAC1D,QAAQ,MAAM,IAAI;KAChB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC;KAC3B,IAAI,SAAS,KAAA,KAAa,KAAK,SAAS,gBAAgB,KAAK,KAAK,SAAS,SAAS,KAAK,CAAC,KAAK,WAAW,GAAG,KAAK,EAAE,GAAG;KACvH,OAAO,wBAAwB,MAAM,MAAM,IAAI;IACjD;GACF;GACA,uBAAuB,eAAe;IAEpC,cAAc,YAAY,KAAK,UAAU,UAAU,SAAS;KAC1D,SAAS,GAAG,gBAAgB;MAC1B,MAAM,QAAQ,SAAS,UAAU,mBAAmB;MACpD,IAAI,OAAO,UAAU,UAAU,cAAc,OAAO,OAAO,MAAM,mBAAmB,KAAK,CAAC;KAC5F,CAAC;KACD,KAAK;IACP,CAAC;GACH;EAKa;EACb;EACA,GAAI,QAAQ,gBAAgB,KAAA,IACxB,CAAC,IACD,CAAC,eAAe;GAAE,GAAG,QAAQ;GAAa,iBAAiB,QAAQ;EAAgB,CAAC,CAAC;CAC3F,GAAG,EACD,gBAAgB,QAAQ,aAAa,KAAA,IAAY,CAAC,IAAI,CAAC;EACrD,MAAM;EACN,SAAS,EAAE,SAAS,KAAc;EAClC,WAAW,EAAE,WAAW,OAAO,EAAE,WAA6B;GAC5D,IAAI,YAAY,KAAA,GAAW,MAAM,IAAI,MAAM,oEAAoE;GAC/G,MAAM,MAAM,MAAM;GAClB,MAAM,uBAAuB,MAAM,MAAM,cAAc,QAAQ,QAAQ,IAAI,KAAK;EAClF,EAAE;CACJ,CAAC,EACH,CAAC;AACH;;;;;;;;AC9QA,MAAM,oBAAoB;AAE1B,MAAM,0BAA0B,YAC9B,YAAY,UAAU,GAAG,oBAAoB,EAAE,WAAW,MAAM,EAAE,IAAI,KAAA;;AAiBxE,MAAM,aAAa,aAAoD;CACrE,YAAY,QAAQ,cAAc;EAAC;EAAM;EAAO;EAAM;EAAO;EAAO;CAAK,CAAC,CACvE,KAAK,cAAc,QAAQ,QAAQ,MAAM,eAAe,WAAW,CAAC,CAAC,CAAC,KAAK,UAAU,KAAK;CAC7F,UAAU;CACV,SAAS,CAAC;EACR,MAAM;EACN,SAAS;EACT,OAAO,QAAQ;GAAE,OAAO,SAAS,QAAQ;EAAM;CACjD,CAAC;AACH;AAEA,MAAM,aAAa,SAAgC,eAAuD;CACxG,MAAM,SAAS,QAAQ,MAAM,cAAc,UAAU,SAAS,WAAW;CACzE,IAAI,QAAQ,QAAQ,KAAA,GAClB,MAAM,IAAI,MAAM,GAAG,cAAc,kBAAkB,gEAAgE;CAErH,OAAO,OAAO;AAChB;;;;;;AAkCA,eAAsB,iBAAiB,SAAwH;CAC7J,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,MAAM,cACnB;EACE,GAAG,UAAU,OAAO;EACpB,MAAM,QAAQ;EACd,cAAc,uBAAuB,OAAO;CAC9C,GACA,OACF;CACA,MAAM,MAAM,UAAU,OAAO,SAAS,OAAO,UAAU;CACvD,MAAM,cAAc,6BAA6B,mBAAmB,QAAQ,EAAE,KAAK,MAAM,CAAC;CAC1F,MAAM,YAAY,KAAK;CACvB,IAAI;EAEF,MAAM,YAAY,gBAAgB,WAAW,CAAC,CAAC;EAC/C,MAAM,MAAM,MAAM,cAAc,YAAY,QAAQ,MAAM,UAAU,OAAO,MAAM,IAAI,OAAO,CAAC;EAC7F,OAAO;GAAE,GAAG;GAAK,WAAW;IAAE,MAAM,OAAO;IAAM,SAAS,IAAI;IAAS,OAAO,IAAI,SAAS,CAAC;GAAE;EAAE;CAClG,UAAU;EACR,MAAM,YAAY,MAAM;CAC1B;AACF;;;;;;;;;;;AAYA,MAAa,sBACV,YACD,OAAO,QAAQ,gBAAyC;CACtD,MAAM,EAAE,OAAO,MAAM,cAAc,MAAM,sBACvC,iBAAiB;EACf,GAAG;EACH,SAAS,aAAa,WAAW,QAAQ,WAAW;EACpD,MAAM,aAAa,QAAQ,QAAQ,QAAQ;CAC7C,CAAC,CACH;CACA,OAAO;EAAE;EAAO;EAAM;EAAW,SAAS,YAAY,CAAC;CAAE;AAC3D"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["PageDocumentSchema","GRAPH_ENVIRONMENT"],"sources":["../../src/tanstack-start/routes.ts","../../src/tanstack-start/graph.ts","../../src/markdown/schema.ts","../../src/tanstack-start/persist-markdown.ts","../../src/tanstack-start/source.ts","../../src/tanstack-start/plugin.ts","../../src/tanstack-start/load.ts"],"sourcesContent":["/**\n * Route discovery for the graph: the router's own generator parses the routes\n * directory (the same parse `routeTree.gen.ts` comes from), into memory only.\n */\n\nimport { resolve } from \"node:path\";\n\nimport { Generator, getConfig, type RouteNode } from \"@tanstack/router-generator\";\n\nimport { inMemoryWriteFs } from \"../vite/route-config\";\n\nexport interface ScannedRoutes {\n /** `__root.tsx`. */\n readonly root: RouteNode;\n /** Top-level route nodes, each with its `children`. */\n readonly tree: ReadonlyArray<RouteNode>;\n /** Every route node, flat. */\n readonly nodes: ReadonlyArray<RouteNode>;\n}\n\nexport async function scanRoutes(root: string, routesDirectory: string): Promise<ScannedRoutes> {\n const cacheDirectory = resolve(root, \"node_modules/.cache/pagegraph\");\n let scanned: ScannedRoutes | undefined;\n const config = getConfig(\n {\n routesDirectory: resolve(root, routesDirectory),\n generatedRouteTree: resolve(cacheDirectory, \"route-tree.gen.ts\"),\n tmpDir: resolve(cacheDirectory, \"tmp\"),\n disableTypes: true,\n enableRouteTreeFormatting: false,\n disableLogging: true,\n plugins: [\n {\n name: \"pagegraph:scan\",\n onRouteTreeChanged({ routeTree, routeNodes, rootRouteNode }) {\n scanned = { root: rootRouteNode, tree: routeTree, nodes: routeNodes };\n },\n },\n ],\n },\n root,\n );\n const memory = inMemoryWriteFs(config.generatedRouteTree);\n await new Generator({ config, root, fs: memory.fs }).run();\n if (scanned === undefined) {\n throw new Error(`pagegraph found no route tree in ${resolve(root, routesDirectory)}`);\n }\n return scanned;\n}\n","/**\n * Build the graph from the app's own modules, evaluated in the `pagegraph`\n * Vite environment: one module per route file that declares `staticData`, plus\n * the app's collections module. Never the route tree, which reaches every\n * route — including app routes that only resolve inside the server runtime.\n */\n\nimport { relative, resolve } from \"node:path\";\n\nimport type { ClaimsOptions } from \"../decide/families/claims\";\n\nimport type { RouteNode } from \"@tanstack/router-generator\";\n\nimport { globToRegExp } from \"../core/checks\";\nimport { buildSeoGraph, type OgImageResolver, type SeoCollection, type SeoGraph, type SeoRouteNode } from \"../core/graph\";\nimport type { RobotsConfig } from \"../core/projections\";\nimport type { SiteRuntime } from \"../config/vite-graph-loader\";\nimport { deriveRouteConfig, resolveOptions, type SeoRouteConfigOptions } from \"../vite/route-config\";\nimport { scanRoutes, type ScannedRoutes } from \"./routes\";\nimport { decodeFacts, type Facts } from \"../markdown/facts\";\n\n/** The robots.txt policy a build serves; `origin` and indexability come from {@link SiteRuntime}. */\nexport type RobotsPolicy = Pick<RobotsConfig, \"disallow\" | \"contentSignal\" | \"directives\">;\n\n/** The graph and the site runtime one evaluation produces. */\nexport interface AppGraph {\n readonly graph: SeoGraph;\n readonly site: SiteRuntime;\n readonly markdown: { readonly origin: string } | null;\n readonly facts: Facts | undefined;\n}\n\nexport type { SiteRuntime };\n\nexport interface PagegraphOptions {\n /** This build's canonical origin, no trailing slash, e.g. `https://example.com`. */\n readonly origin: string;\n /** Fallback social image; use the same resolver in createSeo. */\n readonly ogImage?: OgImageResolver | undefined;\n /**\n * Whether this build's host may be indexed. A non-indexable build serves a\n * disallow-all robots.txt with no Sitemap line. Defaults to `true`.\n */\n readonly indexable?: boolean | undefined;\n readonly robots?:\n | {\n /** Path prefixes disallowed on an indexable host, beyond the route-config exclusions. */\n readonly disallow?: ReadonlyArray<string> | undefined;\n /** `Content-Signal` value for an indexable host, e.g. `\"search=yes, ai-input=yes, ai-train=yes\"`. */\n readonly contentSignal?: string | undefined;\n /** Extra lines in the `User-agent: *` group on an indexable host. */\n readonly directives?: ReadonlyArray<string> | undefined;\n }\n | undefined;\n /**\n * Root-relative module whose `collections` export is a `SeoCollection[]`, or a\n * function returning one (sync or async). It runs in the app's Vite pipeline,\n * so content virtual modules (Fumadocs, MDX) and path aliases resolve.\n */\n readonly collections?: string | undefined;\n /** Canonical origin for rendered documents; serverEntry is relative to the Vite root. */\n readonly markdown?: { readonly origin: string; readonly serverEntry?: string | undefined } | undefined;\n /** Committed claims answers gate the rendered documents and graph heads at build time. */\n readonly claims?: ClaimsOptions | undefined;\n /** Root-relative module exporting the code-owned document facts. */\n readonly facts?: string | undefined;\n /**\n * Route files (globs relative to the routes directory) never evaluated for the\n * graph: app surfaces that declare `staticData` but import server-runtime-only\n * modules. Their paths stay out of the graph.\n */\n readonly exclude?: ReadonlyArray<string> | undefined;\n /** Relative to the Vite root. Defaults to `src/routes`. */\n readonly routesDirectory?: string | undefined;\n /**\n * Generate the route-config module and enforce per-group SEO coverage (the\n * `seoRouteConfig` gate). Its robots exclusions join `robots.disallow`.\n */\n readonly routeConfig?: Omit<SeoRouteConfigOptions, \"routesDirectory\"> | undefined;\n}\n\n/** Imports a module through the `pagegraph` environment's module runner. */\nexport interface GraphModuleRunner {\n import(url: string): Promise<Record<string, unknown>>;\n}\n\n\nconst GRAPH_ROUTE_TYPES = new Set([\"__root\", \"static\", \"layout\", \"pathless_layout\"]);\n\nconst messageOf = (cause: unknown): string => (cause instanceof Error ? cause.message : String(cause));\n\nconst routeStaticData = (module: Record<string, unknown>, file: string): SeoRouteNode[\"options\"][\"staticData\"] => {\n const route = module[\"Route\"] as { options?: SeoRouteNode[\"options\"] } | undefined;\n if (route?.options === undefined) throw new Error(`${file} has no \\`Route\\` export`);\n const data = route.options.staticData;\n return data === undefined ? undefined : { seo: data.seo, markdown: data.markdown, llms: data.llms };\n};\n\nconst loadCollections = async (\n runner: GraphModuleRunner,\n root: string,\n fullPath: string,\n): Promise<ReadonlyArray<SeoCollection>> => {\n const path = relative(root, fullPath);\n const module = await runner.import(fullPath).catch((cause: unknown) => {\n throw new Error(`pagegraph could not evaluate ${path}: ${messageOf(cause)}`, { cause });\n });\n const exported = module[\"collections\"];\n if (exported === undefined) throw new Error(`${path} has no \\`collections\\` export`);\n const collections: unknown = typeof exported === \"function\" ? await (exported as () => unknown)() : exported;\n if (!Array.isArray(collections)) throw new Error(`${path} \\`collections\\` must be an array or return one`);\n return collections as ReadonlyArray<SeoCollection>;\n};\n\n/** What one evaluation loads: the scanned route tree and the modules that feed the graph. */\nexport interface GraphPlan {\n readonly root: string;\n readonly options: PagegraphOptions;\n readonly scan: ScannedRoutes;\n /** Absolute paths of the route files that declare `staticData`, plus `__root`. */\n readonly routeFiles: ReadonlyArray<string>;\n /** Absolute path of the collections module, when configured. */\n readonly collectionsFile: string | undefined;\n readonly factsFile: string | undefined;\n}\n\n/** Scan the routes directory and pick the modules an evaluation loads. */\nexport async function planGraph(root: string, options: PagegraphOptions): Promise<GraphPlan> {\n const scan = await scanRoutes(root, options.routesDirectory ?? \"src/routes\");\n const excluded = (options.exclude ?? []).map(globToRegExp);\n const participates = (node: RouteNode): boolean =>\n GRAPH_ROUTE_TYPES.has(node._fsRouteType) &&\n (node._fsRouteType === \"__root\" || node.createFileRouteProps?.has(\"staticData\") === true) &&\n !excluded.some((pattern) => pattern.test(node.filePath));\n return {\n root,\n options,\n scan,\n routeFiles: [scan.root, ...scan.nodes].filter(participates).map((node) => node.fullPath),\n collectionsFile: options.collections === undefined ? undefined : resolve(root, options.collections),\n factsFile: options.facts === undefined ? undefined : resolve(root, options.facts),\n };\n}\n\n/**\n * A route node's path relative to its parent, as the router matches it: route\n * groups, pathless layouts, and trailing underscores contribute no segment.\n */\nconst publicSegment = (node: RouteNode): string | undefined =>\n node.isNonPath === true || node.cleanedPath === undefined || node.cleanedPath === \"\" ? undefined : node.cleanedPath;\n\n/** Evaluate a plan's modules through `runner`, then build the graph and the site runtime. */\nexport async function evaluateGraph(runner: GraphModuleRunner, plan: GraphPlan): Promise<AppGraph> {\n const { root, options, scan } = plan;\n const staticData = new Map(\n await Promise.all(\n plan.routeFiles.map(async (fullPath) => {\n const file = relative(root, fullPath);\n const routeFile = relative(resolve(root, options.routesDirectory ?? \"src/routes\"), fullPath);\n const module = await runner.import(fullPath).catch((cause: unknown) => {\n throw new Error(\n `pagegraph could not evaluate ${file} in the graph environment: ${messageOf(cause)}\\n` +\n `If it is an app surface rather than a public page, add \"${routeFile}\" to pagegraph({ exclude }).`,\n { cause },\n );\n });\n return [fullPath, routeStaticData(module, file)] as const;\n }),\n ),\n );\n\n const walkable = (node: RouteNode): SeoRouteNode => ({\n options: { path: publicSegment(node), staticData: staticData.get(node.fullPath) },\n children: (node.children ?? []).map(walkable),\n });\n const routeTree: SeoRouteNode = {\n options: { staticData: staticData.get(scan.root.fullPath) },\n children: scan.tree.map(walkable),\n };\n\n const collections =\n plan.collectionsFile === undefined ? [] : await loadCollections(runner, root, plan.collectionsFile);\n const graph = buildSeoGraph({ routeTree, collections, origin: options.origin, ogImage: options.ogImage });\n\n const exclusions =\n options.routeConfig === undefined\n ? []\n : deriveRouteConfig(\n scan.nodes,\n resolveOptions({ ...options.routeConfig, routesDirectory: options.routesDirectory }),\n ).robotsExclusions;\n const site: SiteRuntime = {\n origin: options.origin,\n indexable: options.indexable ?? true,\n robots: {\n disallow: [...new Set([...exclusions, ...(options.robots?.disallow ?? [])])].sort(),\n contentSignal: options.robots?.contentSignal,\n directives: options.robots?.directives,\n },\n };\n let facts: Facts | undefined;\n if (plan.factsFile !== undefined) {\n const path = relative(root, plan.factsFile);\n try {\n const module = await runner.import(plan.factsFile);\n if (module[\"facts\"] === undefined) throw new Error(`${path} has no \\`facts\\` export`);\n facts = decodeFacts(module[\"facts\"]);\n } catch (cause) {\n throw new Error(`pagegraph could not evaluate ${path}: ${messageOf(cause)}`, { cause });\n }\n }\n return { graph, site, markdown: options.markdown === undefined ? null : { origin: options.markdown.origin }, facts };\n}\n","import * as Schema from \"effect/Schema\";\n\nimport type { InlineNode as InlineNodeType, PageDocument as PageDocumentType } from \"./document\";\n\nconst InlineNode: Schema.Codec<InlineNodeType> = Schema.suspend(() => Schema.Union([\n Schema.String,\n Schema.Struct({ fact: Schema.String, text: Schema.String }),\n Schema.Struct({\n tag: Schema.Literals([\"em\", \"strong\", \"a\", \"code\", \"br\"]),\n href: Schema.optionalKey(Schema.String),\n children: Schema.Array(InlineNode),\n }),\n]));\nconst Message = Schema.Struct({\n hash: Schema.String, tree: Schema.Array(InlineNode), text: Schema.String,\n source: Schema.String, facts: Schema.Array(Schema.String),\n audience: Schema.Literals([\"all\", \"agents\", \"humans\"]),\n});\nconst BodyEntry = Schema.Union([\n Schema.Struct({ type: Schema.Literal(\"message\"), message: Message }),\n Schema.Struct({ type: Schema.Literal(\"visual\"), text: Schema.String, source: Schema.String,\n audience: Schema.Literals([\"all\", \"agents\", \"humans\"]) }),\n]);\nexport const PageDocument: Schema.Codec<PageDocumentType> = Schema.Struct({\n path: Schema.String, hash: Schema.String, title: Schema.String, description: Schema.String,\n sections: Schema.Array(Schema.Struct({\n claimsMarkdown: Schema.optionalKey(Schema.String), evidence: Schema.optionalKey(Schema.Boolean),\n kind: Schema.String, id: Schema.String, title: Schema.optionalKey(Message),\n language: Schema.optionalKey(Schema.String), body: Schema.Array(BodyEntry),\n items: Schema.Array(Schema.Struct({\n title: Schema.optionalKey(Message), href: Schema.optionalKey(Schema.String),\n body: Schema.Array(BodyEntry), cells: Schema.Array(Message), source: Schema.String,\n audience: Schema.Literals([\"all\", \"agents\", \"humans\"]),\n })), source: Schema.String, audience: Schema.Literals([\"all\", \"agents\", \"humans\"]),\n })), messages: Schema.Array(Message), facts: Schema.Array(Schema.String),\n});\nexport const CaptureBundle = Schema.Struct({\n origin: Schema.String,\n documents: Schema.Array(PageDocument),\n heads: Schema.Array(Schema.Struct({ path: Schema.String, title: Schema.String, description: Schema.String })),\n});\n","import { mkdir, readdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { replayClaims, type ClaimsOptions } from \"../claims\";\nimport type { Facts } from \"../markdown/facts\";\n\nimport type { PageDocument } from \"../markdown/document\";\nimport { hashDocument } from \"../markdown/document\";\nimport { CaptureBundle, PageDocument as PageDocumentSchema } from \"../markdown/schema\";\nimport { documentMarkdown } from \"../markdown/markdown\";\n\nimport { MARKDOWN_CAPTURE_PATH } from \"./markdown-path\";\n\n/** File names must remain inside the package's capture and the client's output trees. */\nfunction documentName(path: string): string {\n if (!path.startsWith(\"/\") || path.startsWith(\"//\") || /[?#\\\\$\\u0000-\\u001f\\u007f]/.test(path) ||\n (path !== \"/\" && path.endsWith(\"/\")) || path.slice(1).split(\"/\").some((part) => part === \".\" || part === \"..\" || part === \"*\" || (part === \"\" && path !== \"/\"))) {\n throw new Error(`Invalid captured document path \"${path}\"`);\n }\n return path === \"/\" ? \"index\" : path.slice(1);\n}\n\nfunction verifyDocument(document: PageDocument): string {\n const name = documentName(document.path);\n const { hash, ...content } = document;\n if (!/^[0-9a-f]{64}$/.test(hash) || hashDocument(content) !== hash)\n throw new Error(`Invalid captured document hash for \"${document.path}\"`);\n return name;\n}\n\n/** Only valid captures at their derived paths belong to pagegraph; preserve unrelated files. */\nasync function pruneDocuments(directory: string, paths: ReadonlySet<string>): Promise<void> {\n const visit = async (folder: string): Promise<void> => {\n const entries = await readdir(folder, { withFileTypes: true });\n for (const entry of entries) {\n const file = resolve(folder, entry.name);\n if (entry.isDirectory()) { await visit(file); continue; }\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue;\n const contents = await readFile(file, \"utf8\");\n let document: PageDocument;\n let name: string;\n try {\n document = Schema.decodeUnknownSync(Schema.fromJsonString(PageDocumentSchema))(contents, { onExcessProperty: \"error\" });\n name = verifyDocument(document);\n } catch { continue; }\n if (file === resolve(directory, `${name}.json`) && !paths.has(document.path)) await unlink(file);\n }\n };\n await visit(directory);\n}\n\n/** Documents, twins, and graph heads are persisted before prerender onSuccess returns. */\nexport async function persistMarkdownCapture(\n html: string, root: string, clientOutDir: string, claims?: ClaimsOptions, facts: Facts = {},\n): Promise<void> {\n const bundle = Schema.decodeUnknownSync(Schema.fromJsonString(CaptureBundle))(html, { onExcessProperty: \"error\" });\n const origin = new URL(bundle.origin);\n if ((origin.protocol !== \"https:\" && origin.protocol !== \"http:\") || origin.origin !== bundle.origin)\n throw new Error(`Invalid captured markdown origin \"${bundle.origin}\"`);\n const active = new Set<string>();\n const destinations = new Set<string>();\n const names = bundle.documents.map((document) => {\n if (active.has(document.path)) throw new Error(`Duplicate captured document path \"${document.path}\"`);\n active.add(document.path);\n const name = verifyDocument(document);\n if (destinations.has(name)) throw new Error(`Captured document paths share the output name \"${name}\"`);\n destinations.add(name);\n return name;\n });\n await unlink(resolve(clientOutDir, MARKDOWN_CAPTURE_PATH.slice(1)));\n const directory = resolve(root, \".pagegraph/documents\");\n await mkdir(directory, { recursive: true });\n for (const [index, document] of bundle.documents.entries()) {\n const name = names[index];\n const destination = resolve(directory, `${name}.json`);\n const twin = resolve(clientOutDir, `${name}.md`);\n await mkdir(dirname(destination), { recursive: true });\n await writeFile(destination, JSON.stringify(document));\n await mkdir(dirname(twin), { recursive: true });\n await writeFile(twin, documentMarkdown(document, bundle.origin));\n }\n await pruneDocuments(directory, active);\n await writeFile(resolve(root, \".pagegraph/heads.json\"), `${JSON.stringify(bundle.heads)}\\n`);\n if (claims !== undefined)\n await Effect.runPromise(replayClaims(root, bundle.documents, bundle.heads, facts, claims));\n}\n","import { relative, resolve, sep } from \"node:path\";\n\nimport * as Effect from \"effect/Effect\";\nimport * as Schema from \"effect/Schema\";\n\nimport { parseAst } from \"vite\";\n\ntype Program = ReturnType<typeof parseAst>;\ntype Expression = Extract<Program[\"body\"][number], { type: \"ExpressionStatement\" }>[\"expression\"];\ntype JSXOpeningElement = Extract<Expression, { type: \"JSXElement\" }>[\"openingElement\"];\n\nclass DocumentSourceError extends Schema.TaggedError<DocumentSourceError>()(\"DocumentSourceError\", {\n message: Schema.String,\n}) {}\n\nconst SOURCE_COMPONENTS = new Set([\"T\", \"Title\", \"Section\", \"Visual\", \"ForAgents\", \"ForHumans\"]);\n\nconst parse = Effect.fn(\"DocumentSource.parse\")(function* (code: string, id: string) {\n return yield* Effect.try({\n try: () => parseAst(code, { lang: id.endsWith(\".tsx\") ? \"tsx\" : id.endsWith(\".ts\") ? \"ts\" : id.endsWith(\".jsx\") ? \"jsx\" : \"js\" }, id),\n catch: (cause) => new DocumentSourceError({\n message: `Cannot parse document source ${id}: ${cause instanceof Error ? cause.message : String(cause)}`,\n }),\n });\n});\n\nfunction componentNames(program: Program): {\n named: Set<string>;\n sections: Set<string>;\n namespaces: Set<string>;\n} {\n const named = new Set<string>();\n const sections = new Set<string>();\n const namespaces = new Set<string>();\n for (const node of program.body) {\n if (\n node.type !== \"ImportDeclaration\" ||\n node.source.value !== \"pagegraph/react\" ||\n node.importKind === \"type\"\n )\n continue;\n for (const specifier of node.specifiers) {\n if (specifier.type === \"ImportSpecifier\") {\n if (specifier.importKind === \"type\") continue;\n const imported =\n specifier.imported.type === \"Identifier\"\n ? specifier.imported.name\n : specifier.imported.value;\n if (SOURCE_COMPONENTS.has(imported)) named.add(specifier.local.name);\n if (imported === \"Section\") sections.add(specifier.local.name);\n } else if (specifier.type === \"ImportNamespaceSpecifier\") {\n namespaces.add(specifier.local.name);\n }\n }\n }\n return { named, sections, namespaces };\n}\n\nfunction memberParts(name: JSXOpeningElement[\"name\"]): Array<string> {\n if (name.type === \"JSXIdentifier\") return [name.name];\n if (name.type !== \"JSXMemberExpression\") return [];\n return [...memberParts(name.object), name.property.name];\n}\n\nfunction isSourceComponent(\n name: JSXOpeningElement[\"name\"],\n names: ReturnType<typeof componentNames>,\n): boolean {\n const parts = memberParts(name);\n const [base, ...members] = parts;\n if (base === undefined) return false;\n if (members.length === 0) return names.named.has(base);\n if (names.namespaces.has(base))\n return (\n (members.length === 1 && members[0] !== undefined && SOURCE_COMPONENTS.has(members[0])) ||\n (members.length === 2 && members[0] === \"Section\" && members[1] === \"Item\")\n );\n return names.sections.has(base) && members.length === 1 && members[0] === \"Item\";\n}\n\nfunction walk(value: unknown, visit: (node: JSXOpeningElement) => void): void {\n if (Array.isArray(value)) {\n for (const child of value) walk(child, visit);\n } else if (typeof value === \"object\" && value !== null) {\n if (isOpeningElement(value)) visit(value);\n for (const child of Object.values(value)) walk(child, visit);\n }\n}\n\nfunction isOpeningElement(node: object): node is JSXOpeningElement {\n return \"type\" in node && node.type === \"JSXOpeningElement\";\n}\n\nexport function transformDocumentSource(\n code: string,\n id: string,\n root: string,\n): { code: string } | undefined {\n // oxlint-disable-next-line eslint/no-restricted-properties -- Vite's document transform exposes a synchronous source API.\n const program = Effect.runSync(parse(code, id));\n const names = componentNames(program);\n const edits: Array<{ start: number; end: number; value: string }> = [];\n const file = relative(resolve(root), resolve(id)).split(sep).join(\"/\");\n\n walk(program, (node) => {\n if (!isSourceComponent(node.name, names)) return;\n if (\n node.attributes.some(\n (attribute) =>\n attribute.type === \"JSXAttribute\" &&\n attribute.name.type === \"JSXIdentifier\" &&\n attribute.name.name === \"source\",\n )\n )\n return;\n const line = code.slice(0, node.start).split(\"\\n\").length;\n // Generated defaults precede attributes so authored and spread props retain precedence.\n edits.push({\n start: node.name.end,\n end: node.name.end,\n value: ` source=${JSON.stringify(`${file}:${line}`)}`,\n });\n });\n\n if (edits.length === 0) return undefined;\n edits.sort((a, b) => b.start - a.start);\n let transformed = code;\n for (const edit of edits)\n transformed = transformed.slice(0, edit.start) + edit.value + transformed.slice(edit.end);\n return { code: transformed };\n}\n","/**\n * The `pagegraph()` Vite plugin for TanStack Start.\n *\n * The graph is built once per build (and on change in dev) inside the app's own\n * Vite pipeline, then shipped to the server runtime as `virtual:pagegraph/runtime`.\n * The Worker never rebuilds it and never imports build code.\n *\n * Route modules and content collections are evaluated in a dedicated Node\n * environment, `pagegraph`, that shares the app's plugins, aliases, and\n * `define`s. Two settings make that safe:\n * - its `resolve.noExternal` is cleared, so TanStack Start's packages load from\n * node_modules, where their virtual router entry is an inert stub. Bundled (as\n * the app's server bundles them), they would pull in the whole route tree.\n * - only route files that declare `staticData` are evaluated, one by one, never\n * `routeTree.gen.ts`.\n *\n * Dev imports those modules through the environment's module runner and\n * re-evaluates after a change reaches it. A build builds the environment first\n * (a `buildApp` hook that runs before the framework's) with the build's own\n * config and plugins, then imports the output. The CLI and `loadPageHeads` use\n * {@link evaluateAppGraph}, which runs the dev path without a server.\n */\n\nimport { join, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport {\n createRunnableDevEnvironment,\n type BuildEnvironment,\n type Plugin,\n type RunnableDevEnvironment,\n type ViteBuilder,\n type ViteDevServer,\n} from \"vite\";\n\nimport { graphToJson } from \"../core/wire\";\nimport { seoRouteConfig } from \"../vite/route-config\";\nimport { evaluateGraph, planGraph, type AppGraph, type GraphModuleRunner, type GraphPlan, type PagegraphOptions } from \"./graph\";\nimport { MARKDOWN_CAPTURE_PATH } from \"./markdown-path\";\nimport { persistMarkdownCapture } from \"./persist-markdown\";\nimport { transformDocumentSource } from \"./source\";\n\nexport const GRAPH_ENVIRONMENT = \"pagegraph\";\nexport const PLUGIN_NAME = \"pagegraph\";\nconst RUNTIME_ID = \"virtual:pagegraph/runtime\";\nconst RESOLVED_RUNTIME_ID = `\\0${RUNTIME_ID}`;\nconst ENTRY_ID = \"virtual:pagegraph/entry\";\nconst RESOLVED_ENTRY_ID = `\\0${ENTRY_ID}`;\n/** The runtime entry imports the virtual module, so dep pre-bundling must leave it to this plugin. */\nconst RUNTIME_ENTRIES = [\"pagegraph/tanstack-start/server\", \"pagegraph/tanstack-start/markdown\", \"pagegraph/tanstack-start/react\"];\n/** Where a build writes the graph environment's output, relative to the Vite root. */\nconst BUILD_DIRECTORY = \"node_modules/.cache/pagegraph/build\";\n/**\n * Runtime-provided modules (`cloudflare:workers`, `bun:sqlite`) that no plugin\n * resolves stay external in the graph environment, so an unexcluded route that\n * imports one fails at evaluation with its file name rather than as an\n * unresolved import. Plugin-owned schemes (`fumadocs-mdx:`) resolve first.\n */\nconst RUNTIME_SCHEME = /^(?!node:|data:|file:|virtual:)[a-z][a-z0-9+.-]*:/;\n\nconst runtimeModulesPlugin: Plugin = {\n name: \"pagegraph:runtime-modules\",\n enforce: \"post\",\n applyToEnvironment: (environment) => environment.name === GRAPH_ENVIRONMENT,\n resolveId: {\n filter: { id: RUNTIME_SCHEME },\n handler: (id) => ({ id, external: true }),\n },\n};\n\n/** What the plugin exposes to {@link evaluateAppGraph} through `plugin.api`. */\nexport interface PagegraphPluginApi {\n readonly options: PagegraphOptions;\n}\n\nconst runtimeModule = ({ graph, site, markdown, facts }: AppGraph): string =>\n `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`;\n\n/** The graph environment's build entry: a loader per module the plan evaluates. */\nconst entryModule = (plan: GraphPlan): string => {\n const files = [...plan.routeFiles, ...(plan.collectionsFile === undefined ? [] : [plan.collectionsFile]), ...(plan.factsFile === undefined ? [] : [plan.factsFile])];\n const loaders = files.map((file) => ` ${JSON.stringify(file)}: () => import(${JSON.stringify(file)}),`);\n return `export const modules = {\\n${loaders.join(\"\\n\")}\\n};\\n`;\n};\n\n/** Build the graph environment with the build's config, import its output, and evaluate. */\nconst buildGraph = async (builder: ViteBuilder, environment: BuildEnvironment, plan: GraphPlan): Promise<AppGraph> => {\n await builder.build(environment);\n const entry = join(resolve(environment.config.root, environment.config.build.outDir), \"entry.mjs\");\n const { modules } = (await import(pathToFileURL(entry).href)) as {\n modules: Record<string, () => Promise<Record<string, unknown>>>;\n };\n const runner: GraphModuleRunner = {\n import: (file) => {\n const load = modules[file];\n if (load === undefined) throw new Error(`${file} is not in the graph build`);\n return load();\n },\n };\n return evaluateGraph(runner, plan);\n};\n\n/** Start prerenders this private endpoint once; its callback writes public Markdown twins. */\nexport interface MarkdownPrerenderPage {\n readonly path: string;\n readonly sitemap: { readonly exclude: true };\n readonly prerender: { readonly onSuccess: (result: { html: string }) => Promise<void> };\n}\nexport type PagegraphPlugins = Array<Plugin> & { readonly prerenderPages: Array<MarkdownPrerenderPage> };\n\nexport function pagegraph(options: PagegraphOptions): PagegraphPlugins {\n if (options.claims !== undefined && options.markdown === undefined)\n throw new Error(\"Claims require pagegraph({ markdown: { origin }, claims: { rules, model } }) so the build captures documents.\");\n const api: PagegraphPluginApi = { options };\n let root = process.cwd();\n let clientOutDir = resolve(root, \"dist/client\");\n let server: ViteDevServer | undefined;\n let devEnvironment: RunnableDevEnvironment | undefined;\n /** The plan the graph environment's build entry is generated from. */\n let buildPlan: GraphPlan | undefined;\n let current: Promise<AppGraph> | undefined;\n /** Bumped by every change that reaches the graph; an evaluation that spans one is discarded. */\n let generation = 0;\n\n const evaluateInDev = async (environment: RunnableDevEnvironment): Promise<AppGraph> => {\n for (;;) {\n const started = generation;\n // Re-evaluate from scratch: content and route modules may have changed.\n environment.runner.clearCache();\n const result = await evaluateGraph(environment.runner, await planGraph(root, options));\n if (started === generation) return result;\n }\n };\n\n const graphPlugin: Plugin & { api: PagegraphPluginApi } = {\n name: PLUGIN_NAME,\n api,\n // One instance across a build's environments: the graph built in buildApp\n // is the one every environment's runtime module serves.\n sharedDuringBuild: true,\n config() {\n return {\n environments: {\n [GRAPH_ENVIRONMENT]: {\n consumer: \"server\",\n dev: {\n createEnvironment: (name, environmentConfig) => createRunnableDevEnvironment(name, environmentConfig),\n },\n build: {\n outDir: BUILD_DIRECTORY,\n emptyOutDir: true,\n copyPublicDir: false,\n minify: false,\n rolldownOptions: {\n input: { entry: ENTRY_ID },\n output: { entryFileNames: \"[name].mjs\", chunkFileNames: \"chunks/[name]-[hash].mjs\" },\n },\n },\n },\n },\n };\n },\n configEnvironment(name, environmentConfig) {\n if (name === GRAPH_ENVIRONMENT) {\n environmentConfig.resolve ??= {};\n environmentConfig.resolve.noExternal = [];\n return;\n }\n environmentConfig.optimizeDeps ??= {};\n environmentConfig.optimizeDeps.exclude = [...(environmentConfig.optimizeDeps.exclude ?? []), ...RUNTIME_ENTRIES];\n },\n configResolved(resolved) {\n root = resolved.root;\n clientOutDir = resolve(root, resolved.environments.client?.build.outDir ?? \"dist/client\");\n },\n configureServer: {\n // Cloudflare inspects SSR exports in its server hook, which can import the graph runtime.\n order: \"pre\",\n handler(devServer) {\n server = devServer;\n devEnvironment = devServer.environments[GRAPH_ENVIRONMENT] as RunnableDevEnvironment | undefined;\n },\n },\n // Runs after Vite invalidates the changed file in each environment, so the\n // next evaluation reads fresh modules.\n hotUpdate({ file, modules }) {\n if (this.environment.name !== GRAPH_ENVIRONMENT) return;\n const routesDirectory = resolve(root, options.routesDirectory ?? \"src/routes\");\n if (modules.length === 0 && !file.startsWith(`${routesDirectory}/`)) return;\n generation += 1;\n current = undefined;\n for (const other of Object.values(server?.environments ?? {})) {\n if (other.name === GRAPH_ENVIRONMENT) continue;\n const runtime = other.moduleGraph.getModuleById(RESOLVED_RUNTIME_ID);\n if (runtime !== undefined) void other.reloadModule(runtime);\n }\n },\n buildApp: {\n order: \"pre\",\n async handler(builder) {\n const environment = builder.environments[GRAPH_ENVIRONMENT];\n if (environment === undefined) return;\n buildPlan = await planGraph(environment.config.root, options);\n const built = buildGraph(builder, environment, buildPlan);\n current = built;\n await built;\n // Vite builds every environment when a build's app builder builds none;\n // the graph environment must not count, or a plain app would skip the rest.\n environment.isBuilt = false;\n },\n },\n resolveId: {\n filter: { id: /^virtual:pagegraph\\/(runtime|entry)$/ },\n handler: (id) => `\\0${id}`,\n },\n load: {\n filter: { id: /^\\0virtual:pagegraph\\/(runtime|entry)$/ },\n async handler(id) {\n if (id === RESOLVED_ENTRY_ID) {\n if (buildPlan === undefined) throw new Error(`[${PLUGIN_NAME}] ${ENTRY_ID} is only built by the pagegraph buildApp hook`);\n return entryModule(buildPlan);\n }\n // A page evaluated for the graph may import the runtime; it never serves from it.\n if (this.environment.name === GRAPH_ENVIRONMENT) return \"export const graph = null;\\nexport const site = null;\\nexport const markdown = null;\\nexport const facts = undefined;\\n\";\n if (current === undefined) {\n if (devEnvironment === undefined) {\n throw new Error(\n `[${PLUGIN_NAME}] the graph was not built before ${this.environment.name} imported it; build with Vite's app builder (\\`vite build\\`).`,\n );\n }\n current = evaluateInDev(devEnvironment);\n }\n return runtimeModule(await current);\n },\n },\n };\n\n const markdownPlugin: Plugin = {\n name: \"pagegraph:markdown\",\n enforce: \"pre\",\n applyToEnvironment: (environment) => environment.name !== GRAPH_ENVIRONMENT,\n resolveId: {\n filter: { id: /^virtual:pagegraph\\/prerender-server$/ },\n handler: (id) => `\\0${id}`,\n },\n load: {\n filter: { id: /^\\0virtual:pagegraph\\/prerender-server$/ },\n handler: () => `export { default } from ${JSON.stringify(resolve(root, options.markdown?.serverEntry ?? \"dist/server/index.js\"))};`,\n },\n transform: {\n filter: { id: /\\.[jt]sx?(\\?|$)/, code: /pagegraph\\/react/ },\n handler(code, id) {\n const file = id.split(\"?\")[0];\n if (file === undefined || file.includes(\"/node_modules/\") || file.endsWith(\".gen.ts\") || !file.startsWith(`${root}/`)) return;\n return transformDocumentSource(code, file, root);\n },\n },\n configurePreviewServer(previewServer) {\n // Start reports status only; retain the route/capture diagnostic across the Worker boundary.\n previewServer.middlewares.use((_request, response, next) => {\n response.on(\"finish\", () => {\n const error = response.getHeader(\"x-pagegraph-error\");\n if (typeof error === \"string\") previewServer.config.logger.error(decodeURIComponent(error));\n });\n next();\n });\n },\n };\n\n return Object.assign([\n graphPlugin,\n markdownPlugin,\n runtimeModulesPlugin,\n ...(options.routeConfig === undefined\n ? []\n : [seoRouteConfig({ ...options.routeConfig, routesDirectory: options.routesDirectory })]),\n ], {\n prerenderPages: options.markdown === undefined ? [] : [{\n path: MARKDOWN_CAPTURE_PATH,\n sitemap: { exclude: true as const },\n prerender: { onSuccess: async ({ html }: { html: string }) => {\n if (current === undefined) throw new Error(\"The pagegraph graph must be evaluated before Markdown persistence.\");\n const app = await current;\n await persistMarkdownCapture(html, root, clientOutDir, options.claims, app.facts);\n } },\n }],\n });\n}\n","/**\n * Evaluate an app's graph outside a running dev server — for a build, the\n * CLI (`pagegraph.config.ts`), and build-time head loading — through the same\n * `pagegraph` environment the dev server uses.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { decodeFacts, type Facts } from \"../markdown/facts\";\n\nimport { createRunnableDevEnvironment, resolveConfig, type Plugin, type InlineConfig } from \"vite\";\n\nimport type { LoadedSeoGraph, SeoGraphLoader } from \"../config/vite-graph-loader\";\nimport { withCleanStdout } from \"../config/vite-graph-loader\";\nimport { evaluateGraph, planGraph, type AppGraph } from \"./graph\";\nimport type { PagegraphPluginApi } from \"./plugin\";\n\nconst GRAPH_ENVIRONMENT = \"pagegraph\";\n\nconst cliEnvironmentOverride = (command: \"serve\" | \"build\") =>\n command === \"build\" ? { [GRAPH_ENVIRONMENT]: { isBundled: false } } : undefined;\n\nexport interface EvaluateAppGraphOptions {\n /** Directory used to find the Vite config and as its default root. */\n readonly root: string;\n /** Vite command to evaluate. Graph evaluation defaults to `serve`. */\n readonly command?: \"serve\" | \"build\" | undefined;\n /** The app's Vite config file. Defaults to Vite's lookup from `root`. */\n readonly configFile?: string | undefined;\n /**\n * Vite mode the config is evaluated in. Origins and other build constants come\n * from it, so the CLI defaults to `production` to describe the deployed site.\n */\n readonly mode?: string | undefined;\n}\n\n/** Locate the config without overriding its authored root with an inline root. */\nconst appConfig = (options: EvaluateAppGraphOptions): InlineConfig => ({\n configFile: options.configFile ?? [\"js\", \"mjs\", \"ts\", \"cjs\", \"mts\", \"cts\"]\n .map((extension) => resolve(options.root, `vite.config.${extension}`)).find(existsSync) ?? false,\n logLevel: \"error\",\n plugins: [{\n name: \"pagegraph:loader-root\",\n enforce: \"pre\",\n config(config) { config.root ??= options.root; },\n }],\n});\n\nconst pluginApi = (plugins: ReadonlyArray<Plugin>, configFile: string | undefined): PagegraphPluginApi => {\n const plugin = plugins.find((candidate) => candidate.name === \"pagegraph\") as (Plugin & { api?: PagegraphPluginApi }) | undefined;\n if (plugin?.api === undefined) {\n throw new Error(`${configFile ?? \"The Vite config\"} does not register pagegraph() from \"pagegraph/tanstack-start\".`);\n }\n return plugin.api;\n};\n\n/** Resolve settings, artifact root and facts together without route discovery. */\nexport async function loadPagegraphSettings(options: EvaluateAppGraphOptions): Promise<NonNullable<LoadedSeoGraph[\"pagegraph\"]>> {\n const command = options.command ?? \"build\";\n return withCleanStdout(async () => {\n const config = await resolveConfig(\n {\n ...appConfig(options),\n mode: options.mode ?? \"production\",\n environments: cliEnvironmentOverride(command),\n },\n command,\n );\n const settings = pluginApi(config.plugins, config.configFile).options;\n let facts: Facts = {};\n if (settings.facts !== undefined) {\n const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });\n await environment.init();\n try {\n await environment.pluginContainer.buildStart({});\n const module = await environment.runner.import(resolve(config.root, settings.facts));\n facts = decodeFacts(module[\"facts\"]);\n } finally { await environment.close(); }\n }\n return { root: config.root, options: settings, facts };\n });\n}\n\n/**\n * Resolve the app's Vite config, evaluate the graph in its `pagegraph`\n * environment, and release it. The command defaults to `serve`; callers can\n * request `build` to match the deployed site configuration.\n */\nexport async function evaluateAppGraph(options: EvaluateAppGraphOptions): Promise<AppGraph & { readonly pagegraph: NonNullable<LoadedSeoGraph[\"pagegraph\"]> }> {\n const command = options.command ?? \"serve\";\n const config = await resolveConfig(\n {\n ...appConfig(options),\n mode: options.mode,\n environments: cliEnvironmentOverride(command),\n },\n command,\n );\n const api = pluginApi(config.plugins, config.configFile);\n const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });\n await environment.init();\n try {\n // Content plugins (Fumadocs, MDX) generate their virtual sources at buildStart.\n await environment.pluginContainer.buildStart({});\n const app = await evaluateGraph(environment.runner, await planGraph(config.root, api.options));\n return { ...app, pagegraph: { root: config.root, options: api.options, facts: app.facts ?? {} } };\n } finally {\n await environment.close();\n }\n}\n\n/**\n * A `pagegraph.config.ts` graph loader for a TanStack Start app. It uses the\n * built site's identity by default; pass `command: \"serve\"` for a dev host.\n *\n * ```ts\n * export default defineSeoConfig({\n * loadGraph: tanstackStartGraph({ root: import.meta.dirname }),\n * });\n * ```\n */\nexport const tanstackStartGraph =\n (options: EvaluateAppGraphOptions): SeoGraphLoader =>\n async (_input, environment): Promise<LoadedSeoGraph> => {\n const { graph, site, pagegraph } = await withCleanStdout(() =>\n evaluateAppGraph({\n ...options,\n command: environment?.command ?? options.command ?? \"build\",\n mode: environment?.mode ?? options.mode ?? \"production\",\n }),\n );\n return { graph, site, pagegraph, dispose: async () => {} };\n };\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as CaptureAnchor } from "../document-
|
|
1
|
+
import { t as CaptureAnchor } from "../document-D9P8yY5g.js";
|
|
2
2
|
import { LinkComponent } from "@tanstack/react-router";
|
|
3
3
|
//#region src/tanstack-start/react.d.ts
|
|
4
4
|
declare const Link: LinkComponent<typeof CaptureAnchor>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as SiteRuntime } from "../vite-graph-loader-
|
|
1
|
+
import { s as SeoGraph } from "../graph-ChrvU-6X.js";
|
|
2
|
+
import { a as SiteRuntime } from "../vite-graph-loader-BtLgkN0V.js";
|
|
3
3
|
//#region src/tanstack-start/server.d.ts
|
|
4
4
|
/** The deployment's SEO graph. */
|
|
5
5
|
declare function seoGraph(): SeoGraph;
|
|
@@ -52,7 +52,7 @@ function sitemapXml() {
|
|
|
52
52
|
function llmsSection() {
|
|
53
53
|
if (markdown === null) throw new Error("llmsSection requires pagegraph({ markdown: { origin } }) in vite.config.ts");
|
|
54
54
|
const pages = [...seoGraph().nodes.values()].filter((node) => !node.path.includes("$") && (node.markdown === "rendered" || node.llms !== void 0)).map((node) => {
|
|
55
|
-
if (node.head?.description === void 0) throw new Error(`Markdown page ${node.path} must declare a graph head with title and description for llms.txt`);
|
|
55
|
+
if (node.head?.title === void 0 || node.head.description === void 0) throw new Error(`Markdown page ${node.path} must declare a graph head with title and description for llms.txt`);
|
|
56
56
|
return {
|
|
57
57
|
document: {
|
|
58
58
|
path: node.path,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":["graphJson","siteRuntime"],"sources":["../../src/tanstack-start/server.ts"],"sourcesContent":["/**\n * `pagegraph/tanstack-start/server` — runtime. Serves the graph the\n * `pagegraph()` plugin built for this deployment:\n *\n * ```ts\n * // src/routes/robots[.]txt.ts\n * export const Route = createFileRoute(\"/robots.txt\")({ server: { handlers: { GET: robotsTxt } } });\n * ```\n */\n\nimport { graph as graphJson, site as siteRuntime, markdown } from \"virtual:pagegraph/runtime\";\n\nimport type { SeoGraph } from \"../core/graph\";\nimport { renderRobots, renderSitemap } from \"../core/projections\";\nimport { graphFromJson } from \"../core/wire\";\nimport type { SiteRuntime } from \"./graph\";\nimport { llmsMarkdown } from \"../markdown/llms\";\n\nlet decoded: SeoGraph | undefined;\n\nconst unavailable = (): Error =>\n new Error(\n \"pagegraph/tanstack-start/server has no graph: register pagegraph() from \\\"pagegraph/tanstack-start\\\" in vite.config.ts. \" +\n \"(Inside the graph environment itself, the graph is still being built.)\",\n );\n\n/** The deployment's SEO graph. */\nexport function seoGraph(): SeoGraph {\n if (graphJson === null) throw unavailable();\n decoded ??= graphFromJson(graphJson);\n return decoded;\n}\n\n/** The deployment's origin, indexability, and robots policy. */\nexport function seoSite(): SiteRuntime {\n if (siteRuntime === null) throw unavailable();\n return siteRuntime;\n}\n\n/** `GET /robots.txt`: the robots policy on an indexable host, disallow-all otherwise. */\nexport function robotsTxt(): Response {\n const site = seoSite();\n return new Response(renderRobots(seoGraph(), { origin: site.origin, indexable: site.indexable, ...site.robots }), {\n headers: {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Cache-Control\": site.indexable ? \"public, max-age=86400\" : \"public, max-age=3600\",\n },\n });\n}\n\n/** `GET /sitemap.xml`. */\nexport function sitemapXml(): Response {\n const site = seoSite();\n return new Response(renderSitemap(seoGraph(), { origin: site.origin, indexable: site.indexable }), {\n headers: {\n \"Content-Type\": \"application/xml; charset=utf-8\",\n \"Cache-Control\": \"public, max-age=3600\",\n },\n });\n}\n\n/** Generated llms.txt groups, using graph metadata without rendering pages. */\nexport function llmsSection(): string {\n if (markdown === null)\n throw new Error('llmsSection requires pagegraph({ markdown: { origin } }) in vite.config.ts');\n const pages = [...seoGraph().nodes.values()]\n .filter((node) => !node.path.includes(\"$\") && (node.markdown === \"rendered\" || node.llms !== undefined))\n .map((node) => {\n if (node.head?.description === undefined)\n throw new Error(`Markdown page ${node.path} must declare a graph head with title and description for llms.txt`);\n return {\n document: { path: node.path, title: node.head.title, description: node.head.description },\n ...(node.llms === undefined ? {} : { llms: node.llms }),\n };\n });\n return llmsMarkdown(pages, markdown.origin);\n}\n\n/** `GET /llms.txt`: graph-derived links to rendered and explicitly grouped Markdown pages. */\nexport function llmsTxt(options: { sections?: ReadonlyArray<string> } = {}): () => Response {\n return () => new Response(\n [llmsSection().trimEnd(), ...(options.sections ?? []).map((section) => section.trim())]\n .filter(Boolean).join(\"\\n\\n\") + \"\\n\",\n { headers: { \"Content-Type\": \"text/plain; charset=utf-8\", \"Cache-Control\": \"public, max-age=3600\" } },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,IAAI;AAEJ,MAAM,oCACJ,IAAI,MACF,gMAEF;;AAGF,SAAgB,WAAqB;CACnC,IAAIA,UAAc,MAAM,MAAM,YAAY;CAC1C,YAAY,cAAcA,KAAS;CACnC,OAAO;AACT;;AAGA,SAAgB,UAAuB;CACrC,IAAIC,SAAgB,MAAM,MAAM,YAAY;CAC5C,OAAOA;AACT;;AAGA,SAAgB,YAAsB;CACpC,MAAM,OAAO,QAAQ;CACrB,OAAO,IAAI,SAAS,aAAa,SAAS,GAAG;EAAE,QAAQ,KAAK;EAAQ,WAAW,KAAK;EAAW,GAAG,KAAK;CAAO,CAAC,GAAG,EAChH,SAAS;EACP,gBAAgB;EAChB,iBAAiB,KAAK,YAAY,0BAA0B;CAC9D,EACF,CAAC;AACH;;AAGA,SAAgB,aAAuB;CACrC,MAAM,OAAO,QAAQ;CACrB,OAAO,IAAI,SAAS,cAAc,SAAS,GAAG;EAAE,QAAQ,KAAK;EAAQ,WAAW,KAAK;CAAU,CAAC,GAAG,EACjG,SAAS;EACP,gBAAgB;EAChB,iBAAiB;CACnB,EACF,CAAC;AACH;;AAGA,SAAgB,cAAsB;CACpC,IAAI,aAAa,MACf,MAAM,IAAI,MAAM,4EAA4E;CAC9F,MAAM,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,CACzC,QAAQ,SAAS,CAAC,KAAK,KAAK,SAAS,GAAG,MAAM,KAAK,aAAa,cAAc,KAAK,SAAS,KAAA,EAAU,CAAC,CACvG,KAAK,SAAS;EACb,IAAI,KAAK,MAAM,gBAAgB,KAAA,
|
|
1
|
+
{"version":3,"file":"server.js","names":["graphJson","siteRuntime"],"sources":["../../src/tanstack-start/server.ts"],"sourcesContent":["/**\n * `pagegraph/tanstack-start/server` — runtime. Serves the graph the\n * `pagegraph()` plugin built for this deployment:\n *\n * ```ts\n * // src/routes/robots[.]txt.ts\n * export const Route = createFileRoute(\"/robots.txt\")({ server: { handlers: { GET: robotsTxt } } });\n * ```\n */\n\nimport { graph as graphJson, site as siteRuntime, markdown } from \"virtual:pagegraph/runtime\";\n\nimport type { SeoGraph } from \"../core/graph\";\nimport { renderRobots, renderSitemap } from \"../core/projections\";\nimport { graphFromJson } from \"../core/wire\";\nimport type { SiteRuntime } from \"./graph\";\nimport { llmsMarkdown } from \"../markdown/llms\";\n\nlet decoded: SeoGraph | undefined;\n\nconst unavailable = (): Error =>\n new Error(\n \"pagegraph/tanstack-start/server has no graph: register pagegraph() from \\\"pagegraph/tanstack-start\\\" in vite.config.ts. \" +\n \"(Inside the graph environment itself, the graph is still being built.)\",\n );\n\n/** The deployment's SEO graph. */\nexport function seoGraph(): SeoGraph {\n if (graphJson === null) throw unavailable();\n decoded ??= graphFromJson(graphJson);\n return decoded;\n}\n\n/** The deployment's origin, indexability, and robots policy. */\nexport function seoSite(): SiteRuntime {\n if (siteRuntime === null) throw unavailable();\n return siteRuntime;\n}\n\n/** `GET /robots.txt`: the robots policy on an indexable host, disallow-all otherwise. */\nexport function robotsTxt(): Response {\n const site = seoSite();\n return new Response(renderRobots(seoGraph(), { origin: site.origin, indexable: site.indexable, ...site.robots }), {\n headers: {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n \"Cache-Control\": site.indexable ? \"public, max-age=86400\" : \"public, max-age=3600\",\n },\n });\n}\n\n/** `GET /sitemap.xml`. */\nexport function sitemapXml(): Response {\n const site = seoSite();\n return new Response(renderSitemap(seoGraph(), { origin: site.origin, indexable: site.indexable }), {\n headers: {\n \"Content-Type\": \"application/xml; charset=utf-8\",\n \"Cache-Control\": \"public, max-age=3600\",\n },\n });\n}\n\n/** Generated llms.txt groups, using graph metadata without rendering pages. */\nexport function llmsSection(): string {\n if (markdown === null)\n throw new Error('llmsSection requires pagegraph({ markdown: { origin } }) in vite.config.ts');\n const pages = [...seoGraph().nodes.values()]\n .filter((node) => !node.path.includes(\"$\") && (node.markdown === \"rendered\" || node.llms !== undefined))\n .map((node) => {\n if (node.head?.title === undefined || node.head.description === undefined)\n throw new Error(`Markdown page ${node.path} must declare a graph head with title and description for llms.txt`);\n return {\n document: { path: node.path, title: node.head.title, description: node.head.description },\n ...(node.llms === undefined ? {} : { llms: node.llms }),\n };\n });\n return llmsMarkdown(pages, markdown.origin);\n}\n\n/** `GET /llms.txt`: graph-derived links to rendered and explicitly grouped Markdown pages. */\nexport function llmsTxt(options: { sections?: ReadonlyArray<string> } = {}): () => Response {\n return () => new Response(\n [llmsSection().trimEnd(), ...(options.sections ?? []).map((section) => section.trim())]\n .filter(Boolean).join(\"\\n\\n\") + \"\\n\",\n { headers: { \"Content-Type\": \"text/plain; charset=utf-8\", \"Cache-Control\": \"public, max-age=3600\" } },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,IAAI;AAEJ,MAAM,oCACJ,IAAI,MACF,gMAEF;;AAGF,SAAgB,WAAqB;CACnC,IAAIA,UAAc,MAAM,MAAM,YAAY;CAC1C,YAAY,cAAcA,KAAS;CACnC,OAAO;AACT;;AAGA,SAAgB,UAAuB;CACrC,IAAIC,SAAgB,MAAM,MAAM,YAAY;CAC5C,OAAOA;AACT;;AAGA,SAAgB,YAAsB;CACpC,MAAM,OAAO,QAAQ;CACrB,OAAO,IAAI,SAAS,aAAa,SAAS,GAAG;EAAE,QAAQ,KAAK;EAAQ,WAAW,KAAK;EAAW,GAAG,KAAK;CAAO,CAAC,GAAG,EAChH,SAAS;EACP,gBAAgB;EAChB,iBAAiB,KAAK,YAAY,0BAA0B;CAC9D,EACF,CAAC;AACH;;AAGA,SAAgB,aAAuB;CACrC,MAAM,OAAO,QAAQ;CACrB,OAAO,IAAI,SAAS,cAAc,SAAS,GAAG;EAAE,QAAQ,KAAK;EAAQ,WAAW,KAAK;CAAU,CAAC,GAAG,EACjG,SAAS;EACP,gBAAgB;EAChB,iBAAiB;CACnB,EACF,CAAC;AACH;;AAGA,SAAgB,cAAsB;CACpC,IAAI,aAAa,MACf,MAAM,IAAI,MAAM,4EAA4E;CAC9F,MAAM,QAAQ,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,CACzC,QAAQ,SAAS,CAAC,KAAK,KAAK,SAAS,GAAG,MAAM,KAAK,aAAa,cAAc,KAAK,SAAS,KAAA,EAAU,CAAC,CACvG,KAAK,SAAS;EACb,IAAI,KAAK,MAAM,UAAU,KAAA,KAAa,KAAK,KAAK,gBAAgB,KAAA,GAC9D,MAAM,IAAI,MAAM,iBAAiB,KAAK,KAAK,mEAAmE;EAChH,OAAO;GACL,UAAU;IAAE,MAAM,KAAK;IAAM,OAAO,KAAK,KAAK;IAAO,aAAa,KAAK,KAAK;GAAY;GACxF,GAAI,KAAK,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK;EACvD;CACF,CAAC;CACH,OAAO,aAAa,OAAO,SAAS,MAAM;AAC5C;;AAGA,SAAgB,QAAQ,UAAgD,CAAC,GAAmB;CAC1F,aAAa,IAAI,SACf,CAAC,YAAY,CAAC,CAAC,QAAQ,GAAG,IAAI,QAAQ,YAAY,CAAC,EAAA,CAAG,KAAK,YAAY,QAAQ,KAAK,CAAC,CAAC,CAAC,CACpF,OAAO,OAAO,CAAC,CAAC,KAAK,MAAM,IAAI,MAClC,EAAE,SAAS;EAAE,gBAAgB;EAA6B,iBAAiB;CAAuB,EAAE,CACtG;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ClaimsOptions } from "./claims-
|
|
3
|
-
import { o as Facts } from "./page-heads-
|
|
4
|
-
import { r as RobotsConfig } from "./projections-
|
|
1
|
+
import { r as OgImageResolver, s as SeoGraph } from "./graph-ChrvU-6X.js";
|
|
2
|
+
import { n as ClaimsOptions } from "./claims-CCf2_Jox.js";
|
|
3
|
+
import { o as Facts } from "./page-heads-DDz_2_hq.js";
|
|
4
|
+
import { r as RobotsConfig } from "./projections-Dqo_ih-J.js";
|
|
5
5
|
import { t as SeoRouteConfigOptions } from "./route-config-wneg6ktn.js";
|
|
6
6
|
import "@tanstack/router-generator";
|
|
7
7
|
import { PluginOption } from "vite";
|
|
@@ -20,6 +20,8 @@ interface AppGraph {
|
|
|
20
20
|
interface PagegraphOptions {
|
|
21
21
|
/** This build's canonical origin, no trailing slash, e.g. `https://example.com`. */
|
|
22
22
|
readonly origin: string;
|
|
23
|
+
/** Fallback social image; use the same resolver in createSeo. */
|
|
24
|
+
readonly ogImage?: OgImageResolver | undefined;
|
|
23
25
|
/**
|
|
24
26
|
* Whether this build's host may be indexed. A non-indexable build serves a
|
|
25
27
|
* disallow-all robots.txt with no Sitemap line. Defaults to `true`.
|
|
@@ -130,4 +132,4 @@ interface ViteGraphLoaderOptions {
|
|
|
130
132
|
declare const viteGraphLoader: <Input extends GraphLoaderInput | void = void>(options: ViteGraphLoaderOptions) => SeoGraphLoader<Input>;
|
|
131
133
|
//#endregion
|
|
132
134
|
export { SiteRuntime as a, AppGraph as c, SeoGraphLoader as i, PagegraphOptions as l, GraphLoaderInput as n, ViteGraphLoaderOptions as o, LoadedSeoGraph as r, viteGraphLoader as s, GraphLoaderEnvironment as t, RobotsPolicy as u };
|
|
133
|
-
//# sourceMappingURL=vite-graph-loader-
|
|
135
|
+
//# sourceMappingURL=vite-graph-loader-BtLgkN0V.d.ts.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"checks-BS-aDXB_.js","names":[],"sources":["../src/core/freshness.ts","../src/core/checks.ts"],"sourcesContent":["/**\n * Freshness: how long ago each indexable page last changed, by the dates its\n * declaration carries ({@link lastModified}). Pure — the caller injects `now`,\n * so a test and the CLI read the same verdict from the same graph.\n *\n * Only sitemap-eligible pages are judged: a noindex or redirect page is not a\n * refresh candidate. A page with no date is reported as `undated` rather than\n * stale, because age cannot be inferred; the sitemap omits its `<lastmod>` too.\n */\n\nimport type { SeoKind } from \"./declare\";\nimport type { SeoGraph, SeoSource } from \"./graph\";\nimport { isSitemapEligible, lastModified, parseDeclaredDate } from \"./projections\";\n\n/** Project policy: pages whose last change is older than this are stale. */\nexport interface FreshnessPolicy {\n readonly maxAgeDays: number;\n}\n\nexport interface FreshnessEntry {\n readonly path: string;\n readonly kind: SeoKind;\n readonly source: SeoSource;\n /** The declared date, normalized to ISO 8601. */\n readonly lastModified: string;\n /** Whole days between `lastModified` and the report's `asOf`. */\n readonly ageDays: number;\n}\n\nexport interface FreshnessReport {\n readonly asOf: string;\n readonly maxAgeDays: number;\n /** Pages older than `maxAgeDays`, oldest first: the refresh queue. */\n readonly stale: ReadonlyArray<FreshnessEntry>;\n /** Dated pages within `maxAgeDays`, newest first. */\n readonly fresh: ReadonlyArray<FreshnessEntry>;\n /** Sitemap-eligible pages that declare no date, in graph order. */\n readonly undated: ReadonlyArray<string>;\n /** Sitemap-eligible pages whose date does not parse; `check` fails on each. */\n readonly invalid: ReadonlyArray<string>;\n}\n\nconst DAY_MS = 86_400_000;\n\n/**\n * Split the graph's sitemap-eligible pages into stale, fresh, undated, and\n * invalid. An invalid date cannot be aged, but it is listed so a report never\n * reads as complete while it skipped a page.\n */\nexport function freshnessReport(\n graph: SeoGraph,\n policy: FreshnessPolicy,\n now: Date,\n): FreshnessReport {\n const stale: Array<FreshnessEntry> = [];\n const fresh: Array<FreshnessEntry> = [];\n const undated: Array<string> = [];\n const invalid: Array<string> = [];\n\n for (const node of graph.nodes.values()) {\n if (!isSitemapEligible(node)) continue;\n const declared = lastModified(node);\n if (declared === undefined) {\n undated.push(node.path);\n continue;\n }\n const time = parseDeclaredDate(declared);\n if (time === undefined) {\n invalid.push(node.path);\n continue;\n }\n const entry: FreshnessEntry = {\n path: node.path,\n kind: node.kind,\n source: node.source,\n lastModified: new Date(time).toISOString(),\n ageDays: Math.max(0, Math.floor((now.getTime() - time) / DAY_MS)),\n };\n (entry.ageDays > policy.maxAgeDays ? stale : fresh).push(entry);\n }\n\n stale.sort((a, b) => b.ageDays - a.ageDays || (a.path < b.path ? -1 : 1));\n fresh.sort((a, b) => a.ageDays - b.ageDays || (a.path < b.path ? -1 : 1));\n return {\n asOf: now.toISOString(),\n maxAgeDays: policy.maxAgeDays,\n stale,\n fresh,\n undated,\n invalid,\n };\n}\n","/**\n * The SEO check engine: a set of rules run against the derived {@link SeoGraph}.\n * Pure — no I/O, no `clientEnv`, no React. The CLI's `pagegraph check` command and the\n * vitest suite both call {@link checkGraph}; nothing else derives correctness.\n *\n * Two severities, mapped to the CLI's exit contract:\n * - `structural` — a declaration is internally broken (a link points nowhere, a\n * card would render empty, a page contradicts its own robots/sitemap intent).\n * Any structural violation fails `pagegraph check` (exit 1); these must not ship.\n * - `editorial` — a quality smell (duplicate or mis-sized titles/descriptions).\n * Reported as warnings; `pagegraph check` still exits 0 when only these are present.\n *\n * The graph only knows what declarations and frontmatter carry, so the rules are\n * scoped to that: per-node title/description live only on collection *instances*,\n * never on structural route nodes (whose head tags are composed at render time and\n * are not in the graph). Rules are data-driven and listed once in\n * {@link CHECK_RULES}.\n */\n\nimport type { SitemapPolicy } from \"./declare\";\nimport { freshnessReport, type FreshnessPolicy } from \"./freshness\";\nimport type { SeoGraph, SeoNode } from \"./graph\";\nimport type { LinkEdge } from \"./links\";\nimport { isSitemapEligible, parseDeclaredDate } from \"./projections\";\n\nexport type Severity = \"structural\" | \"editorial\";\n\nexport interface Violation {\n severity: Severity;\n rule: string;\n path?: string | undefined;\n message: string;\n fix?: string | undefined;\n}\n\n/**\n * A contextual-link coverage rule: every sitemap-eligible page matching the\n * `path` glob must have at least `minInbound` incoming contextual links. Unlike\n * the static rules, these come from project policy (a CLI flag or\n * `pagegraph.config.ts`), because \"which pages are money pages\" is app knowledge.\n *\n * The same rule shape is evaluated twice: `checkCoverage` counts declared\n * `related` edges, and `checkRenderedCoverage` counts the contextual anchors a\n * crawl actually served. The target universe is always the declared graph's\n * sitemap-eligible pages — the rendered pass only swaps the edge source.\n */\nexport interface CoverageRule {\n /** Path glob: `*` matches within a segment, `**` matches across segments. */\n readonly path: string;\n /** Minimum incoming contextual links the matched page needs. */\n readonly minInbound: number;\n}\n\n/** A single finding before its rule's `severity`/`rule` name are attached. */\ninterface RawViolation {\n path?: string | undefined;\n message: string;\n fix?: string | undefined;\n}\n\ninterface CheckRule {\n readonly name: string;\n readonly severity: Severity;\n readonly evaluate: (graph: SeoGraph) => ReadonlyArray<RawViolation>;\n}\n\n/** A positive sitemap policy — the author asked for this page to be indexed. */\nconst hasPositiveSitemap = (sitemap: SitemapPolicy | false | undefined): sitemap is SitemapPolicy =>\n sitemap !== undefined && sitemap !== false;\n\n/** Content and manifest instances carry page-level title/description. */\nconst isInstance = (node: SeoNode): boolean => node.source !== \"route\";\n\n/** Group instance nodes by a present, non-empty string field for duplicate detection. */\nconst groupInstancesBy = (\n graph: SeoGraph,\n field: (node: SeoNode) => string | undefined,\n): Map<string, Array<SeoNode>> => {\n const groups = new Map<string, Array<SeoNode>>();\n for (const node of graph.nodes.values()) {\n if (!isInstance(node)) continue;\n const value = field(node)?.trim();\n if (!value) continue;\n const bucket = groups.get(value);\n if (bucket) bucket.push(node);\n else groups.set(value, [node]);\n }\n return groups;\n};\n\nconst DESCRIPTION_MAX = 160;\nconst DESCRIPTION_MIN = 50;\n\n/**\n * Every check, in one place. `checkGraph` runs them in order and stamps each\n * finding with its rule name and severity, so the output is grouped by rule and\n * deterministic (nodes iterate in graph insertion order, edges in array order).\n */\nconst CHECK_RULES: ReadonlyArray<CheckRule> = [\n {\n name: \"path-owner-collision\",\n severity: \"structural\",\n evaluate: (graph) =>\n (graph.collisions ?? []).map((collision) => ({\n path: collision.path,\n message: `Canonical path \"${collision.path}\" is owned by multiple sources: ${collision.sources.join(\", \")}.`,\n fix: \"Give every concrete page one canonical path and one graph owner.\",\n })),\n },\n {\n name: \"canonical-path-collision\",\n severity: \"structural\",\n evaluate: (graph) => {\n const groups = new Map<string, Array<string>>();\n for (const path of graph.nodes.keys()) {\n const canonical = path.toLowerCase().replace(/\\/$/, \"\") || \"/\";\n const paths = groups.get(canonical);\n if (paths) paths.push(path);\n else groups.set(canonical, [path]);\n }\n return [...groups.values()]\n .filter((paths) => paths.length > 1)\n .flatMap((paths) =>\n paths.map((path) => ({\n path,\n message: `Canonical path collides with ${paths.filter((candidate) => candidate !== path).join(\", \")}.`,\n fix: \"Use one lowercase, trailing-slash-normalized canonical path.\",\n })),\n );\n },\n },\n {\n name: \"self-edge\",\n severity: \"structural\",\n evaluate: (graph) =>\n graph.edges\n .filter((edge) => edge.from === edge.to)\n .map((edge) => ({\n path: edge.from,\n message: `${edge.type} edge points back to its own source node.`,\n fix: \"Remove the self-reference from the canonical manifest or route declaration.\",\n })),\n },\n {\n name: \"duplicate-edge\",\n severity: \"structural\",\n evaluate: (graph) => {\n const seen = new Set<string>();\n const duplicates: Array<RawViolation> = [];\n for (const edge of graph.edges) {\n const key = `${edge.from}\\u0000${edge.to}\\u0000${edge.type}`;\n if (seen.has(key)) {\n duplicates.push({\n path: edge.from,\n message: `Duplicate ${edge.type} edge from \"${edge.from}\" to \"${edge.to}\".`,\n fix: \"Declare each graph relationship exactly once.\",\n });\n } else {\n seen.add(key);\n }\n }\n return duplicates;\n },\n },\n {\n // A `related` or `redirect` edge points at a path with no node — the target\n // route/page was renamed or deleted and the declaration wasn't updated.\n name: \"dead-edge\",\n severity: \"structural\",\n evaluate: (graph) =>\n graph.edges\n .filter((edge) => edge.type !== \"crumb-parent\" && !graph.nodes.has(edge.to))\n .map((edge) => ({\n path: edge.from,\n message: `${edge.type} edge from \"${edge.from}\" points at \"${edge.to}\", which is not a node in the graph.`,\n fix: `Update the ${edge.type === \"redirect\" ? \"redirectTo\" : \"related\"} target on the \"${edge.from}\" route, or restore \"${edge.to}\".`,\n })),\n },\n {\n // A content instance with no usable title can't render a legible <title> or card.\n name: \"instance-missing-title\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter((node) => isInstance(node) && !node.instance?.title.trim())\n .map((node) => ({\n path: node.path,\n message: `Content page \"${node.path}\" has no title.`,\n fix: \"Add a `title` to the page frontmatter.\",\n })),\n },\n {\n // The declaration asks for the page to be in the sitemap yet also marks it\n // noindex — contradictory intent. The projection resolves it (noindex wins,\n // excluded), but the declaration should say one thing.\n name: \"sitemap-noindex-contradiction\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter(\n (node) =>\n hasPositiveSitemap(node.policy.sitemap) &&\n node.policy.robots?.toLowerCase().includes(\"noindex\"),\n )\n .map((node) => ({\n path: node.path,\n message: `\"${node.path}\" declares a sitemap policy but its robots value is \"${node.policy.robots}\".`,\n fix: \"Drop the sitemap policy (or set `sitemap: false`) on a noindex page, or remove the noindex robots value.\",\n })),\n },\n {\n // Robots declarations are house-convention lowercase: the sitemap projection\n // matches `includes(\"noindex\")` literally, so a miscased value (\"Noindex\")\n // would silently stay sitemap-eligible. This gate makes that unrepresentable.\n name: \"robots-not-lowercase\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter(\n (node) =>\n node.policy.robots !== undefined &&\n node.policy.robots !== node.policy.robots.toLowerCase(),\n )\n .map((node) => ({\n path: node.path,\n message: `\"${node.path}\" declares robots \"${node.policy.robots}\" — robots values must be lowercase.`,\n fix: 'Lowercase the robots declaration (e.g. \"noindex, follow\").',\n })),\n },\n {\n // A `related` card for a route target renders from that route's `link`\n // metadata; without it the card has no title/description and renders empty.\n // (Content-instance targets render from their frontmatter, so they're exempt.)\n name: \"related-target-missing-link\",\n severity: \"structural\",\n evaluate: (graph) => {\n const linklessTargets = new Set<string>();\n for (const edge of graph.edges) {\n if (edge.type !== \"related\") continue;\n const target = graph.nodes.get(edge.to);\n if (target && target.source === \"route\" && target.policy.link === undefined) {\n linklessTargets.add(edge.to);\n }\n }\n return [...linklessTargets].map((path) => ({\n path,\n message: `Route \"${path}\" is a related-link target but declares no link metadata; its card would render empty.`,\n fix: `Add \\`link: { title, description }\\` to the \"${path}\" route's staticData.seo.`,\n }));\n },\n },\n {\n // A redirect/alias node should never advertise itself in the sitemap.\n name: \"redirect-in-sitemap\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()]\n .filter(\n (node) => node.policy.redirectTo !== undefined && hasPositiveSitemap(node.policy.sitemap),\n )\n .map((node) => ({\n path: node.path,\n message: `Redirect \"${node.path}\" (→ \"${node.policy.redirectTo}\") also declares a sitemap policy.`,\n fix: \"Remove the sitemap policy from the redirect route; only its target belongs in the sitemap.\",\n })),\n },\n {\n // Every declared date feeds the sitemap's `<lastmod>`, and the sitemap\n // projection refuses a date it cannot parse. Catch it here, at the\n // declaration, instead of as a failed sitemap request.\n name: \"invalid-date\",\n severity: \"structural\",\n evaluate: (graph) =>\n [...graph.nodes.values()].flatMap((node) => {\n const dates: Array<[string, string | undefined]> = node.instance\n ? [\n [\"publishedAt\", node.instance.publishedAt],\n [\"modifiedAt\", node.instance.modifiedAt],\n ]\n : [[\"modifiedAt\", node.policy.modifiedAt]];\n return dates.flatMap(([field, value]) =>\n value !== undefined && parseDeclaredDate(value) === undefined\n ? [\n {\n path: node.path,\n message: `${field} \"${value}\" is not an ISO 8601 date on a real calendar day.`,\n fix: `Write ${field} as an ISO 8601 date, e.g. \"2026-09-29\".`,\n },\n ]\n : [],\n );\n }),\n },\n {\n name: \"duplicate-title\",\n severity: \"editorial\",\n evaluate: (graph) =>\n [...groupInstancesBy(graph, (node) => node.instance?.title).entries()]\n .filter(([, nodes]) => nodes.length > 1)\n .flatMap(([title, nodes]) =>\n nodes.map((node) => ({\n path: node.path,\n message: `Title \"${title}\" is shared by ${nodes.length} pages.`,\n fix: \"Give each page a distinct title.\",\n })),\n ),\n },\n {\n name: \"duplicate-description\",\n severity: \"editorial\",\n evaluate: (graph) =>\n [...groupInstancesBy(graph, (node) => node.instance?.description).entries()]\n .filter(([, nodes]) => nodes.length > 1)\n .flatMap(([, nodes]) =>\n nodes.map((node) => ({\n path: node.path,\n message: `Description is shared by ${nodes.length} pages.`,\n fix: \"Write a distinct meta description for each page.\",\n })),\n ),\n },\n {\n // Meta descriptions outside ~50–160 chars either get truncated in SERPs or\n // read as too thin.\n name: \"description-length\",\n severity: \"editorial\",\n evaluate: (graph) =>\n [...graph.nodes.values()].flatMap((node) => {\n if (!isInstance(node)) return [];\n const description = node.instance?.description?.trim();\n if (!description) return [];\n if (description.length > DESCRIPTION_MAX) {\n return [\n {\n path: node.path,\n message: `Description is ${description.length} chars (max ${DESCRIPTION_MAX}); it will be truncated in results.`,\n fix: `Trim the description to ${DESCRIPTION_MAX} characters or fewer.`,\n },\n ];\n }\n if (description.length < DESCRIPTION_MIN) {\n return [\n {\n path: node.path,\n message: `Description is ${description.length} chars (min ${DESCRIPTION_MIN}); it reads as thin.`,\n fix: `Expand the description to at least ${DESCRIPTION_MIN} characters.`,\n },\n ];\n }\n return [];\n }),\n },\n];\n\n/**\n * Options for {@link checkGraph}. Freshness needs a clock, and the engine stays\n * pure, so a freshness policy is only accepted together with the `now` it is\n * judged against.\n */\nexport type CheckGraphOptions = {\n readonly coverage?: ReadonlyArray<CoverageRule> | undefined;\n} & (\n | { readonly freshness: FreshnessPolicy; readonly now: Date }\n | { readonly freshness?: undefined; readonly now?: undefined }\n);\n\n/**\n * Run every static rule against the graph, then any caller-supplied\n * {@link CoverageRule}s and freshness policy, and return the flat list of\n * violations. With no options the result is exactly the static rule set.\n */\nexport function checkGraph(graph: SeoGraph, options: CheckGraphOptions = {}): Array<Violation> {\n const violations: Array<Violation> = CHECK_RULES.flatMap((rule) =>\n rule.evaluate(graph).map((raw): Violation => ({\n severity: rule.severity,\n rule: rule.name,\n path: raw.path,\n message: raw.message,\n fix: raw.fix,\n })),\n );\n const coverage = options.coverage;\n if (coverage !== undefined && coverage.length > 0) {\n violations.push(...checkCoverage(graph, coverage));\n }\n if (options.freshness !== undefined) {\n violations.push(...checkFreshness(graph, options.freshness, options.now));\n }\n return violations;\n}\n\n/**\n * One editorial `stale-page` violation per sitemap-eligible page whose last\n * declared change is older than the policy allows. Undated pages are not\n * violations: see {@link freshnessReport} for the full picture.\n */\nexport function checkFreshness(\n graph: SeoGraph,\n policy: FreshnessPolicy,\n now: Date,\n): Array<Violation> {\n return freshnessReport(graph, policy, now).stale.map((entry) => ({\n severity: \"editorial\",\n rule: \"stale-page\",\n path: entry.path,\n message: `Last changed ${entry.lastModified.slice(0, 10)}, ${entry.ageDays} days ago (limit ${policy.maxAgeDays}).`,\n fix: \"Review the page; when its content changes, update its modifiedAt.\",\n }));\n}\n\n/** Escape a glob for `RegExp`, then expand `**`, `*`, and `?` to path-aware forms. */\nexport const globToRegExp = (glob: string): RegExp => {\n const escaped = glob.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const source = escaped\n .replace(/\\*\\*/g, \"\\u0000\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/\\?/g, \"[^/]\")\n .replace(/\\u0000/g, \".*\");\n return new RegExp(`^${source}$`);\n};\n\n/**\n * The minimal view a coverage rule needs: which paths exist (for the glob), which\n * of them are sitemap-eligible (the enforced set), and how many contextual links\n * each receives. Declared and rendered coverage differ only in how this surface\n * is built, so the rule engine itself stays single-sourced.\n */\ninterface CoverageSurface {\n readonly paths: ReadonlyArray<string>;\n readonly eligiblePaths: ReadonlyArray<string>;\n readonly inbound: (path: string) => number;\n}\n\n/**\n * Enforce contextual-link coverage rules over an explicit surface.\n *\n * A rule that matches no sitemap-eligible page is itself a violation: a typo or\n * a rule aimed at a noindex page would otherwise pass silently forever.\n */\nconst checkCoverageRules = (\n surface: CoverageSurface,\n rules: ReadonlyArray<CoverageRule>,\n): Array<Violation> => {\n const violations: Array<Violation> = [];\n for (const rule of rules) {\n const matcher = globToRegExp(rule.path);\n const matched = surface.paths.filter((path) => matcher.test(path));\n const eligible = surface.eligiblePaths.filter((path) => matcher.test(path));\n\n if (eligible.length === 0) {\n violations.push({\n severity: \"structural\",\n rule: \"coverage-rule-unmatched\",\n message:\n matched.length === 0\n ? `Coverage rule \"${rule.path}\" matches no page in the graph.`\n : `Coverage rule \"${rule.path}\" matches only pages that are not sitemap-eligible.`,\n fix: \"Point the rule at a sitemap-eligible path, or drop it.\",\n });\n continue;\n }\n\n for (const path of eligible) {\n const inbound = surface.inbound(path);\n if (inbound >= rule.minInbound) continue;\n violations.push({\n severity: \"structural\",\n rule: \"inbound-link-coverage\",\n path,\n message: `\"${path}\" has ${inbound} incoming contextual link(s); the coverage rule requires ${rule.minInbound}.`,\n fix: `Add ${rule.minInbound - inbound} contextual link(s) pointing at \"${path}\".`,\n });\n }\n }\n return violations;\n};\n\n/** The sitemap-eligible path set of a declared graph, in graph insertion order. */\nconst eligiblePathsOf = (graph: SeoGraph): ReadonlyArray<string> =>\n [...graph.nodes.values()].filter(isSitemapEligible).map((node) => node.path);\n\n/**\n * Enforce contextual-link coverage against the *declared* graph: only `related`\n * edges count — breadcrumb ancestry is navigation, not context.\n */\nexport function checkCoverage(\n graph: SeoGraph,\n rules: ReadonlyArray<CoverageRule>,\n): Array<Violation> {\n return checkCoverageRules(\n {\n paths: [...graph.nodes.keys()],\n eligiblePaths: eligiblePathsOf(graph),\n inbound: (path) =>\n graph.edges.filter((edge) => edge.type === \"related\" && edge.to === path).length,\n },\n rules,\n );\n}\n\n/**\n * Enforce contextual-link coverage against the *rendered* graph: only same-origin\n * body-region anchors count. Nav, header, and footer anchors are site chrome, so\n * a page linked solely from them has no contextual inbound link — the same\n * distinction the declared pass draws by counting `related` edges alone.\n *\n * The target universe is the declared graph's sitemap-eligible pages: the globs\n * in `pagegraph.config.ts` are written against declared paths, and the crawl is what\n * supplies the served edges. Pass the crawl's `internalEdges` (each edge carries\n * its `region`).\n */\nexport function checkRenderedCoverage(\n graph: SeoGraph,\n renderedEdges: ReadonlyArray<LinkEdge>,\n rules: ReadonlyArray<CoverageRule>,\n): Array<Violation> {\n const inbound = new Map<string, number>();\n for (const edge of renderedEdges) {\n if (edge.region !== \"body\") continue;\n inbound.set(edge.to, (inbound.get(edge.to) ?? 0) + 1);\n }\n return checkCoverageRules(\n {\n paths: [...graph.nodes.keys()],\n eligiblePaths: eligiblePathsOf(graph),\n inbound: (path) => inbound.get(path) ?? 0,\n },\n rules,\n );\n}\n\n/** Structural violations fail `pagegraph check`; editorial-only stays green. */\nexport const hasStructuralViolations = (violations: ReadonlyArray<Violation>): boolean =>\n violations.some((violation) => violation.severity === \"structural\");\n"],"mappings":"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"}
|
package/dist/declare-D5o7hwmv.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
//#region src/core/declare.ts
|
|
2
|
-
/** Apply a {@link RouteSeo.titleTemplate}; no template means the title is already full. */
|
|
3
|
-
function applyTitleTemplate(template, title) {
|
|
4
|
-
return template === void 0 ? title : template.replaceAll("%s", () => title);
|
|
5
|
-
}
|
|
6
|
-
//#endregion
|
|
7
|
-
export { applyTitleTemplate as t };
|
|
8
|
-
|
|
9
|
-
//# sourceMappingURL=declare-D5o7hwmv.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"declare-D5o7hwmv.js","names":[],"sources":["../src/core/declare.ts"],"sourcesContent":["/**\n * The declaration surface: what a route says about itself in\n * `staticData.seo`. Everything else in this package derives from it.\n *\n * Consumers supply valid route paths, page kinds, and authored fact ids by\n * augmenting {@link Register}, the same pattern TanStack Router uses for its own\n * typed registry. Unaugmented, these fall back to `string`, so the package\n * compiles and behaves on its own.\n */\n\nimport type { AnyRouteMatch } from \"@tanstack/react-router\";\nimport type { Graph, Thing, WithContext } from \"schema-dts\";\n\n/**\n * Consumer-augmented registry.\n *\n * ```ts\n * declare module \"pagegraph\" {\n * interface Register {\n * paths: FileRouteTypes[\"fullPaths\"];\n * kinds: \"page\" | \"article\" | \"hub\";\n * facts: typeof facts;\n * }\n * }\n * ```\n */\nexport interface Register {}\n\n/** Every public full path in the consumer's route tree (type-only; erased at runtime). */\nexport type PublicPath = Register extends { paths: infer P extends string } ? P : string;\n\n/** The consumer's page taxonomy. A label: nothing in the core branches on it. */\nexport type SeoKind = Register extends { kinds: infer K extends string } ? K : string;\n\nexport interface SitemapPolicy {\n priority: number;\n changeFrequency: \"weekly\" | \"monthly\";\n}\n\n/** A question and answer the page shows and marks up as FAQPage. */\nexport interface SeoFaq {\n question: string;\n answer: string;\n category?: string | undefined;\n isHighlighted?: boolean | undefined;\n}\n\n/** A top-level JSON-LD entity or graph ready to serialize into a script element. */\nexport type JsonLdDocument = WithContext<Thing> | Graph;\n\n/**\n * The document head a page ships. A static route declares it once in\n * `staticData.seo.head` and renders it with `seo.head` from `pagegraph/react`;\n * the graph, `pageHeads`, and the checks read the same title, description, and\n * FAQs. A dynamic route passes it to `seoHead(ctx, head)` from its `head()`.\n */\nexport interface SeoPageHead {\n /**\n * The page title. A route that declares {@link RouteSeo.titleTemplate} gets\n * it applied; otherwise this is the full `<title>`.\n */\n title: string;\n description: string;\n ogTitle?: string | undefined;\n ogDescription?: string | undefined;\n /** Override of the route's robots policy. */\n robots?: string | undefined;\n article?:\n | {\n publishedAt: string;\n modifiedAt?: string | undefined;\n author?: { name: string; url?: string | undefined } | undefined;\n image?: string | undefined;\n tags?: ReadonlyArray<string> | undefined;\n }\n | undefined;\n faqs?: ReadonlyArray<SeoFaq> | undefined;\n service?: { name: string; description?: string | undefined; serviceType: string } | undefined;\n /** Standalone keywords meta for non-article pages; article pages derive it from tags. */\n keywords?: ReadonlyArray<string> | undefined;\n /** Visible canonical pages rendered as an ordered collection on this page. */\n itemList?: { name: string; items: ReadonlyArray<{ name: string; url: string }> } | undefined;\n /** Additional schema.org documents for this route. */\n jsonLd?: ReadonlyArray<JsonLdDocument> | undefined;\n /**\n * Explicit canonical path (used for canonical + og:url instead of the match pathname).\n * For routes whose canonical is computed independently of the URL, e.g. a docs splat\n * that derives it from the resolved slug segments in its loader.\n */\n canonicalPath?: string | undefined;\n /**\n * Explicit breadcrumb trail for the BreadcrumbList JSON-LD, overriding the match-chain\n * trail. For routes whose hierarchy lives outside the route tree. Each `path` is a URL path.\n */\n breadcrumbs?: ReadonlyArray<{ name: string; path: string }> | undefined;\n}\n\nexport interface RouteSeo {\n kind: SeoKind;\n /** Breadcrumb label; fn form reads the match (e.g. loaderData frontmatter title). */\n crumb?: string | ((match: AnyRouteMatch) => string) | undefined;\n /** false = excluded from sitemap. Absent on non-indexable kinds is fine. */\n sitemap?: SitemapPolicy | false | undefined;\n /** meta robots value, e.g. \"noindex, follow\" on a hub page. */\n robots?: string | undefined;\n /** Typed cross-link edges. */\n related?: ReadonlyArray<PublicPath> | undefined;\n /** How OTHER pages render a card for this page. */\n link?: { title: string; description: string } | undefined;\n /** This route is a redirect/alias. */\n redirectTo?: PublicPath | undefined;\n /**\n * When this page's content last changed meaningfully, as an ISO 8601 date.\n * Feeds the sitemap `<lastmod>` and the freshness report. Content instances\n * carry their own dates instead; a param route's value is never inherited.\n */\n modifiedAt?: string | undefined;\n /** A static page's head, rendered by `seo.head`; the graph and `pageHeads` read it. */\n head?: SeoPageHead | undefined;\n /**\n * Full-title template for pages rendered through this route, with `%s` for the\n * page's own title (e.g. `\"%s | Example Blog\"`). `seoHead` applies it to the\n * title a dynamic route passes, and the graph applies it to the instances a\n * collection routes through it, so the suffix lives in one place.\n */\n titleTemplate?: string | undefined;\n}\n\n/** Apply a {@link RouteSeo.titleTemplate}; no template means the title is already full. */\nexport function applyTitleTemplate(template: string | undefined, title: string): string {\n // A replacer function keeps `$&` and friends in the title literal.\n return template === undefined ? title : template.replaceAll(\"%s\", () => title);\n}\n\ndeclare module \"@tanstack/react-router\" {\n interface StaticDataRouteOption {\n seo?: RouteSeo | undefined;\n /** How this route supplies its authored document. */\n markdown?: \"rendered\" | \"source\" | undefined;\n /** Group label for this page in llms.txt. */\n llms?: string | undefined;\n }\n}\n"],"mappings":";;AAiIA,SAAgB,mBAAmB,UAA8B,OAAuB;CAEtF,OAAO,aAAa,KAAA,IAAY,QAAQ,SAAS,WAAW,YAAY,KAAK;AAC/E"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"graph-DMNOLXLu.js","names":[],"sources":["../src/core/graph.ts"],"sourcesContent":["/**\n * SEO graph — the derived model that projections (sitemap, robots), the CLI, and\n * the check engine all read from. Built from route declarations (`staticData.seo`)\n * plus consumer-supplied content collections. This module is pure: no React, no\n * env, no knowledge of where instances come from. Origins and env-derived values\n * are injected by the callers of the projections, never read here.\n *\n * A node is one of:\n * - a structural route (`source: \"route\"`), keyed by its normalized full path,\n * merging a layout route's declaration (crumb) with its index child's (kind,\n * sitemap policy) when both resolve to the same URL;\n * - a content instance (`source` = the collection's label), keyed by the page URL\n * and carrying page-level metadata.\n *\n * Edges: `crumb-parent` (breadcrumb ancestry), `related` (deliberate cross-links),\n * `collection-member` (membership in a curated set), and `redirect` (route aliases).\n * The route walk emits crumb/related/redirect edges from declarations; a collection\n * may declare any additional edges its instances need.\n */\n\nimport type { AnyRoute } from \"@tanstack/react-router\";\n\nimport { applyTitleTemplate, type PublicPath, type RouteSeo, type SeoKind } from \"./declare\";\n\n/**\n * Where a node came from: `\"route\"` for a structural route declaration, or the\n * `source` label of the collection that produced the instance (e.g. \"blog\").\n */\nexport type SeoSource = string;\n\nexport interface SeoNode {\n /** Canonical path, no origin (e.g. \"/pricing\", \"/blog/my-post\"). */\n path: string;\n kind: SeoKind;\n source: SeoSource;\n /** Route-declared authored-document mode, inherited by collection instances. */\n markdown?: \"rendered\" | \"source\" | undefined;\n /** Group label for llms.txt, inherited by collection instances. */\n llms?: string | undefined;\n /** Route-declared policy, or synthesized (kind + inherited sitemap) for instances. */\n policy: RouteSeo;\n /**\n * The head the page ships: a static route's declared `head`, or an instance's\n * title (through its route's `titleTemplate`) and description. Absent when the\n * route computes its head at request time and no collection supplies it.\n */\n head?: SeoNodeHead | undefined;\n instance?:\n | {\n title: string;\n description?: string | undefined;\n publishedAt?: string | undefined;\n modifiedAt?: string | undefined;\n }\n | undefined;\n}\n\n/**\n * A node's head as graph data: the full title, description, and FAQ text. An\n * instance without a description still carries its title.\n */\nexport interface SeoNodeHead {\n readonly title: string;\n readonly description?: string | undefined;\n readonly faqs?: ReadonlyArray<{ readonly question: string; readonly answer: string }> | undefined;\n}\n\nexport type SeoEdgeType = \"crumb-parent\" | \"related\" | \"redirect\" | \"collection-member\";\n\nexport interface SeoEdge {\n from: string;\n to: string;\n type: SeoEdgeType;\n}\n\nexport interface SeoGraph {\n nodes: Map<string, SeoNode>;\n edges: Array<SeoEdge>;\n /** Exact-path ownership conflicts encountered while assembling graph sources. */\n collisions?: ReadonlyArray<{ path: string; sources: ReadonlyArray<SeoSource> }> | undefined;\n}\n\n/** One concrete page produced by a collection. */\nexport interface SeoInstance {\n /** Canonical path of the page (e.g. \"/blog/my-post\"). */\n readonly path: string;\n readonly title: string;\n readonly description?: string | undefined;\n readonly publishedAt?: string | undefined;\n readonly modifiedAt?: string | undefined;\n}\n\nexport interface SeoCollection {\n /**\n * The param route these instances render through (e.g. \"/blog/$slug\"). Instances\n * inherit this route's declared policy (kind + sitemap) — the graph reads it from\n * the structural node, so declarations stay the single source of truth.\n */\n readonly route: PublicPath;\n /** The `source` stamped on every node this collection produces (e.g. \"blog\"). */\n readonly source: SeoSource;\n readonly instances: ReadonlyArray<SeoInstance>;\n /** Edges the collection declares between its instances and the rest of the graph. */\n readonly edges?: ReadonlyArray<SeoEdge> | undefined;\n}\n\n/**\n * The structural route shape the graph walks: a TanStack route tree has it\n * before `init()`, and an adapter can build it from parsed route files.\n */\nexport interface SeoRouteNode {\n readonly options: {\n readonly path?: string | undefined;\n readonly staticData?:\n | {\n readonly seo?: RouteSeo | undefined;\n readonly markdown?: SeoNode[\"markdown\"];\n readonly llms?: SeoNode[\"llms\"];\n }\n | undefined;\n };\n readonly children?: ReadonlyArray<SeoRouteNode> | undefined;\n}\n\nexport interface BuildSeoGraphInput {\n readonly routeTree: AnyRoute | SeoRouteNode;\n readonly collections?: ReadonlyArray<SeoCollection> | undefined;\n}\n\n/** Kind for instances whose collection route carries no declaration to inherit. */\nconst FALLBACK_KIND: SeoKind = \"page\";\n\n\n/**\n * Join a child's local path onto its parent's computed full path with the same\n * semantics as TanStack's route init (relative segments, index \"/\" inherits the\n * parent, pathless/group routes are transparent), then normalize trailing slashes.\n */\nfunction joinPath(parent: string, seg: string | undefined): string {\n if (seg === undefined) return parent; // pathless layout / route group\n if (seg === \"/\") return parent; // index route resolves to its parent's URL\n const trimmed = seg.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n const joined = `${parent === \"/\" ? \"\" : parent}/${trimmed}`;\n return joined.replace(/\\/{2,}/g, \"/\");\n}\n\n/** A route's declared head as graph data: templated title, description, and plain FAQs. */\nfunction declaredHead(seo: RouteSeo): SeoNodeHead | undefined {\n if (seo.head === undefined) return undefined;\n return {\n title: applyTitleTemplate(seo.titleTemplate, seo.head.title),\n description: seo.head.description,\n faqs: seo.head.faqs?.map(({ question, answer }) => ({ question, answer })),\n };\n}\n\n/** Merge a route's declaration into an existing same-path node (deeper route wins). */\nfunction mergeSeo(base: RouteSeo, override: RouteSeo): RouteSeo {\n return {\n kind: override.kind,\n crumb: override.crumb ?? base.crumb,\n sitemap: override.sitemap ?? base.sitemap,\n robots: override.robots ?? base.robots,\n related: override.related ?? base.related,\n link: override.link ?? base.link,\n redirectTo: override.redirectTo ?? base.redirectTo,\n modifiedAt: override.modifiedAt ?? base.modifiedAt,\n head: override.head ?? base.head,\n titleTemplate: override.titleTemplate ?? base.titleTemplate,\n };\n}\n\n/**\n * Walk the route tree building structural nodes and crumb-parent edges. `crumbStack`\n * holds the paths of crumb-declaring ancestors so each crumb node links to its nearest\n * crumb ancestor down the real route-parent chain (matching render-time breadcrumbs).\n */\nfunction walkRoutes(\n route: SeoRouteNode,\n parentPath: string,\n isRoot: boolean,\n nodes: Map<string, SeoNode>,\n edges: Array<SeoEdge>,\n crumbStack: Array<string>,\n): void {\n const path = isRoot ? \"/\" : joinPath(parentPath, route.options.path);\n const data = route.options.staticData;\n const seo = data?.seo;\n\n if (seo || data?.markdown !== undefined || data?.llms !== undefined) {\n const existing = nodes.get(path);\n if (existing) {\n if (seo) existing.policy = mergeSeo(existing.policy, seo);\n existing.kind = existing.policy.kind;\n // A head renders with its own route's template, never one merged from a layout.\n existing.head = (seo && declaredHead(seo)) ?? existing.head;\n existing.markdown = data?.markdown ?? existing.markdown;\n existing.llms = data?.llms ?? existing.llms;\n } else {\n const policy = seo ?? { kind: FALLBACK_KIND };\n nodes.set(path, {\n path,\n kind: policy.kind,\n source: \"route\",\n policy: { ...policy },\n head: seo && declaredHead(seo),\n markdown: data?.markdown,\n llms: data?.llms,\n });\n }\n\n const nearestCrumbAncestor = crumbStack[crumbStack.length - 1];\n if (seo?.crumb !== undefined && nearestCrumbAncestor !== undefined) {\n edges.push({ from: path, to: nearestCrumbAncestor, type: \"crumb-parent\" });\n }\n }\n\n const pushedCrumb = seo?.crumb !== undefined;\n if (pushedCrumb) crumbStack.push(path);\n for (const child of route.children ?? []) {\n walkRoutes(child, path, false, nodes, edges, crumbStack);\n }\n if (pushedCrumb) crumbStack.pop();\n}\n\n/**\n * Add one collection's instance nodes, inheriting kind + sitemap policy from the\n * collection route's declaration, then append the edges it declares. A path already\n * owned by another node is a collision: the first owner keeps the path and the\n * conflict is reported (the `path-owner-collision` check turns it into a violation).\n */\nfunction addCollection(\n nodes: Map<string, SeoNode>,\n edges: Array<SeoEdge>,\n collisions: Array<{ path: string; sources: ReadonlyArray<SeoSource> }>,\n collection: SeoCollection,\n): void {\n const collectionNode = nodes.get(collection.route);\n const kind = collectionNode?.kind ?? FALLBACK_KIND;\n const sitemap = collectionNode?.policy.sitemap;\n const titleTemplate = collectionNode?.policy.titleTemplate;\n\n /**\n * Paths this collection lost to an earlier owner. Their instances never enter\n * the graph, so any edge declared out of them would dangle — and the dead-edge\n * check only validates an edge's `to`, so nothing downstream would catch it.\n */\n const rejected = new Set<string>();\n\n for (const instance of collection.instances) {\n const existing = nodes.get(instance.path);\n if (existing) {\n collisions.push({ path: instance.path, sources: [existing.source, collection.source] });\n rejected.add(instance.path);\n continue;\n }\n nodes.set(instance.path, {\n path: instance.path,\n kind,\n source: collection.source,\n markdown: collectionNode?.markdown,\n llms: collectionNode?.llms,\n policy: { kind, sitemap },\n head: { title: applyTitleTemplate(titleTemplate, instance.title), description: instance.description },\n instance: {\n title: instance.title,\n description: instance.description,\n publishedAt: instance.publishedAt,\n modifiedAt: instance.modifiedAt,\n },\n });\n }\n\n for (const edge of collection.edges ?? []) {\n if (rejected.has(edge.from)) continue;\n edges.push(edge);\n }\n}\n\n/**\n * Build the SEO graph from route declarations and content collections.\n *\n * Synchronous: the caller materializes its collections before calling, so there is\n * no async work here. Callers own the origin.\n */\nexport function buildSeoGraph(input: BuildSeoGraphInput): SeoGraph {\n const nodes = new Map<string, SeoNode>();\n const edges: Array<SeoEdge> = [];\n const collisions: Array<{ path: string; sources: ReadonlyArray<SeoSource> }> = [];\n\n walkRoutes(input.routeTree as unknown as SeoRouteNode, \"/\", true, nodes, edges, []);\n\n for (const collection of input.collections ?? []) {\n addCollection(nodes, edges, collisions, collection);\n }\n\n for (const node of nodes.values()) {\n if (node.source !== \"route\") continue;\n for (const to of node.policy.related ?? []) {\n edges.push({ from: node.path, to, type: \"related\" });\n }\n if (node.policy.redirectTo !== undefined) {\n edges.push({ from: node.path, to: node.policy.redirectTo, type: \"redirect\" });\n }\n }\n\n return { nodes, edges, collisions };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"page-heads-BgbaIjj9.js","names":[],"sources":["../src/core/page-heads.ts"],"sourcesContent":["import { globToRegExp } from \"./checks\";\nimport type { SeoGraph, SeoNode } from \"./graph\";\n\n/** Plain document metadata, without framework or schema dependencies. */\nexport interface PageHead {\n readonly path: string;\n readonly title: string;\n readonly description: string;\n}\n\nexport interface PageHeadsOptions {\n /** Path globs: `*` matches a segment, `**` matches across segments. */\n readonly exclude?: ReadonlyArray<string>;\n /** Exclude noindex pages and redirects. Parameter templates are always excluded. Defaults to true. */\n readonly indexable?: boolean;\n}\n\n/** Select page nodes before the host resolves metadata; sitemap-disabled hubs remain pages. */\nexport function selectPageHeadNodes(graph: SeoGraph, options: PageHeadsOptions = {}): Array<SeoNode> {\n const excludes = (options.exclude ?? []).map(globToRegExp);\n return [...graph.nodes.values()].filter((node) =>\n node.kind !== \"layout\" &&\n !node.path.includes(\"$\") &&\n (node.policy.sitemap !== undefined || node.instance !== undefined) &&\n !excludes.some((pattern) => pattern.test(node.path)) &&\n (options.indexable === false || (\n node.policy.redirectTo === undefined &&\n !node.policy.robots?.toLowerCase().split(/[\\s,]+/).includes(\"noindex\")\n )),\n );\n}\n\n/** Extract resolved heads, naming the selected page when metadata is missing or blank. */\nexport function pageHeads(graph: SeoGraph, options: PageHeadsOptions = {}): Array<PageHead> {\n return selectPageHeadNodes(graph, options).map((node) => {\n const title = node.head?.title;\n const description = node.head?.description;\n if (!title?.trim()) throw new Error(`Invalid page head for ${node.path}: missing title`);\n if (!description?.trim()) throw new Error(`Invalid page head for ${node.path}: missing description`);\n return { path: node.path, title, description };\n });\n}\n"],"mappings":";;;AAkBA,SAAgB,oBAAoB,OAAiB,UAA4B,CAAC,GAAmB;CACnG,MAAM,YAAY,QAAQ,WAAW,CAAC,EAAA,CAAG,IAAI,YAAY;CACzD,OAAO,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,QAAQ,SACvC,KAAK,SAAS,YACd,CAAC,KAAK,KAAK,SAAS,GAAG,MACtB,KAAK,OAAO,YAAY,KAAA,KAAa,KAAK,aAAa,KAAA,MACxD,CAAC,SAAS,MAAM,YAAY,QAAQ,KAAK,KAAK,IAAI,CAAC,MAClD,QAAQ,cAAc,SACrB,KAAK,OAAO,eAAe,KAAA,KAC3B,CAAC,KAAK,OAAO,QAAQ,YAAY,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,SAAS,SAAS,EAEzE;AACF;;AAGA,SAAgB,UAAU,OAAiB,UAA4B,CAAC,GAAoB;CAC1F,OAAO,oBAAoB,OAAO,OAAO,CAAC,CAAC,KAAK,SAAS;EACvD,MAAM,QAAQ,KAAK,MAAM;EACzB,MAAM,cAAc,KAAK,MAAM;EAC/B,IAAI,CAAC,OAAO,KAAK,GAAG,MAAM,IAAI,MAAM,yBAAyB,KAAK,KAAK,gBAAgB;EACvF,IAAI,CAAC,aAAa,KAAK,GAAG,MAAM,IAAI,MAAM,yBAAyB,KAAK,KAAK,sBAAsB;EACnG,OAAO;GAAE,MAAM,KAAK;GAAM;GAAO;EAAY;CAC/C,CAAC;AACH"}
|