pagegraph 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +403 -148
- package/dist/audit.d.ts +2 -2
- package/dist/audit.js +4 -4
- package/dist/build-only/audit.d.ts +1 -0
- package/dist/build-only/audit.js +6 -0
- package/dist/build-only/audit.js.map +1 -0
- package/dist/build-only/claims.d.ts +1 -0
- package/dist/build-only/claims.js +6 -0
- package/dist/build-only/claims.js.map +1 -0
- package/dist/build-only/config.d.ts +1 -0
- package/dist/build-only/config.js +6 -0
- package/dist/build-only/config.js.map +1 -0
- package/dist/build-only/oxlint.d.ts +1 -0
- package/dist/build-only/oxlint.js +6 -0
- package/dist/build-only/oxlint.js.map +1 -0
- package/dist/build-only/tanstack-start.d.ts +1 -0
- package/dist/build-only/tanstack-start.js +6 -0
- package/dist/build-only/tanstack-start.js.map +1 -0
- package/dist/build-only/vite.d.ts +1 -0
- package/dist/build-only/vite.js +6 -0
- package/dist/build-only/vite.js.map +1 -0
- package/dist/{checks-nk8ZRRIA.js → checks-BS-aDXB_.js} +4 -142
- package/dist/checks-BS-aDXB_.js.map +1 -0
- package/dist/claims-Bch7Lxlo.d.ts +92 -0
- package/dist/claims-Bv7voeEV.js +431 -0
- package/dist/claims-Bv7voeEV.js.map +1 -0
- package/dist/claims.d.ts +94 -0
- package/dist/claims.js +2 -0
- package/dist/cli.js +10368 -8485
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -83
- package/dist/config.js +3 -122
- package/dist/config.js.map +1 -1
- package/dist/{content-CB_kHmJ3.js → content-CHd0X4s6.js} +3 -2
- package/dist/{content-CB_kHmJ3.js.map → content-CHd0X4s6.js.map} +1 -1
- package/dist/{content-DVL2iyGU.d.ts → content-DgNr7apS.d.ts} +5 -130
- package/dist/declare-D5o7hwmv.js +9 -0
- package/dist/declare-D5o7hwmv.js.map +1 -0
- package/dist/document-CztEu3Vd.d.ts +109 -0
- package/dist/document-Dvemcv7V.js +379 -0
- package/dist/document-Dvemcv7V.js.map +1 -0
- package/dist/facts-1OKclPOk.js +115 -0
- package/dist/facts-1OKclPOk.js.map +1 -0
- package/dist/graph-D1PIndsC.d.ts +12238 -0
- package/dist/graph-DMNOLXLu.js +172 -0
- package/dist/graph-DMNOLXLu.js.map +1 -0
- package/dist/index-C5w9ripz.d.ts +204 -0
- package/dist/index.d.ts +6 -196
- package/dist/index.js +55 -134
- package/dist/index.js.map +1 -1
- package/dist/inline-BbS93GAT.js +682 -0
- package/dist/inline-BbS93GAT.js.map +1 -0
- package/dist/llms-Dhr_5u2O.js +19 -0
- package/dist/llms-Dhr_5u2O.js.map +1 -0
- package/dist/markdown-C6dRE9zD.js +168 -0
- package/dist/markdown-C6dRE9zD.js.map +1 -0
- package/dist/markdown-path-lJB9BAVZ.js +6 -0
- package/dist/markdown-path-lJB9BAVZ.js.map +1 -0
- package/dist/message-CuBjgZ74.js +12 -0
- package/dist/message-CuBjgZ74.js.map +1 -0
- package/dist/oxlint.d.ts +58 -0
- package/dist/oxlint.js +202 -0
- package/dist/oxlint.js.map +1 -0
- package/dist/page-heads-BfjWQvau.d.ts +108 -0
- package/dist/page-heads-BgbaIjj9.js +25 -0
- package/dist/page-heads-BgbaIjj9.js.map +1 -0
- package/dist/projections-Bk-qOOsR.d.ts +78 -0
- package/dist/projections-D0oOFKiY.js +142 -0
- package/dist/projections-D0oOFKiY.js.map +1 -0
- package/dist/react.d.ts +27 -12051
- package/dist/react.js +14 -4
- package/dist/react.js.map +1 -1
- package/dist/route-config-35ZEzkNC.js +308 -0
- package/dist/route-config-35ZEzkNC.js.map +1 -0
- package/dist/route-config-wneg6ktn.d.ts +29 -0
- package/dist/tanstack-start/index.d.ts +56 -0
- package/dist/tanstack-start/index.js +717 -0
- package/dist/tanstack-start/index.js.map +1 -0
- package/dist/tanstack-start/markdown.d.ts +12 -0
- package/dist/tanstack-start/markdown.js +149 -0
- package/dist/tanstack-start/markdown.js.map +1 -0
- package/dist/tanstack-start/prerender-worker.d.ts +2 -0
- package/dist/tanstack-start/prerender-worker.js +7 -0
- package/dist/tanstack-start/prerender-worker.js.map +1 -0
- package/dist/tanstack-start/react.d.ts +7 -0
- package/dist/tanstack-start/react.js +12 -0
- package/dist/tanstack-start/react.js.map +1 -0
- package/dist/tanstack-start/server.d.ts +20 -0
- package/dist/tanstack-start/server.js +77 -0
- package/dist/tanstack-start/server.js.map +1 -0
- package/dist/vite-graph-loader-CsGiYNWE.js +105 -0
- package/dist/vite-graph-loader-CsGiYNWE.js.map +1 -0
- package/dist/vite-graph-loader-DZCwejuJ.d.ts +133 -0
- package/dist/vite.d.ts +2 -28
- package/dist/vite.js +1 -307
- package/dist/wire-B4RQMc4e.js +26 -0
- package/dist/wire-B4RQMc4e.js.map +1 -0
- package/package.json +70 -7
- package/dist/checks-nk8ZRRIA.js.map +0 -1
- package/dist/vite.js.map +0 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { a as SeoGraph } from "./graph-D1PIndsC.js";
|
|
2
|
+
import { n as ClaimsOptions } from "./claims-Bch7Lxlo.js";
|
|
3
|
+
import { o as Facts } from "./page-heads-BfjWQvau.js";
|
|
4
|
+
import { r as RobotsConfig } from "./projections-Bk-qOOsR.js";
|
|
5
|
+
import { t as SeoRouteConfigOptions } from "./route-config-wneg6ktn.js";
|
|
6
|
+
import "@tanstack/router-generator";
|
|
7
|
+
import { PluginOption } from "vite";
|
|
8
|
+
//#region src/tanstack-start/graph.d.ts
|
|
9
|
+
/** The robots.txt policy a build serves; `origin` and indexability come from {@link SiteRuntime}. */
|
|
10
|
+
type RobotsPolicy = Pick<RobotsConfig, "disallow" | "contentSignal" | "directives">;
|
|
11
|
+
/** The graph and the site runtime one evaluation produces. */
|
|
12
|
+
interface AppGraph {
|
|
13
|
+
readonly graph: SeoGraph;
|
|
14
|
+
readonly site: SiteRuntime;
|
|
15
|
+
readonly markdown: {
|
|
16
|
+
readonly origin: string;
|
|
17
|
+
} | null;
|
|
18
|
+
readonly facts: Facts | undefined;
|
|
19
|
+
}
|
|
20
|
+
interface PagegraphOptions {
|
|
21
|
+
/** This build's canonical origin, no trailing slash, e.g. `https://example.com`. */
|
|
22
|
+
readonly origin: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether this build's host may be indexed. A non-indexable build serves a
|
|
25
|
+
* disallow-all robots.txt with no Sitemap line. Defaults to `true`.
|
|
26
|
+
*/
|
|
27
|
+
readonly indexable?: boolean | undefined;
|
|
28
|
+
readonly robots?: {
|
|
29
|
+
/** Path prefixes disallowed on an indexable host, beyond the route-config exclusions. */
|
|
30
|
+
readonly disallow?: ReadonlyArray<string> | undefined;
|
|
31
|
+
/** `Content-Signal` value for an indexable host, e.g. `"search=yes, ai-input=yes, ai-train=yes"`. */
|
|
32
|
+
readonly contentSignal?: string | undefined;
|
|
33
|
+
/** Extra lines in the `User-agent: *` group on an indexable host. */
|
|
34
|
+
readonly directives?: ReadonlyArray<string> | undefined;
|
|
35
|
+
} | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Root-relative module whose `collections` export is a `SeoCollection[]`, or a
|
|
38
|
+
* function returning one (sync or async). It runs in the app's Vite pipeline,
|
|
39
|
+
* so content virtual modules (Fumadocs, MDX) and path aliases resolve.
|
|
40
|
+
*/
|
|
41
|
+
readonly collections?: string | undefined;
|
|
42
|
+
/** Canonical origin for rendered documents; serverEntry is relative to the Vite root. */
|
|
43
|
+
readonly markdown?: {
|
|
44
|
+
readonly origin: string;
|
|
45
|
+
readonly serverEntry?: string | undefined;
|
|
46
|
+
} | undefined;
|
|
47
|
+
/** Committed claims answers gate the rendered documents and graph heads at build time. */
|
|
48
|
+
readonly claims?: ClaimsOptions | undefined;
|
|
49
|
+
/** Root-relative module exporting the code-owned document facts. */
|
|
50
|
+
readonly facts?: string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Route files (globs relative to the routes directory) never evaluated for the
|
|
53
|
+
* graph: app surfaces that declare `staticData` but import server-runtime-only
|
|
54
|
+
* modules. Their paths stay out of the graph.
|
|
55
|
+
*/
|
|
56
|
+
readonly exclude?: ReadonlyArray<string> | undefined;
|
|
57
|
+
/** Relative to the Vite root. Defaults to `src/routes`. */
|
|
58
|
+
readonly routesDirectory?: string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Generate the route-config module and enforce per-group SEO coverage (the
|
|
61
|
+
* `seoRouteConfig` gate). Its robots exclusions join `robots.disallow`.
|
|
62
|
+
*/
|
|
63
|
+
readonly routeConfig?: Omit<SeoRouteConfigOptions, "routesDirectory"> | undefined;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/config/vite-graph-loader.d.ts
|
|
67
|
+
/** Identity and robots policy produced by the graph's host. */
|
|
68
|
+
interface SiteRuntime {
|
|
69
|
+
readonly origin: string;
|
|
70
|
+
readonly indexable: boolean;
|
|
71
|
+
readonly robots: Pick<RobotsConfig, "disallow" | "contentSignal" | "directives">;
|
|
72
|
+
}
|
|
73
|
+
/** A graph, plus the release of whatever producing it acquired. */
|
|
74
|
+
interface LoadedSeoGraph {
|
|
75
|
+
readonly graph: SeoGraph;
|
|
76
|
+
/** The site's identity and policy belong to the graph-producing host. */
|
|
77
|
+
readonly site: SiteRuntime;
|
|
78
|
+
/** Settings and artifacts from the same resolved app that produced the graph. */
|
|
79
|
+
readonly pagegraph?: {
|
|
80
|
+
readonly root: string;
|
|
81
|
+
readonly options: PagegraphOptions;
|
|
82
|
+
readonly facts: Facts;
|
|
83
|
+
} | undefined;
|
|
84
|
+
/** Called once the command is done with the graph, on success or failure. */
|
|
85
|
+
readonly dispose: () => Promise<void>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Produces the SEO graph for the `pagegraph` CLI. A plain promise on purpose: a
|
|
89
|
+
* config file must be writable without Effect, which the CLI keeps behind its
|
|
90
|
+
* `bin` (an optional peer dependency).
|
|
91
|
+
*/
|
|
92
|
+
/** JSON input that can be supplied by config files to an app graph export. */
|
|
93
|
+
type GraphLoaderInput = null | boolean | number | string | ReadonlyArray<GraphLoaderInput> | {
|
|
94
|
+
readonly [key: string]: GraphLoaderInput;
|
|
95
|
+
};
|
|
96
|
+
/** Per-invocation evaluation settings, separate from the graph module's JSON input. */
|
|
97
|
+
interface GraphLoaderEnvironment {
|
|
98
|
+
readonly command?: "serve" | "build" | undefined;
|
|
99
|
+
readonly mode?: string | undefined;
|
|
100
|
+
}
|
|
101
|
+
type SeoGraphLoader<Input extends GraphLoaderInput | void = void> = (input: Input, environment?: GraphLoaderEnvironment) => Promise<LoadedSeoGraph>;
|
|
102
|
+
interface ViteGraphLoaderOptions {
|
|
103
|
+
/** The app's Vite root — the directory its aliases and plugins resolve against. */
|
|
104
|
+
readonly root: string;
|
|
105
|
+
/** Module exporting the graph loader, root-relative (e.g. `/lib/seo/graph.ts`). */
|
|
106
|
+
readonly entry: string;
|
|
107
|
+
/** Site identity and robots policy for this graph. */
|
|
108
|
+
readonly site: SiteRuntime;
|
|
109
|
+
/** Named export on `entry` returning `Promise<SeoGraph>`. Defaults to `loadSeoGraph`. */
|
|
110
|
+
readonly exportName?: string | undefined;
|
|
111
|
+
/** Vite plugins the app's module graph needs — a content/MDX plugin, say. */
|
|
112
|
+
readonly plugins?: ReadonlyArray<PluginOption> | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* Bare or virtual module ids to replace with inert source, so a module that
|
|
115
|
+
* only resolves inside the app's server runtime does not break the load.
|
|
116
|
+
*/
|
|
117
|
+
readonly stubs?: Readonly<Record<string, string>> | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Variables to seed on `process.env` before the app's modules parse it. Vite
|
|
120
|
+
* exposes prefixed values through `import.meta.env`, so this is how a module
|
|
121
|
+
* that demands an env var at import time is satisfied. Existing values win —
|
|
122
|
+
* a real `.env` is never clobbered.
|
|
123
|
+
*/
|
|
124
|
+
readonly env?: Readonly<Record<string, string>> | undefined;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build a {@link SeoGraphLoader} that evaluates `entry` in a headless Vite
|
|
128
|
+
* server rooted at `root`, and returns what its `exportName` export resolves to.
|
|
129
|
+
*/
|
|
130
|
+
declare const viteGraphLoader: <Input extends GraphLoaderInput | void = void>(options: ViteGraphLoaderOptions) => SeoGraphLoader<Input>;
|
|
131
|
+
//#endregion
|
|
132
|
+
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-DZCwejuJ.d.ts.map
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
interface SeoRouteConfigOptions {
|
|
4
|
-
/** Absolute path the generated route-config module is written to. */
|
|
5
|
-
outputPath: string;
|
|
6
|
-
/** Route groups whose page leaves form the public surface. Include the parens. */
|
|
7
|
-
publicGroups: ReadonlyArray<string>;
|
|
8
|
-
/** Route groups where every page leaf must declare `staticData` or `head`. */
|
|
9
|
-
enforceCoverageIn: ReadonlyArray<string>;
|
|
10
|
-
/** Route groups whose page paths are disallowed in robots.txt (e.g. auth). */
|
|
11
|
-
disallowGroups?: ReadonlyArray<string> | undefined;
|
|
12
|
-
/** Public routes that live outside any group folder, so nothing discovers them. */
|
|
13
|
-
extraPublicRoutes?: ReadonlyArray<string> | undefined;
|
|
14
|
-
/** Reserved top-level segments that are not route-tree paths (e.g. an api mount). */
|
|
15
|
-
reservedExtras?: ReadonlyArray<string> | undefined;
|
|
16
|
-
/** Prefixes always disallowed in robots.txt, route tree or not. */
|
|
17
|
-
alwaysDisallow?: ReadonlyArray<string> | undefined;
|
|
18
|
-
/** Routes directory, relative to the Vite root. Defaults to `src/routes`. */
|
|
19
|
-
routesDirectory?: string | undefined;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Vite plugin that runs its own `@tanstack/router-generator` instance over the
|
|
23
|
-
* routes directory and derives the route-config module from the result.
|
|
24
|
-
*/
|
|
25
|
-
declare function seoRouteConfig(options: SeoRouteConfigOptions): Plugin;
|
|
26
|
-
//#endregion
|
|
27
|
-
export { SeoRouteConfigOptions, seoRouteConfig };
|
|
28
|
-
//# sourceMappingURL=vite.d.ts.map
|
|
1
|
+
import { n as seoRouteConfig, t as SeoRouteConfigOptions } from "./route-config-wneg6ktn.js";
|
|
2
|
+
export { type SeoRouteConfigOptions, seoRouteConfig };
|
package/dist/vite.js
CHANGED
|
@@ -1,308 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { open, stat } from "node:fs/promises";
|
|
3
|
-
import { basename, normalize, resolve } from "node:path";
|
|
4
|
-
import { Generator, getConfig } from "@tanstack/router-generator";
|
|
5
|
-
//#region src/vite/index.ts
|
|
6
|
-
/**
|
|
7
|
-
* Derives a route-config module from the parsed TanStack route tree.
|
|
8
|
-
*
|
|
9
|
-
* The route tree is the single source of truth for which paths exist; this
|
|
10
|
-
* plugin projects it into the small config the host consumes at runtime:
|
|
11
|
-
* - `robotsExclusions` — path prefixes disallowed in robots.txt (feed them to
|
|
12
|
-
* `renderRobots`).
|
|
13
|
-
* - `reservedSegments` — top-level path segments the app must not hand out (an
|
|
14
|
-
* org slug, a tenant name). Derived from the full public route surface — the
|
|
15
|
-
* `publicGroups` route groups plus the group-less `extraPublicRoutes` — which
|
|
16
|
-
* is parsed here but not emitted: "which pages are public" already has a
|
|
17
|
-
* canonical runtime answer in the SEO graph.
|
|
18
|
-
*
|
|
19
|
-
* It also enforces that every page in `enforceCoverageIn` declares its SEO: a
|
|
20
|
-
* page leaf missing both `staticData` and `head` fails dev startup and build.
|
|
21
|
-
*
|
|
22
|
-
* **Why we run our own Generator.** The parse must come from
|
|
23
|
-
* `@tanstack/router-generator` itself — a second, hand-rolled scanner would
|
|
24
|
-
* drift from TanStack's group/param/layout conventions. But routing our plugin
|
|
25
|
-
* through TanStack Start does not work: `start-plugin-core` builds its own
|
|
26
|
-
* generator-plugin array and drops `router.plugins` on the floor
|
|
27
|
-
* (tanstack/router#7768 — `plugins` is not public API there), and even when
|
|
28
|
-
* forced through, the router unplugin wraps `generator.run()` in
|
|
29
|
-
* `catch (e) { console.error(e) }`, so a plugin can never fail a build.
|
|
30
|
-
*
|
|
31
|
-
* So we instantiate `Generator` ourselves — `config.plugins` is natively
|
|
32
|
-
* honored on our own instance, and because we own the `.run()` call site a
|
|
33
|
-
* coverage violation throws straight out of `buildStart`. Cost: route files are
|
|
34
|
-
* parsed twice at startup (~40 files, milliseconds) — the host's router plugin
|
|
35
|
-
* still owns the real `routeTree.gen.ts`; ours is a shadow parse whose route
|
|
36
|
-
* tree is thrown away (see {@link inMemoryWriteFs}).
|
|
37
|
-
*
|
|
38
|
-
* `onRouteTreeChanged` fires on every generator run, but the config is written
|
|
39
|
-
* only when its content changes, and the output path lives outside the routes
|
|
40
|
-
* directory, so regeneration never feeds back into the watcher.
|
|
41
|
-
*/
|
|
42
|
-
const PLUGIN_NAME = "seo-route-config";
|
|
43
|
-
/** Match a route's group folder (`filePath` is relative to the routes directory). */
|
|
44
|
-
function groupOf(filePath, groups) {
|
|
45
|
-
return groups.find((group) => filePath.startsWith(`${group}/`));
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* A page leaf is a `.tsx` route file that is not a `route.tsx` layout. This
|
|
49
|
-
* excludes layout wrappers, `.ts` server routes (robots.txt / sitemap.xml /
|
|
50
|
-
* markdown mirrors), and any other non-page file — matching the leaves that
|
|
51
|
-
* contribute a crawlable URL.
|
|
52
|
-
*/
|
|
53
|
-
function isPageLeaf(node) {
|
|
54
|
-
return node.filePath.endsWith(".tsx") && basename(node.filePath) !== "route.tsx";
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Convert a group-prefixed routePath (e.g. "/(marketing)/blog/$slug") into the
|
|
58
|
-
* public URL form used by route-config ("/blog/*"): drop the group segment,
|
|
59
|
-
* collapse the first dynamic/splat segment and everything after it to "*", and
|
|
60
|
-
* trim the index trailing slash.
|
|
61
|
-
*/
|
|
62
|
-
function routePathToPublicUrl(routePath) {
|
|
63
|
-
const stripped = routePath.replace(/^\/\([^)]+\)/, "");
|
|
64
|
-
if (stripped === "" || stripped === "/") return "/";
|
|
65
|
-
const segments = stripped.split("/");
|
|
66
|
-
const dynamicIndex = segments.findIndex((segment) => segment.startsWith("$"));
|
|
67
|
-
if (dynamicIndex !== -1) return `${segments.slice(0, dynamicIndex).join("/")}/*`;
|
|
68
|
-
return stripped.endsWith("/") ? stripped.slice(0, -1) : stripped;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* First path segment of each route, reserved so it can't be claimed as an org
|
|
72
|
-
* slug. Strip the leading slash and a trailing "/*", then take the first
|
|
73
|
-
* segment ("/features/email" -> "features").
|
|
74
|
-
*/
|
|
75
|
-
function extractReservedSegments(routes) {
|
|
76
|
-
const segments = /* @__PURE__ */ new Set();
|
|
77
|
-
for (const route of routes) {
|
|
78
|
-
const first = route.replace(/^\//, "").replace(/\/\*$/, "").split("/")[0];
|
|
79
|
-
if (first) segments.add(first);
|
|
80
|
-
}
|
|
81
|
-
return Array.from(segments).sort();
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* robots.txt disallow prefixes are directory prefixes, so a splat route
|
|
85
|
-
* ("/reset-password/*") disallows the whole subtree ("/reset-password").
|
|
86
|
-
*/
|
|
87
|
-
function normalizeRoutePrefix(route) {
|
|
88
|
-
const cleaned = route.replace(/\/\*$/, "");
|
|
89
|
-
return cleaned === "" ? "/" : cleaned;
|
|
90
|
-
}
|
|
91
|
-
function deriveRouteConfig(routeNodes, options) {
|
|
92
|
-
const publicUrls = new Set(options.extraPublicRoutes);
|
|
93
|
-
const disallowedUrls = /* @__PURE__ */ new Set();
|
|
94
|
-
for (const node of routeNodes) {
|
|
95
|
-
const group = groupOf(node.filePath, options.publicGroups);
|
|
96
|
-
if (group === void 0 || !isPageLeaf(node) || node.routePath === void 0) continue;
|
|
97
|
-
const url = routePathToPublicUrl(node.routePath);
|
|
98
|
-
publicUrls.add(url);
|
|
99
|
-
if (options.disallowGroups.includes(group)) disallowedUrls.add(url);
|
|
100
|
-
}
|
|
101
|
-
const reservedSegments = [...extractReservedSegments([...publicUrls].sort()), ...options.reservedExtras].sort();
|
|
102
|
-
return {
|
|
103
|
-
robotsExclusions: [.../* @__PURE__ */ new Set([...options.alwaysDisallow, ...[...disallowedUrls].map(normalizeRoutePrefix)])].sort(),
|
|
104
|
-
reservedSegments
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Every page in an enforced group must declare its search presence. A leaf that
|
|
109
|
-
* passes neither `staticData` (from which the SEO graph reads `staticData.seo`)
|
|
110
|
-
* nor `head` has no path to correct metadata.
|
|
111
|
-
*/
|
|
112
|
-
function findCoverageViolations(routeNodes, options) {
|
|
113
|
-
const violations = [];
|
|
114
|
-
for (const node of routeNodes) {
|
|
115
|
-
const group = groupOf(node.filePath, options.enforceCoverageIn);
|
|
116
|
-
if (group === void 0 || !isPageLeaf(node)) continue;
|
|
117
|
-
const props = node.createFileRouteProps;
|
|
118
|
-
if (props?.has("staticData") || props?.has("head")) continue;
|
|
119
|
-
violations.push(`${node.filePath} (${group} page)`);
|
|
120
|
-
}
|
|
121
|
-
return violations;
|
|
122
|
-
}
|
|
123
|
-
function coverageMessage(violations) {
|
|
124
|
-
return `[${PLUGIN_NAME}] ${violations.length} page(s) declare neither staticData nor head. Every page in an SEO-enforced route group must declare its SEO — add staticData: { seo: ... } or a head() to each route:\n - ${violations.join("\n - ")}`;
|
|
125
|
-
}
|
|
126
|
-
const HEADER = `// Auto-generated by the ${PLUGIN_NAME} Vite plugin.\n// DO NOT EDIT MANUALLY — regenerated from the route tree on every dev/build.
|
|
127
|
-
`;
|
|
128
|
-
function renderRouteConfig(config) {
|
|
129
|
-
return `${HEADER}
|
|
130
|
-
export const routeConfig = ${JSON.stringify(config, null, 2)} as const;
|
|
131
|
-
|
|
132
|
-
export type RouteConfig = typeof routeConfig;
|
|
133
|
-
`;
|
|
134
|
-
}
|
|
135
|
-
function routeConfigGeneratorPlugin(options) {
|
|
136
|
-
const { outputPath } = options;
|
|
137
|
-
return {
|
|
138
|
-
name: PLUGIN_NAME,
|
|
139
|
-
init() {
|
|
140
|
-
if (!existsSync(resolve(outputPath, ".."))) throw new Error(`[${PLUGIN_NAME}] output directory does not exist: ${resolve(outputPath, "..")}`);
|
|
141
|
-
},
|
|
142
|
-
onRouteTreeChanged({ routeNodes }) {
|
|
143
|
-
const violations = findCoverageViolations(routeNodes, options);
|
|
144
|
-
if (violations.length > 0) throw new Error(coverageMessage(violations));
|
|
145
|
-
const content = renderRouteConfig(deriveRouteConfig(routeNodes, options));
|
|
146
|
-
if ((existsSync(outputPath) ? readFileSync(outputPath, "utf8") : void 0) !== content) writeFileSync(outputPath, content);
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* A `Generator` filesystem that reads real files but keeps every write in
|
|
152
|
-
* memory, so our shadow parse cannot touch the repo.
|
|
153
|
-
*
|
|
154
|
-
* The Generator writes through `safeFileWrite`: `writeFile(<tmp>)`, then
|
|
155
|
-
* `rename(<tmp>, <dest>)`. Two destinations exist, and neither may land:
|
|
156
|
-
* - the route tree — we point it at a throwaway path and hold it in memory, so
|
|
157
|
-
* the only route tree on disk stays the one the host's router plugin writes;
|
|
158
|
-
* - route source files — the Generator normalizes them (`createFileRoute` ids,
|
|
159
|
-
* code-split transforms). The host's generator already did that with its own
|
|
160
|
-
* config; ours is not the host's, so such a write is dropped rather than
|
|
161
|
-
* allowed to fight it.
|
|
162
|
-
*
|
|
163
|
-
* Reads and stats of real files go to disk, which keeps the Generator's mtime
|
|
164
|
-
* cache honest: a route edited mid-watch is always seen.
|
|
165
|
-
*/
|
|
166
|
-
function inMemoryWriteFs(routeTreePath) {
|
|
167
|
-
const written = /* @__PURE__ */ new Map();
|
|
168
|
-
let clock = 0n;
|
|
169
|
-
const tick = () => clock += 1n;
|
|
170
|
-
return {
|
|
171
|
-
/**
|
|
172
|
-
* The in-memory route tree is invisible to the watcher, so nothing else can
|
|
173
|
-
* mark it stale. Bumping its mtime before each run is what makes the
|
|
174
|
-
* Generator rebuild the tree — and therefore re-fire `onRouteTreeChanged`
|
|
175
|
-
* — on every run, not only when the set of routes changes. Coverage and the
|
|
176
|
-
* derived config depend on route *contents* (`staticData` / `head`), which
|
|
177
|
-
* leave the route tree itself untouched.
|
|
178
|
-
*/
|
|
179
|
-
touchRouteTree() {
|
|
180
|
-
const file = written.get(routeTreePath);
|
|
181
|
-
if (file) written.set(routeTreePath, {
|
|
182
|
-
content: file.content,
|
|
183
|
-
mtimeMs: tick()
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
fs: {
|
|
187
|
-
async stat(filePath) {
|
|
188
|
-
const file = written.get(filePath);
|
|
189
|
-
if (file) return {
|
|
190
|
-
mtimeMs: file.mtimeMs,
|
|
191
|
-
mode: 33188,
|
|
192
|
-
uid: 0,
|
|
193
|
-
gid: 0
|
|
194
|
-
};
|
|
195
|
-
const real = await stat(filePath, { bigint: true });
|
|
196
|
-
return {
|
|
197
|
-
mtimeMs: real.mtimeMs,
|
|
198
|
-
mode: Number(real.mode),
|
|
199
|
-
uid: Number(real.uid),
|
|
200
|
-
gid: Number(real.gid)
|
|
201
|
-
};
|
|
202
|
-
},
|
|
203
|
-
async readFile(filePath) {
|
|
204
|
-
const file = written.get(filePath);
|
|
205
|
-
if (file) return {
|
|
206
|
-
stat: { mtimeMs: file.mtimeMs },
|
|
207
|
-
fileContent: file.content
|
|
208
|
-
};
|
|
209
|
-
if (!existsSync(filePath)) return "file-not-existing";
|
|
210
|
-
const handle = await open(filePath, "r");
|
|
211
|
-
const fileStat = await handle.stat({ bigint: true });
|
|
212
|
-
const fileContent = (await handle.readFile()).toString();
|
|
213
|
-
await handle.close();
|
|
214
|
-
return {
|
|
215
|
-
stat: { mtimeMs: fileStat.mtimeMs },
|
|
216
|
-
fileContent
|
|
217
|
-
};
|
|
218
|
-
},
|
|
219
|
-
async writeFile(filePath, content) {
|
|
220
|
-
written.set(filePath, {
|
|
221
|
-
content,
|
|
222
|
-
mtimeMs: tick()
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
async rename(oldPath, newPath) {
|
|
226
|
-
const file = written.get(oldPath);
|
|
227
|
-
written.delete(oldPath);
|
|
228
|
-
if (file && newPath === routeTreePath) written.set(newPath, {
|
|
229
|
-
content: file.content,
|
|
230
|
-
mtimeMs: tick()
|
|
231
|
-
});
|
|
232
|
-
},
|
|
233
|
-
async chmod() {},
|
|
234
|
-
async chown() {}
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
function resolveOptions(options) {
|
|
239
|
-
return {
|
|
240
|
-
outputPath: options.outputPath,
|
|
241
|
-
publicGroups: options.publicGroups,
|
|
242
|
-
enforceCoverageIn: options.enforceCoverageIn,
|
|
243
|
-
disallowGroups: options.disallowGroups ?? [],
|
|
244
|
-
extraPublicRoutes: options.extraPublicRoutes ?? [],
|
|
245
|
-
reservedExtras: options.reservedExtras ?? [],
|
|
246
|
-
alwaysDisallow: options.alwaysDisallow ?? [],
|
|
247
|
-
routesDirectory: options.routesDirectory ?? "src/routes"
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Vite plugin that runs its own `@tanstack/router-generator` instance over the
|
|
252
|
-
* routes directory and derives the route-config module from the result.
|
|
253
|
-
*/
|
|
254
|
-
function seoRouteConfig(options) {
|
|
255
|
-
const resolved = resolveOptions(options);
|
|
256
|
-
let generator;
|
|
257
|
-
let touchRouteTree;
|
|
258
|
-
let routesDirectory;
|
|
259
|
-
/**
|
|
260
|
-
* A throw here propagates: it fails `vite build` and dev startup, which is
|
|
261
|
-
* the reason we own this call site instead of registering through Start.
|
|
262
|
-
*/
|
|
263
|
-
const run = async (event) => {
|
|
264
|
-
if (generator === void 0 || touchRouteTree === void 0) throw new Error(`[${PLUGIN_NAME}] generator was not initialized by configResolved`);
|
|
265
|
-
touchRouteTree();
|
|
266
|
-
await generator.run(event);
|
|
267
|
-
};
|
|
268
|
-
return {
|
|
269
|
-
name: PLUGIN_NAME,
|
|
270
|
-
enforce: "pre",
|
|
271
|
-
configResolved(config) {
|
|
272
|
-
const root = config.root;
|
|
273
|
-
const cacheDirectory = resolve(root, `node_modules/.cache/${PLUGIN_NAME}`);
|
|
274
|
-
routesDirectory = resolve(root, resolved.routesDirectory);
|
|
275
|
-
const generatorConfig = getConfig({
|
|
276
|
-
routesDirectory,
|
|
277
|
-
generatedRouteTree: resolve(cacheDirectory, "route-tree.gen.ts"),
|
|
278
|
-
tmpDir: resolve(cacheDirectory, "tmp"),
|
|
279
|
-
plugins: [routeConfigGeneratorPlugin(resolved)],
|
|
280
|
-
disableTypes: true,
|
|
281
|
-
enableRouteTreeFormatting: false,
|
|
282
|
-
disableLogging: true
|
|
283
|
-
}, root);
|
|
284
|
-
const memory = inMemoryWriteFs(generatorConfig.generatedRouteTree);
|
|
285
|
-
touchRouteTree = memory.touchRouteTree;
|
|
286
|
-
generator = new Generator({
|
|
287
|
-
config: generatorConfig,
|
|
288
|
-
root,
|
|
289
|
-
fs: memory.fs
|
|
290
|
-
});
|
|
291
|
-
},
|
|
292
|
-
async buildStart() {
|
|
293
|
-
await run();
|
|
294
|
-
},
|
|
295
|
-
async watchChange(id, { event }) {
|
|
296
|
-
const file = normalize(id);
|
|
297
|
-
if (routesDirectory === void 0 || !file.startsWith(routesDirectory)) return;
|
|
298
|
-
await run({
|
|
299
|
-
path: file,
|
|
300
|
-
type: event
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
//#endregion
|
|
1
|
+
import { i as seoRouteConfig } from "./route-config-35ZEzkNC.js";
|
|
306
2
|
export { seoRouteConfig };
|
|
307
|
-
|
|
308
|
-
//# sourceMappingURL=vite.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/core/wire.ts
|
|
2
|
+
function graphToJson(graph) {
|
|
3
|
+
return {
|
|
4
|
+
nodes: [...graph.nodes.values()].map((node) => ({
|
|
5
|
+
...node,
|
|
6
|
+
policy: {
|
|
7
|
+
...node.policy,
|
|
8
|
+
crumb: typeof node.policy.crumb === "function" ? void 0 : node.policy.crumb,
|
|
9
|
+
head: void 0
|
|
10
|
+
}
|
|
11
|
+
})),
|
|
12
|
+
edges: graph.edges,
|
|
13
|
+
collisions: graph.collisions
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function graphFromJson(json) {
|
|
17
|
+
return {
|
|
18
|
+
nodes: new Map(json.nodes.map((node) => [node.path, node])),
|
|
19
|
+
edges: [...json.edges],
|
|
20
|
+
collisions: json.collisions
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { graphToJson as n, graphFromJson as t };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=wire-B4RQMc4e.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-B4RQMc4e.js","names":[],"sources":["../src/core/wire.ts"],"sourcesContent":["/**\n * The graph as JSON, for shipping a build-time graph to a runtime that cannot\n * rebuild it (a Worker serving sitemap.xml). Node order is preserved — the\n * sitemap lists collection instances in collection order — and a function\n * `crumb` (render-time only) is dropped.\n */\n\nimport type { SeoEdge, SeoGraph, SeoNode, SeoSource } from \"./graph\";\n\nexport interface SeoGraphJson {\n readonly nodes: ReadonlyArray<SeoNode>;\n readonly edges: ReadonlyArray<SeoEdge>;\n readonly collisions?: ReadonlyArray<{ readonly path: string; readonly sources: ReadonlyArray<SeoSource> }> | undefined;\n}\n\nexport function graphToJson(graph: SeoGraph): SeoGraphJson {\n return {\n nodes: [...graph.nodes.values()].map((node) => ({\n ...node,\n // `node.head` carries the head as data; the declaration's render-only\n // fields (JSON-LD, Open Graph overrides) stay in the route module.\n policy: {\n ...node.policy,\n crumb: typeof node.policy.crumb === \"function\" ? undefined : node.policy.crumb,\n head: undefined,\n },\n })),\n edges: graph.edges,\n collisions: graph.collisions,\n };\n}\n\nexport function graphFromJson(json: SeoGraphJson): SeoGraph {\n return {\n nodes: new Map(json.nodes.map((node) => [node.path, node])),\n edges: [...json.edges],\n collisions: json.collisions,\n };\n}\n"],"mappings":";AAeA,SAAgB,YAAY,OAA+B;CACzD,OAAO;EACL,OAAO,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,UAAU;GAC9C,GAAG;GAGH,QAAQ;IACN,GAAG,KAAK;IACR,OAAO,OAAO,KAAK,OAAO,UAAU,aAAa,KAAA,IAAY,KAAK,OAAO;IACzE,MAAM,KAAA;GACR;EACF,EAAE;EACF,OAAO,MAAM;EACb,YAAY,MAAM;CACpB;AACF;AAEA,SAAgB,cAAc,MAA8B;CAC1D,OAAO;EACL,OAAO,IAAI,IAAI,KAAK,MAAM,KAAK,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;EAC1D,OAAO,CAAC,GAAG,KAAK,KAAK;EACrB,YAAY,KAAK;CACnB;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pagegraph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Route-declared SEO graph, audit, and agentic workflow toolkit for TanStack Start.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"seo",
|
|
@@ -46,17 +46,63 @@
|
|
|
46
46
|
"types": "./dist/react.d.ts",
|
|
47
47
|
"import": "./dist/react.js"
|
|
48
48
|
},
|
|
49
|
+
"./tanstack-start/server": {
|
|
50
|
+
"types": "./dist/tanstack-start/server.d.ts",
|
|
51
|
+
"import": "./dist/tanstack-start/server.js"
|
|
52
|
+
},
|
|
53
|
+
"./tanstack-start/markdown": {
|
|
54
|
+
"types": "./dist/tanstack-start/markdown.d.ts",
|
|
55
|
+
"import": "./dist/tanstack-start/markdown.js"
|
|
56
|
+
},
|
|
57
|
+
"./tanstack-start/react": {
|
|
58
|
+
"types": "./dist/tanstack-start/react.d.ts",
|
|
59
|
+
"import": "./dist/tanstack-start/react.js"
|
|
60
|
+
},
|
|
61
|
+
"./tanstack-start/prerender-worker": {
|
|
62
|
+
"types": "./dist/tanstack-start/prerender-worker.d.ts",
|
|
63
|
+
"import": "./dist/tanstack-start/prerender-worker.js"
|
|
64
|
+
},
|
|
49
65
|
"./vite": {
|
|
50
66
|
"types": "./dist/vite.d.ts",
|
|
67
|
+
"workerd": "./dist/build-only/vite.js",
|
|
68
|
+
"worker": "./dist/build-only/vite.js",
|
|
69
|
+
"browser": "./dist/build-only/vite.js",
|
|
51
70
|
"import": "./dist/vite.js"
|
|
52
71
|
},
|
|
53
72
|
"./config": {
|
|
54
73
|
"types": "./dist/config.d.ts",
|
|
74
|
+
"workerd": "./dist/build-only/config.js",
|
|
75
|
+
"worker": "./dist/build-only/config.js",
|
|
76
|
+
"browser": "./dist/build-only/config.js",
|
|
55
77
|
"import": "./dist/config.js"
|
|
56
78
|
},
|
|
57
79
|
"./audit": {
|
|
58
80
|
"types": "./dist/audit.d.ts",
|
|
81
|
+
"workerd": "./dist/build-only/audit.js",
|
|
82
|
+
"worker": "./dist/build-only/audit.js",
|
|
83
|
+
"browser": "./dist/build-only/audit.js",
|
|
59
84
|
"import": "./dist/audit.js"
|
|
85
|
+
},
|
|
86
|
+
"./tanstack-start": {
|
|
87
|
+
"types": "./dist/tanstack-start/index.d.ts",
|
|
88
|
+
"workerd": "./dist/build-only/tanstack-start.js",
|
|
89
|
+
"worker": "./dist/build-only/tanstack-start.js",
|
|
90
|
+
"browser": "./dist/build-only/tanstack-start.js",
|
|
91
|
+
"import": "./dist/tanstack-start/index.js"
|
|
92
|
+
},
|
|
93
|
+
"./oxlint": {
|
|
94
|
+
"types": "./dist/oxlint.d.ts",
|
|
95
|
+
"workerd": "./dist/build-only/oxlint.js",
|
|
96
|
+
"worker": "./dist/build-only/oxlint.js",
|
|
97
|
+
"browser": "./dist/build-only/oxlint.js",
|
|
98
|
+
"import": "./dist/oxlint.js"
|
|
99
|
+
},
|
|
100
|
+
"./claims": {
|
|
101
|
+
"types": "./dist/claims.d.ts",
|
|
102
|
+
"workerd": "./dist/build-only/claims.js",
|
|
103
|
+
"worker": "./dist/build-only/claims.js",
|
|
104
|
+
"browser": "./dist/build-only/claims.js",
|
|
105
|
+
"import": "./dist/claims.js"
|
|
60
106
|
}
|
|
61
107
|
},
|
|
62
108
|
"publishConfig": {
|
|
@@ -75,21 +121,26 @@
|
|
|
75
121
|
"release:validate": "bun run scripts/validate-release.ts",
|
|
76
122
|
"release": "bun run release:check && bun run tegami publish"
|
|
77
123
|
},
|
|
78
|
-
"dependencies": {
|
|
79
|
-
"@tanstack/router-generator": "^1.167.21"
|
|
80
|
-
},
|
|
81
124
|
"devDependencies": {
|
|
82
|
-
"@
|
|
125
|
+
"@cloudflare/vite-plugin": "^1.62.5",
|
|
83
126
|
"@effect/ai-typesafe": "4.0.0",
|
|
84
127
|
"@effect/platform-bun": "4.0.0",
|
|
85
128
|
"@effect/platform-node-shared": "4.0.0",
|
|
86
129
|
"@effect/vitest": "4.0.0",
|
|
87
|
-
"@
|
|
130
|
+
"@noble/hashes": "^2.4.0",
|
|
131
|
+
"@opencode/sdk": "2.0.22",
|
|
132
|
+
"@tanstack/react-router": "^1.170.26",
|
|
133
|
+
"@tanstack/react-start": "^1.168.56",
|
|
134
|
+
"@tanstack/router-generator": "^1.167.21",
|
|
88
135
|
"@types/node": "^24.0.0",
|
|
89
136
|
"@types/react": "19.2.18",
|
|
137
|
+
"@types/react-dom": "19.2.3",
|
|
138
|
+
"@vitejs/plugin-react": "^6.0.6",
|
|
90
139
|
"effect": "4.0.0",
|
|
91
140
|
"lighthouse": "13.4.1",
|
|
141
|
+
"oxlint": "^1.83.0",
|
|
92
142
|
"react": "19.2.8",
|
|
143
|
+
"react-dom": "19.2.8",
|
|
93
144
|
"schema-dts": "^2.0.0",
|
|
94
145
|
"tegami": "1.4.0",
|
|
95
146
|
"tsdown": "^0.22.13",
|
|
@@ -101,10 +152,13 @@
|
|
|
101
152
|
"@effect/ai-typesafe": "^4.0.0",
|
|
102
153
|
"@effect/platform-bun": "^4.0.0",
|
|
103
154
|
"@tanstack/react-router": "^1.170.0",
|
|
155
|
+
"@tanstack/router-generator": "^1.167.21",
|
|
104
156
|
"effect": "^4.0.0",
|
|
105
157
|
"lighthouse": ">=13.0.0",
|
|
106
158
|
"react": "^19.0.0",
|
|
107
|
-
"vite": "^8.0.0"
|
|
159
|
+
"vite": "^8.0.0",
|
|
160
|
+
"@tanstack/react-start": "^1.168.56",
|
|
161
|
+
"react-dom": "^19.0.0"
|
|
108
162
|
},
|
|
109
163
|
"peerDependenciesMeta": {
|
|
110
164
|
"@effect/ai-typesafe": {
|
|
@@ -113,6 +167,9 @@
|
|
|
113
167
|
"@effect/platform-bun": {
|
|
114
168
|
"optional": true
|
|
115
169
|
},
|
|
170
|
+
"@tanstack/router-generator": {
|
|
171
|
+
"optional": true
|
|
172
|
+
},
|
|
116
173
|
"effect": {
|
|
117
174
|
"optional": true
|
|
118
175
|
},
|
|
@@ -124,6 +181,12 @@
|
|
|
124
181
|
},
|
|
125
182
|
"vite": {
|
|
126
183
|
"optional": true
|
|
184
|
+
},
|
|
185
|
+
"@tanstack/react-start": {
|
|
186
|
+
"optional": true
|
|
187
|
+
},
|
|
188
|
+
"react-dom": {
|
|
189
|
+
"optional": true
|
|
127
190
|
}
|
|
128
191
|
},
|
|
129
192
|
"overrides": {
|