@junejs/server 1.0.0-dev.28 → 1.0.0-dev.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/llms-links.d.ts +6 -0
- package/dist/llms-links.js +35 -0
- package/dist/pipeline.js +6 -1
- package/package.json +2 -2
- package/src/llms-links.ts +51 -0
- package/src/pipeline.ts +7 -5
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { u as RouteResolver } from "./pipeline-cCR6MGNK.js";
|
|
2
|
+
import { LlmsLink } from "@junejs/core/discovery";
|
|
3
|
+
//#region src/llms-links.d.ts
|
|
4
|
+
declare function collectLlmsLinks(origin: string, routes: string[], resolve: RouteResolver): Promise<LlmsLink[]>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { collectLlmsLinks };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/llms-links.ts
|
|
2
|
+
const DEFAULT_SECTION = "Pages";
|
|
3
|
+
const mdPath = (path) => path === "/" ? "/index.md" : `${path.replace(/\/+$/, "")}.md`;
|
|
4
|
+
async function collectLlmsLinks(origin, routes, resolve) {
|
|
5
|
+
const links = [];
|
|
6
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7
|
+
for (const route of routes) {
|
|
8
|
+
const resolved = await resolve(route);
|
|
9
|
+
if (!resolved || !("def" in resolved)) continue;
|
|
10
|
+
const { def } = resolved;
|
|
11
|
+
if (def.llms === false) continue;
|
|
12
|
+
const dynamic = route.includes("[");
|
|
13
|
+
const declared = typeof def.llms === "function" ? await def.llms() : def.llms;
|
|
14
|
+
const entries = declared ? Array.isArray(declared) ? declared : [declared] : dynamic ? [] : [{}];
|
|
15
|
+
const meta = typeof def.metadata === "object" ? def.metadata : void 0;
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
const path = entry.path ?? (dynamic ? void 0 : route);
|
|
18
|
+
if (!path) continue;
|
|
19
|
+
const url = origin + (def.md === false ? path : mdPath(path));
|
|
20
|
+
if (seen.has(url)) continue;
|
|
21
|
+
seen.add(url);
|
|
22
|
+
const own = path === route;
|
|
23
|
+
links.push({
|
|
24
|
+
title: entry.title ?? (own ? meta?.title : void 0) ?? path,
|
|
25
|
+
url,
|
|
26
|
+
description: entry.description ?? (own ? meta?.description : void 0),
|
|
27
|
+
section: entry.section ?? DEFAULT_SECTION,
|
|
28
|
+
optional: entry.optional === true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return links;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { collectLlmsLinks };
|
package/dist/pipeline.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { iconLetter } from "./icon-letter.js";
|
|
2
|
+
import { collectLlmsLinks } from "./llms-links.js";
|
|
2
3
|
import { webManifest } from "./web-manifest.js";
|
|
3
4
|
import { SEGMENT_HEADER, TITLE_HEADER, encodeTitle, negotiate } from "./negotiate.js";
|
|
4
5
|
import { ensureScope, runInScope, setRequestLocale } from "@junejs/db";
|
|
@@ -172,7 +173,11 @@ function createPipeline(cfg) {
|
|
|
172
173
|
}
|
|
173
174
|
async function discovery(url) {
|
|
174
175
|
switch (url.pathname) {
|
|
175
|
-
case "/llms.txt":
|
|
176
|
+
case "/llms.txt": {
|
|
177
|
+
const routes = await cfg.routeList();
|
|
178
|
+
const links = await collectLlmsLinks(url.origin, routes, cfg.resolve);
|
|
179
|
+
return text(llmsTxt(url.origin, routes, agent, docConfig.site, links), "text/markdown; charset=utf-8");
|
|
180
|
+
}
|
|
176
181
|
case "/robots.txt": return text(robotsTxt(url.origin), "text/plain; charset=utf-8");
|
|
177
182
|
case "/sitemap.xml": return text(sitemapXml(url.origin, await cfg.routeList(), cfg.i18n), "application/xml; charset=utf-8");
|
|
178
183
|
case "/.well-known/api-catalog": return text(JSON.stringify(apiCatalog(url.origin, agent)), "application/linkset+json");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.29",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"rolldown": "^1.1.0"
|
|
141
141
|
},
|
|
142
142
|
"peerDependencies": {
|
|
143
|
-
"@junejs/core": ">=0.2.0-dev.
|
|
143
|
+
"@junejs/core": ">=0.2.0-dev.50",
|
|
144
144
|
"react": "^19.2.0",
|
|
145
145
|
"react-dom": "^19.2.0",
|
|
146
146
|
"react-server-dom-webpack": "19.2.7",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// /llms.txt links, collected from the routes themselves. Each route may export
|
|
2
|
+
// `llms` (see LlmsEntry in @junejs/core/route): a static route defaults to one link
|
|
3
|
+
// under "Pages" built from its static metadata; a dynamic route — a [param]
|
|
4
|
+
// template an agent can't fetch — lists each real page it serves, or is skipped.
|
|
5
|
+
// Links point at the markdown projection (<path>.md) unless the route turned md off,
|
|
6
|
+
// so an agent reads the page rather than its HTML (llmstxt.org's recommendation).
|
|
7
|
+
import type { LlmsLink } from "@junejs/core/discovery";
|
|
8
|
+
import type { LlmsEntry } from "@junejs/core/route";
|
|
9
|
+
|
|
10
|
+
import type { RouteResolver } from "./pipeline";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SECTION = "Pages";
|
|
13
|
+
|
|
14
|
+
const mdPath = (path: string) => (path === "/" ? "/index.md" : `${path.replace(/\/+$/, "")}.md`);
|
|
15
|
+
|
|
16
|
+
export async function collectLlmsLinks(origin: string, routes: string[], resolve: RouteResolver): Promise<LlmsLink[]> {
|
|
17
|
+
const links: LlmsLink[] = [];
|
|
18
|
+
const seen = new Set<string>();
|
|
19
|
+
for (const route of routes) {
|
|
20
|
+
// A dynamic template resolves against its own pattern ("/docs/[slug]" matches
|
|
21
|
+
// /docs/:slug), which is all we need: the route definition, not its data.
|
|
22
|
+
const resolved = await resolve(route);
|
|
23
|
+
if (!resolved || !("def" in resolved)) continue; // resource routes have no page to list
|
|
24
|
+
const { def } = resolved;
|
|
25
|
+
if (def.llms === false) continue;
|
|
26
|
+
|
|
27
|
+
const dynamic = route.includes("[");
|
|
28
|
+
const declared = typeof def.llms === "function" ? await def.llms() : def.llms;
|
|
29
|
+
const entries: LlmsEntry[] = declared ? (Array.isArray(declared) ? declared : [declared]) : dynamic ? [] : [{}];
|
|
30
|
+
// Static metadata only: a metadata FUNCTION needs loader data we don't run here.
|
|
31
|
+
const meta = typeof def.metadata === "object" ? def.metadata : undefined;
|
|
32
|
+
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const path = entry.path ?? (dynamic ? undefined : route);
|
|
35
|
+
if (!path) continue; // a dynamic entry must name its page
|
|
36
|
+
const url = origin + (def.md === false ? path : mdPath(path));
|
|
37
|
+
// dedupe on the FINAL url: "/a" and "/a/" both render /a.md, so they're one page
|
|
38
|
+
if (seen.has(url)) continue;
|
|
39
|
+
seen.add(url);
|
|
40
|
+
const own = path === route; // the route's own page → its metadata describes it
|
|
41
|
+
links.push({
|
|
42
|
+
title: entry.title ?? (own ? meta?.title : undefined) ?? path,
|
|
43
|
+
url,
|
|
44
|
+
description: entry.description ?? (own ? meta?.description : undefined),
|
|
45
|
+
section: entry.section ?? DEFAULT_SECTION,
|
|
46
|
+
optional: entry.optional === true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return links;
|
|
51
|
+
}
|
package/src/pipeline.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import type { Resources } from "@junejs/core/resources";
|
|
51
51
|
|
|
52
52
|
import { iconLetter } from "./icon-letter";
|
|
53
|
+
import { collectLlmsLinks } from "./llms-links";
|
|
53
54
|
import { webManifest } from "./web-manifest";
|
|
54
55
|
import { negotiate, TITLE_HEADER, SEGMENT_HEADER, encodeTitle } from "./negotiate";
|
|
55
56
|
|
|
@@ -480,11 +481,12 @@ export function createPipeline(cfg: PipelineConfig): Pipeline {
|
|
|
480
481
|
|
|
481
482
|
async function discovery(url: URL): Promise<Response | null> {
|
|
482
483
|
switch (url.pathname) {
|
|
483
|
-
case "/llms.txt":
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
);
|
|
484
|
+
case "/llms.txt": {
|
|
485
|
+
const routes = await cfg.routeList();
|
|
486
|
+
// sections, descriptions, and Optional come from each route's `llms` export
|
|
487
|
+
const links = await collectLlmsLinks(url.origin, routes, cfg.resolve);
|
|
488
|
+
return text(llmsTxt(url.origin, routes, agent, docConfig.site, links), "text/markdown; charset=utf-8");
|
|
489
|
+
}
|
|
488
490
|
case "/robots.txt":
|
|
489
491
|
return text(robotsTxt(url.origin), "text/plain; charset=utf-8");
|
|
490
492
|
case "/sitemap.xml":
|