@andesine/pages 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -2
  4. package/dist/answers.d.ts +6 -0
  5. package/dist/astro/content.astro +118 -0
  6. package/dist/astro/middleware.js +39 -0
  7. package/dist/astro/middleware.js.map +1 -0
  8. package/dist/astro/routes/answers.js +129 -0
  9. package/dist/astro/routes/answers.js.map +1 -0
  10. package/dist/astro/routes/entry-pages.js +22 -0
  11. package/dist/astro/routes/entry-pages.js.map +1 -0
  12. package/dist/astro/routes/llms-full.js +12 -0
  13. package/dist/astro/routes/llms-full.js.map +1 -0
  14. package/dist/astro/routes/llms.js +12 -0
  15. package/dist/astro/routes/llms.js.map +1 -0
  16. package/dist/astro/routes/markdown-alias.js +14 -0
  17. package/dist/astro/routes/markdown-alias.js.map +1 -0
  18. package/dist/astro/routes/markdown.js +12 -0
  19. package/dist/astro/routes/markdown.js.map +1 -0
  20. package/dist/astro/routes/not-found.astro +12 -0
  21. package/dist/astro/routes/robots.js +9 -0
  22. package/dist/astro/routes/robots.js.map +1 -0
  23. package/dist/astro/routes/search.js +42 -0
  24. package/dist/astro/routes/search.js.map +1 -0
  25. package/dist/astro/routes/sitemap.js +12 -0
  26. package/dist/astro/routes/sitemap.js.map +1 -0
  27. package/dist/astro/routes/skill.js +13 -0
  28. package/dist/astro/routes/skill.js.map +1 -0
  29. package/dist/astro/routes/skills.js +24 -0
  30. package/dist/astro/routes/skills.js.map +1 -0
  31. package/dist/astro/routes/social.js +91 -0
  32. package/dist/astro/routes/social.js.map +1 -0
  33. package/dist/astro/routes/static.astro +53 -0
  34. package/dist/astro/routes/well-known-llms.js +2 -0
  35. package/dist/astro.d.ts +34 -0
  36. package/dist/astro.js +60 -0
  37. package/dist/astro.js.map +1 -0
  38. package/dist/chunks/assets-D6WJmZ05.js +21 -0
  39. package/dist/chunks/assets-D6WJmZ05.js.map +1 -0
  40. package/dist/chunks/catalog-DU4BUrpw.js +28 -0
  41. package/dist/chunks/catalog-DU4BUrpw.js.map +1 -0
  42. package/dist/chunks/config-CuH5JSI4.js +184 -0
  43. package/dist/chunks/config-CuH5JSI4.js.map +1 -0
  44. package/dist/chunks/files-BxqiQzFj.js +62 -0
  45. package/dist/chunks/files-BxqiQzFj.js.map +1 -0
  46. package/dist/chunks/hash-D4piIIQ6.js +11 -0
  47. package/dist/chunks/hash-D4piIIQ6.js.map +1 -0
  48. package/dist/chunks/index-D8wL2B5s.d.ts +571 -0
  49. package/dist/chunks/index-vsu3hISM.d.ts +62 -0
  50. package/dist/chunks/limits-Ppc7BQPG.js +45 -0
  51. package/dist/chunks/limits-Ppc7BQPG.js.map +1 -0
  52. package/dist/chunks/markdown-DKJSQxR2.js +42 -0
  53. package/dist/chunks/markdown-DKJSQxR2.js.map +1 -0
  54. package/dist/chunks/mdx-components-CUUDTapN.d.ts +9 -0
  55. package/dist/chunks/normalize-DG5nxOLm.js +361 -0
  56. package/dist/chunks/normalize-DG5nxOLm.js.map +1 -0
  57. package/dist/chunks/paths-75QgA3Qs.js +16 -0
  58. package/dist/chunks/paths-75QgA3Qs.js.map +1 -0
  59. package/dist/chunks/prepare-lnuw_ycM.js +219 -0
  60. package/dist/chunks/prepare-lnuw_ycM.js.map +1 -0
  61. package/dist/chunks/read-DCv1lmcM.js +57 -0
  62. package/dist/chunks/read-DCv1lmcM.js.map +1 -0
  63. package/dist/chunks/request-DOuBHGQs.js +92 -0
  64. package/dist/chunks/request-DOuBHGQs.js.map +1 -0
  65. package/dist/chunks/response-CtzaKMWH.js +15 -0
  66. package/dist/chunks/response-CtzaKMWH.js.map +1 -0
  67. package/dist/chunks/site-DY-uZ0PP.js +419 -0
  68. package/dist/chunks/site-DY-uZ0PP.js.map +1 -0
  69. package/dist/chunks/skill-CBTGUHQH.js +85 -0
  70. package/dist/chunks/skill-CBTGUHQH.js.map +1 -0
  71. package/dist/chunks/sources-Cm6NdIen.js +236 -0
  72. package/dist/chunks/sources-Cm6NdIen.js.map +1 -0
  73. package/dist/chunks/stored-CabomV-C.js +354 -0
  74. package/dist/chunks/stored-CabomV-C.js.map +1 -0
  75. package/dist/chunks/stored-CcEfvCQH.js +475 -0
  76. package/dist/chunks/stored-CcEfvCQH.js.map +1 -0
  77. package/dist/chunks/types-DBzuXfSe.d.ts +53 -0
  78. package/dist/chunks/view-C-9ZM1zK.js +745 -0
  79. package/dist/chunks/view-C-9ZM1zK.js.map +1 -0
  80. package/dist/chunks/view-Dlf_VRI7.d.ts +151 -0
  81. package/dist/client.d.ts +73 -0
  82. package/dist/client.js +295 -0
  83. package/dist/client.js.map +1 -0
  84. package/dist/collections.d.ts +7 -0
  85. package/dist/collections.js +215 -0
  86. package/dist/collections.js.map +1 -0
  87. package/dist/entry-pages.d.ts +10 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.js +38 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/integration.d.ts +16 -0
  92. package/dist/integration.js +435 -0
  93. package/dist/integration.js.map +1 -0
  94. package/dist/llms-full.d.ts +5 -0
  95. package/dist/llms.d.ts +5 -0
  96. package/dist/markdown-alias.d.ts +7 -0
  97. package/dist/markdown.d.ts +7 -0
  98. package/dist/middleware.d.ts +8 -0
  99. package/dist/robots.d.ts +5 -0
  100. package/dist/runtime.d.ts +81 -0
  101. package/dist/runtime.js +56 -0
  102. package/dist/runtime.js.map +1 -0
  103. package/dist/scripts/restore-navigation.js +1 -0
  104. package/dist/search.d.ts +6 -0
  105. package/dist/sitemap.d.ts +5 -0
  106. package/dist/skill.d.ts +5 -0
  107. package/dist/skills.d.ts +10 -0
  108. package/dist/social.d.ts +7 -0
  109. package/dist/well-known-llms.d.ts +2 -0
  110. package/package.json +103 -3
@@ -0,0 +1,236 @@
1
+ import { n as toSegments, t as toHref } from "./paths-75QgA3Qs.js";
2
+ import { i as toFileMarkdown, t as loadAndesineSource } from "./stored-CabomV-C.js";
3
+ import { n as loadOpenAPISource } from "./stored-CcEfvCQH.js";
4
+ import { r as toLabel$1 } from "./markdown-DKJSQxR2.js";
5
+ import { mdxAdapter } from "@andesine/converters/mdx";
6
+ import { getCollection } from "astro:content";
7
+ //#region src/navigation/tree.ts
8
+ /** Returns the nodes from the root to the node of the page, or an empty list. */
9
+ const findTrail = (nodes, page) => {
10
+ for (const node of nodes) {
11
+ if (node.page === page) return [node];
12
+ const trail = node.children ? findTrail(node.children, page) : [];
13
+ if (trail.length) return [node, ...trail];
14
+ }
15
+ return [];
16
+ };
17
+ /** Returns the nodes with a page, in reading order. */
18
+ const flattenTree = (nodes) => {
19
+ return nodes.flatMap((node) => {
20
+ return [...node.page ? [node] : [], ...node.children ? flattenTree(node.children) : []];
21
+ });
22
+ };
23
+ const createNavigationItems = (nodes, trail) => {
24
+ return nodes.map((node) => {
25
+ return {
26
+ id: node.id,
27
+ label: node.label,
28
+ href: node.page?.href,
29
+ method: node.method,
30
+ current: node === trail.at(-1),
31
+ active: trail.includes(node),
32
+ children: node.children ? createNavigationItems(node.children, trail) : []
33
+ };
34
+ });
35
+ };
36
+ //#endregion
37
+ //#region src/navigation/sections.ts
38
+ const createSection = (config, sources, fallbackHref) => {
39
+ const navigation = sources.flatMap((source) => source.navigation);
40
+ const href = flattenTree(navigation)[0]?.page?.href ?? sources[0]?.href ?? fallbackHref;
41
+ return {
42
+ ...config,
43
+ href,
44
+ sources,
45
+ navigation
46
+ };
47
+ };
48
+ /** Without configured sections, one implicit section holds all sources. */
49
+ const createSections = (config, sources) => {
50
+ const base = toSegments(config.base, "Site base");
51
+ const mountHref = (sourceID) => {
52
+ const mount = config.sources.find((source) => source.id === sourceID)?.mount ?? "/";
53
+ return toHref(base, toSegments(mount, "Mount"));
54
+ };
55
+ if (!config.sections.length) return [createSection({
56
+ id: "default",
57
+ label: config.name,
58
+ links: []
59
+ }, sources, mountHref(sources[0]?.id))];
60
+ return config.sections.map((section) => {
61
+ return createSection(section, sources.filter((source) => section.sources.includes(source.id)), mountHref(section.sources[0]));
62
+ });
63
+ };
64
+ //#endregion
65
+ //#region src/sources/files/load.ts
66
+ const toLabel = (name) => {
67
+ return name.replace(/[-_]+/g, " ").replace(/^./, (letter) => letter.toUpperCase());
68
+ };
69
+ const toMarkdown = (entry, label, pageURL) => {
70
+ try {
71
+ return toFileMarkdown(entry.body, pageURL);
72
+ } catch (error) {
73
+ throw new Error(`${label}: ${error.message}`);
74
+ }
75
+ };
76
+ const sortTree = (nodes) => {
77
+ return [...nodes].sort((a, b) => a.order - b.order || a.path.localeCompare(b.path, "en")).map(({ id, label, page, children }) => {
78
+ return {
79
+ id,
80
+ label,
81
+ page,
82
+ children: children && sortTree(children)
83
+ };
84
+ }).filter((node) => node.page || node.children?.length);
85
+ };
86
+ const createFilesSource = (source, options, entries) => {
87
+ const { base } = options;
88
+ const prefix = [...toSegments(base, "Site base"), ...toSegments(source.mount, `Source "${source.id}" mount`)];
89
+ const root = [];
90
+ const folders = /* @__PURE__ */ new Map();
91
+ const pages = [];
92
+ const getFolder = (segments) => {
93
+ if (!segments.length) return root;
94
+ const path = segments.join("/");
95
+ const existing = folders.get(path);
96
+ if (existing) return existing.children;
97
+ const folder = {
98
+ id: `${source.id}:${path}/`,
99
+ label: toLabel(segments.at(-1)),
100
+ path,
101
+ order: 0,
102
+ children: []
103
+ };
104
+ folders.set(path, folder);
105
+ getFolder(segments.slice(0, -1)).push(folder);
106
+ return folder.children;
107
+ };
108
+ for (const entry of entries) {
109
+ const data = entry.data;
110
+ const label = `Source "${source.id}", file "${entry.id}"`;
111
+ const path = entry.id.replace(/\.mdx?$/, "");
112
+ const segments = toSegments(path, label);
113
+ const folderSegments = segments.slice(0, -1);
114
+ const isIndex = segments.at(-1) === "index";
115
+ const route = data.slug === void 0 ? isIndex ? folderSegments : segments : toSegments(data.slug, `${label}, slug`);
116
+ const href = toHref(prefix, route);
117
+ const page = {
118
+ sourceID: source.id,
119
+ id: entry.id,
120
+ href,
121
+ title: data.title,
122
+ description: data.description,
123
+ toc: data.toc,
124
+ layout: data.layout,
125
+ searchHidden: data.searchHidden,
126
+ markdown: toMarkdown(entry, label, new URL(href, options.site)),
127
+ updatedAt: data.updatedAt?.toISOString(),
128
+ content: {
129
+ type: "file",
130
+ entry
131
+ }
132
+ };
133
+ const node = {
134
+ id: `${source.id}:${entry.id}`,
135
+ label: data.navigationLabel ?? data.title,
136
+ path,
137
+ order: data.order,
138
+ page: data.navigationHidden ? void 0 : page
139
+ };
140
+ pages.push(page);
141
+ if (isIndex && folderSegments.length) {
142
+ getFolder(folderSegments);
143
+ Object.assign(folders.get(folderSegments.join("/")), {
144
+ label: node.label,
145
+ order: node.order,
146
+ page: node.page
147
+ });
148
+ } else getFolder(folderSegments).push(node);
149
+ }
150
+ return {
151
+ id: source.id,
152
+ href: toHref(prefix),
153
+ pages,
154
+ navigation: sortTree(root)
155
+ };
156
+ };
157
+ const loadFilesSource = async (source, options) => {
158
+ const entries = await getCollection(source.id);
159
+ return createFilesSource(source, options, entries);
160
+ };
161
+ //#endregion
162
+ //#region src/sources/openapi/links.ts
163
+ /** Reads a string prop, written as `id="…"` or `id={"…"}`. */
164
+ const readAttribute = (node, name) => {
165
+ const value = node.attributes?.find((attribute) => attribute.name === name)?.value;
166
+ if (typeof value === "string") return value;
167
+ try {
168
+ const parsed = value ? JSON.parse(value.value) : void 0;
169
+ return typeof parsed === "string" ? parsed : void 0;
170
+ } catch {
171
+ return;
172
+ }
173
+ };
174
+ const findOperationTags = (node) => {
175
+ return node.type.startsWith("mdxJsx") && node.name === "Operation" ? [node] : (node.children ?? []).flatMap(findOperationTags);
176
+ };
177
+ /** Parses the page as MDX, like its source, so code and other elements are never changed. */
178
+ const parseOperationTags = async (markdown) => {
179
+ if (!markdown.includes("<Operation")) return [];
180
+ try {
181
+ return findOperationTags(await mdxAdapter.parse(markdown));
182
+ } catch {
183
+ return [];
184
+ }
185
+ };
186
+ /**
187
+ * Replaces `<Operation>` tags in Markdown alternatives with links to the operations' Markdown,
188
+ * which agents can follow. Pages change in place, so navigation keeps pointing to them.
189
+ */
190
+ const linkOperationTags = async (sources, site) => {
191
+ const operations = /* @__PURE__ */ new Map();
192
+ const toLink = (tag) => {
193
+ const target = operations.get(`${readAttribute(tag, "source")}:${readAttribute(tag, "id")}`);
194
+ const operation = target?.content.type === "openapi" ? target.content.operation : void 0;
195
+ if (!target || !operation) return void 0;
196
+ const url = new URL(`${target.href}index.md`, site).href;
197
+ return `[${toLabel$1(target.title)}](${url}): \`${operation.method.toUpperCase()} ${operation.path}\``;
198
+ };
199
+ sources.forEach((source) => {
200
+ source.pages.forEach((page) => {
201
+ if (page.content.type === "openapi" && page.content.operation) operations.set(`${source.id}:${page.content.operation.id}`, page);
202
+ });
203
+ });
204
+ if (!operations.size) return sources;
205
+ await Promise.all(sources.flatMap((source) => source.pages).map(async (page) => {
206
+ (await parseOperationTags(page.markdown)).reverse().forEach((tag) => {
207
+ const start = tag.position?.start.offset;
208
+ const end = tag.position?.end.offset;
209
+ const link = toLink(tag);
210
+ if (start === void 0 || end === void 0 || link === void 0) return;
211
+ page.markdown = `${page.markdown.slice(0, start)}${link}${page.markdown.slice(end)}`;
212
+ });
213
+ }));
214
+ return sources;
215
+ };
216
+ //#endregion
217
+ //#region src/sources/index.ts
218
+ /** Loads the sources from their collections. */
219
+ const loadStaticSources = async (config) => {
220
+ const sources = await Promise.all(config.sources.flatMap((source) => {
221
+ if (source.type === "files") return [loadFilesSource(source, {
222
+ base: config.base,
223
+ site: config.site
224
+ })];
225
+ if (source.type === "openapi") return [loadOpenAPISource(source, {
226
+ base: config.base,
227
+ site: config.site
228
+ })];
229
+ return [loadAndesineSource(source, config.base)];
230
+ }));
231
+ return linkOperationTags(sources, config.site);
232
+ };
233
+ //#endregion
234
+ export { findTrail as a, createNavigationItems as i, loadFilesSource as n, flattenTree as o, createSections as r, loadStaticSources as t };
235
+
236
+ //# sourceMappingURL=sources-Cm6NdIen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources-Cm6NdIen.js","names":["toLabel"],"sources":["../../src/navigation/tree.ts","../../src/navigation/sections.ts","../../src/sources/files/load.ts","../../src/sources/openapi/links.ts","../../src/sources/index.ts"],"sourcesContent":["import type { NavigationItem } from \"../context\";\nimport type { SourceNode, SourcePage } from \"../sources\";\n\n/** Returns the nodes from the root to the node of the page, or an empty list. */\nconst findTrail = (nodes: SourceNode[], page: SourcePage): SourceNode[] => {\n for (const node of nodes) {\n if (node.page === page) return [node];\n\n const trail = node.children ? findTrail(node.children, page) : [];\n\n if (trail.length) return [node, ...trail];\n }\n\n return [];\n};\n/** Returns the nodes with a page, in reading order. */\nconst flattenTree = (nodes: SourceNode[]): SourceNode[] => {\n return nodes.flatMap((node) => {\n return [...(node.page ? [node] : []), ...(node.children ? flattenTree(node.children) : [])];\n });\n};\nconst createNavigationItems = (nodes: SourceNode[], trail: SourceNode[]): NavigationItem[] => {\n return nodes.map((node) => {\n return {\n id: node.id,\n label: node.label,\n href: node.page?.href,\n method: node.method,\n current: node === trail.at(-1),\n active: trail.includes(node),\n children: node.children ? createNavigationItems(node.children, trail) : []\n };\n });\n};\n\nexport { findTrail, flattenTree, createNavigationItems };\n","import type { IconLinkConfig, PagesConfig } from \"../config\";\nimport { toHref, toSegments } from \"../routing/paths\";\nimport type { SourceData, SourceNode } from \"../sources\";\nimport { flattenTree } from \"./tree\";\n\ninterface Section {\n id: string;\n label: string;\n icon?: string;\n links: IconLinkConfig[];\n /** URL of the first page, or of the first source mount. */\n href: string;\n sources: SourceData[];\n navigation: SourceNode[];\n}\n\nconst createSection = (\n config: Pick<Section, \"id\" | \"label\" | \"icon\" | \"links\">,\n sources: SourceData[],\n /** Used when the sources have no pages. */\n fallbackHref: string\n): Section => {\n const navigation = sources.flatMap((source) => source.navigation);\n const href = flattenTree(navigation)[0]?.page?.href ?? sources[0]?.href ?? fallbackHref;\n\n return { ...config, href, sources, navigation };\n};\n/** Without configured sections, one implicit section holds all sources. */\nconst createSections = (config: PagesConfig, sources: SourceData[]): Section[] => {\n const base = toSegments(config.base, \"Site base\");\n const mountHref = (sourceID?: string): string => {\n const mount = config.sources.find((source) => source.id === sourceID)?.mount ?? \"/\";\n\n return toHref(base, toSegments(mount, \"Mount\"));\n };\n\n if (!config.sections.length) {\n return [\n createSection(\n { id: \"default\", label: config.name, links: [] },\n sources,\n mountHref(sources[0]?.id)\n )\n ];\n }\n\n return config.sections.map((section) => {\n return createSection(\n section,\n sources.filter((source) => section.sources.includes(source.id)),\n mountHref(section.sources[0])\n );\n });\n};\n\nexport { createSections };\nexport type { Section };\n","import { getCollection } from \"astro:content\";\nimport type { FilesSourceConfig } from \"../../config\";\nimport { toFileMarkdown } from \"../../content/markdown\";\nimport { toHref, toSegments } from \"../../routing/paths\";\nimport type { FileEntry, SourceData, SourceNode, SourcePage } from \"../types\";\nimport type { FileMetadata } from \"./schema\";\n\ninterface TreeNode extends SourceNode {\n path: string;\n order: number;\n children?: TreeNode[];\n}\n\ninterface FilesSourceOptions {\n base: string;\n /** The site URL, for absolute links in Markdown alternatives. */\n site: string;\n}\n\nconst toLabel = (name: string): string => {\n return name.replace(/[-_]+/g, \" \").replace(/^./, (letter) => letter.toUpperCase());\n};\nconst toMarkdown = (entry: FileEntry, label: string, pageURL: URL): string => {\n try {\n return toFileMarkdown(entry.body, pageURL);\n } catch (error) {\n throw new Error(`${label}: ${(error as Error).message}`);\n }\n};\nconst sortTree = (nodes: TreeNode[]): SourceNode[] => {\n return [...nodes]\n .sort((a, b) => a.order - b.order || a.path.localeCompare(b.path, \"en\"))\n .map(({ id, label, page, children }) => {\n return {\n id,\n label,\n page,\n children: children && sortTree(children)\n };\n })\n .filter((node) => node.page || node.children?.length);\n};\nconst createFilesSource = (\n source: FilesSourceConfig,\n options: FilesSourceOptions,\n entries: FileEntry[]\n): SourceData => {\n const { base } = options;\n const prefix = [\n ...toSegments(base, \"Site base\"),\n ...toSegments(source.mount, `Source \"${source.id}\" mount`)\n ];\n const root: TreeNode[] = [];\n const folders = new Map<string, TreeNode>();\n const pages: SourcePage[] = [];\n const getFolder = (segments: string[]): TreeNode[] => {\n if (!segments.length) return root;\n\n const path = segments.join(\"/\");\n const existing = folders.get(path);\n\n if (existing) return existing.children!;\n\n const folder: TreeNode = {\n id: `${source.id}:${path}/`,\n label: toLabel(segments.at(-1)!),\n path,\n order: 0,\n children: []\n };\n\n folders.set(path, folder);\n getFolder(segments.slice(0, -1)).push(folder);\n\n return folder.children!;\n };\n\n for (const entry of entries) {\n const data = entry.data as FileMetadata;\n const label = `Source \"${source.id}\", file \"${entry.id}\"`;\n const path = entry.id.replace(/\\.mdx?$/, \"\");\n const segments = toSegments(path, label);\n const folderSegments = segments.slice(0, -1);\n const isIndex = segments.at(-1) === \"index\";\n const route =\n data.slug === undefined\n ? isIndex\n ? folderSegments\n : segments\n : toSegments(data.slug, `${label}, slug`);\n const href = toHref(prefix, route);\n const page: SourcePage = {\n sourceID: source.id,\n id: entry.id,\n href,\n title: data.title,\n description: data.description,\n toc: data.toc,\n layout: data.layout,\n searchHidden: data.searchHidden,\n markdown: toMarkdown(entry, label, new URL(href, options.site)),\n updatedAt: data.updatedAt?.toISOString(),\n content: { type: \"file\", entry }\n };\n const node: TreeNode = {\n id: `${source.id}:${entry.id}`,\n label: data.navigationLabel ?? data.title,\n path,\n order: data.order,\n page: data.navigationHidden ? undefined : page\n };\n\n pages.push(page);\n\n // A folder's index page is the landing page of its group.\n if (isIndex && folderSegments.length) {\n getFolder(folderSegments);\n Object.assign(folders.get(folderSegments.join(\"/\"))!, {\n label: node.label,\n order: node.order,\n page: node.page\n });\n } else {\n getFolder(folderSegments).push(node);\n }\n }\n\n return { id: source.id, href: toHref(prefix), pages, navigation: sortTree(root) };\n};\nconst loadFilesSource = async (\n source: FilesSourceConfig,\n options: FilesSourceOptions\n): Promise<SourceData> => {\n const entries = (await getCollection(source.id)) as FileEntry[];\n\n return createFilesSource(source, options, entries);\n};\n\nexport { createFilesSource, loadFilesSource };\n","import { mdxAdapter } from \"@andesine/converters/mdx\";\nimport { toLabel } from \"../../output/markdown\";\nimport type { SourceData, SourcePage } from \"../types\";\n\ninterface ParsedNode {\n type: string;\n name?: string | null;\n attributes?: ParsedAttribute[];\n children?: ParsedNode[];\n position?: { start: { offset?: number }; end: { offset?: number } };\n}\n\ninterface ParsedAttribute {\n type: string;\n name?: string;\n value?: string | { value: string } | null;\n}\n\n/** Reads a string prop, written as `id=\"…\"` or `id={\"…\"}`. */\nconst readAttribute = (node: ParsedNode, name: string): string | undefined => {\n const value = node.attributes?.find((attribute) => attribute.name === name)?.value;\n\n if (typeof value === \"string\") return value;\n\n try {\n const parsed: unknown = value ? JSON.parse(value.value) : undefined;\n\n return typeof parsed === \"string\" ? parsed : undefined;\n } catch {\n return undefined;\n }\n};\nconst findOperationTags = (node: ParsedNode): ParsedNode[] => {\n const isTag = node.type.startsWith(\"mdxJsx\") && node.name === \"Operation\";\n\n return isTag ? [node] : (node.children ?? []).flatMap(findOperationTags);\n};\n/** Parses the page as MDX, like its source, so code and other elements are never changed. */\nconst parseOperationTags = async (markdown: string): Promise<ParsedNode[]> => {\n if (!markdown.includes(\"<Operation\")) return [];\n\n try {\n return findOperationTags((await mdxAdapter.parse(markdown)) as ParsedNode);\n } catch {\n // Pages that are not MDX, e.g. Markdown files, cannot render the component.\n return [];\n }\n};\n/**\n * Replaces `<Operation>` tags in Markdown alternatives with links to the operations' Markdown,\n * which agents can follow. Pages change in place, so navigation keeps pointing to them.\n */\nconst linkOperationTags = async (sources: SourceData[], site: string): Promise<SourceData[]> => {\n const operations = new Map<string, SourcePage>();\n const toLink = (tag: ParsedNode): string | undefined => {\n const target = operations.get(`${readAttribute(tag, \"source\")}:${readAttribute(tag, \"id\")}`);\n const operation = target?.content.type === \"openapi\" ? target.content.operation : undefined;\n\n if (!target || !operation) return undefined;\n\n const url = new URL(`${target.href}index.md`, site).href;\n\n return `[${toLabel(target.title)}](${url}): \\`${operation.method.toUpperCase()} ${operation.path}\\``;\n };\n\n sources.forEach((source) => {\n source.pages.forEach((page) => {\n if (page.content.type === \"openapi\" && page.content.operation) {\n operations.set(`${source.id}:${page.content.operation.id}`, page);\n }\n });\n });\n\n if (!operations.size) return sources;\n\n await Promise.all(\n sources\n .flatMap((source) => source.pages)\n .map(async (page) => {\n const tags = await parseOperationTags(page.markdown);\n\n // From the end, so the offsets of earlier tags stay valid.\n tags.reverse().forEach((tag) => {\n const start = tag.position?.start.offset;\n const end = tag.position?.end.offset;\n const link = toLink(tag);\n\n if (start === undefined || end === undefined || link === undefined) return;\n\n page.markdown = `${page.markdown.slice(0, start)}${link}${page.markdown.slice(end)}`;\n });\n })\n );\n\n return sources;\n};\n\nexport { linkOperationTags };\n","import type { PagesConfig } from \"../config\";\nimport { loadAndesineSource } from \"./andesine/stored\";\nimport { loadFilesSource } from \"./files/load\";\nimport { linkOperationTags } from \"./openapi/links\";\nimport { loadOpenAPISource } from \"./openapi/stored\";\nimport type { SourceData } from \"./types\";\n\n/** Loads the sources from their collections. */\nconst loadStaticSources = async (config: PagesConfig): Promise<SourceData[]> => {\n const sources = await Promise.all(\n config.sources.flatMap((source) => {\n if (source.type === \"files\") {\n return [loadFilesSource(source, { base: config.base, site: config.site })];\n }\n\n if (source.type === \"openapi\") {\n return [loadOpenAPISource(source, { base: config.base, site: config.site })];\n }\n\n return [loadAndesineSource(source, config.base)];\n })\n );\n\n return linkOperationTags(sources, config.site);\n};\n\nexport { loadStaticSources };\nexport type * from \"./types\";\n"],"mappings":"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"}
@@ -0,0 +1,354 @@
1
+ import { n as toSegments, t as toHref } from "./paths-75QgA3Qs.js";
2
+ import { n as prepareContent } from "./prepare-lnuw_ycM.js";
3
+ import { toStructuredContent } from "@andesine/sdk";
4
+ import { toMDX } from "@andesine/converters/mdx";
5
+ import { getCollection } from "astro:content";
6
+ //#region src/content/fragments.ts
7
+ const slotNames = ["summary", "aside"];
8
+ const contentTypes = /* @__PURE__ */ new Set([
9
+ "image",
10
+ "element",
11
+ "horizontalRule",
12
+ "codeBlock",
13
+ "table"
14
+ ]);
15
+ /** Empty fragments have only empty paragraphs. */
16
+ const hasContent = (node) => {
17
+ return Boolean(node.text?.trim()) || contentTypes.has(node.type) || (node.content ?? []).some(hasContent);
18
+ };
19
+ /**
20
+ * Splits an entry into the page body and the `summary` and `aside` fragments, which the layout
21
+ * shows in their own places. Other named fragments stay in the body.
22
+ */
23
+ const splitFragments = (entry) => {
24
+ const document = entry.content;
25
+ const slots = slotNames.flatMap((name) => {
26
+ const fragment = entry.fragments[name];
27
+ return fragment ? [{
28
+ name,
29
+ label: fragment.name,
30
+ content: fragment.content
31
+ }] : [];
32
+ });
33
+ const labels = new Set(slots.map((slot) => slot.label));
34
+ const isSlot = (node) => {
35
+ return node.type === "fragment" && labels.has(String(node.attrs?.name));
36
+ };
37
+ const result = { body: {
38
+ ...document,
39
+ content: (document.content ?? []).filter((node) => !isSlot(node))
40
+ } };
41
+ for (const slot of slots) if (hasContent(slot.content)) result[slot.name] = slot.content;
42
+ return result;
43
+ };
44
+ //#endregion
45
+ //#region src/content/visibility.ts
46
+ const tagPattern = /<Visibility\s+for=(["'])(agents|humans)\1\s*>|<\/Visibility\s*>/g;
47
+ /** Ranges of fenced and inline code, where tags are only text. */
48
+ const findCode = (source) => {
49
+ const ranges = [];
50
+ let fence;
51
+ let offset = 0;
52
+ for (const line of source.split("\n")) {
53
+ const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
54
+ const end = offset + line.length;
55
+ if (fence) {
56
+ if (marker?.[0] === fence.marker[0] && marker.length >= fence.marker.length) {
57
+ ranges.push([fence.start, end]);
58
+ fence = void 0;
59
+ }
60
+ } else if (marker) fence = {
61
+ marker,
62
+ start: offset
63
+ };
64
+ else for (const match of line.matchAll(/`+[^`]*`+/g)) ranges.push([offset + match.index, offset + match.index + match[0].length]);
65
+ offset = end + 1;
66
+ }
67
+ if (fence) ranges.push([fence.start, source.length]);
68
+ return ranges;
69
+ };
70
+ /** Removes the indent that all lines share, as JSX children are often indented. */
71
+ const dedent = (text) => {
72
+ const lines = text.split("\n");
73
+ const indents = lines.filter((line) => line.trim()).map((line) => /^[ \t]*/.exec(line)[0].length);
74
+ const indent = Math.min(...indents);
75
+ return indents.length ? lines.map((line) => line.slice(indent)).join("\n") : text;
76
+ };
77
+ /**
78
+ * Keeps the `<Visibility>` blocks of one audience, without their tags, and removes the others.
79
+ * Tags in code stay. Blocks cannot be nested.
80
+ */
81
+ const filterVisibility = (source, audience) => {
82
+ const code = findCode(source);
83
+ const tags = [...source.matchAll(tagPattern)].map((match) => {
84
+ return {
85
+ start: match.index,
86
+ end: match.index + match[0].length,
87
+ audience: match[2]
88
+ };
89
+ }).filter((tag) => !code.some(([start, end]) => tag.start >= start && tag.end <= end));
90
+ let result = "";
91
+ let position = 0;
92
+ for (let index = 0; index < tags.length; index++) {
93
+ const open = tags[index];
94
+ const close = tags[index + 1];
95
+ if (!(open.audience !== void 0 && close !== void 0 && !close.audience)) throw new Error("Each <Visibility> needs a closing tag, and blocks cannot be nested.");
96
+ result += source.slice(position, open.start);
97
+ result += open.audience === audience ? dedent(source.slice(open.end, close.start)) : "";
98
+ position = close.end;
99
+ index++;
100
+ }
101
+ return `${result}${source.slice(position)}`.replace(/\n{3,}/g, "\n\n");
102
+ };
103
+ //#endregion
104
+ //#region src/content/markdown.ts
105
+ const rewriteLinks = (node, linkURL) => {
106
+ const props = node.attrs?.props;
107
+ const href = props?.href;
108
+ const isLinkedCard = node.type === "element" && node.attrs?.name === "Card" && typeof href === "string";
109
+ return {
110
+ ...node,
111
+ attrs: isLinkedCard ? {
112
+ ...node.attrs,
113
+ props: {
114
+ ...props,
115
+ href: linkURL(href)
116
+ }
117
+ } : node.attrs,
118
+ marks: node.marks?.map((mark) => {
119
+ return mark.type === "link" && typeof mark.attrs?.href === "string" ? {
120
+ ...mark,
121
+ attrs: {
122
+ ...mark.attrs,
123
+ href: linkURL(mark.attrs.href)
124
+ }
125
+ } : mark;
126
+ }),
127
+ content: node.content?.map((child) => rewriteLinks(child, linkURL))
128
+ };
129
+ };
130
+ /**
131
+ * Converts Andesine documents to the page's Markdown alternative. Elements become MDX
132
+ * components, like in MDX files.
133
+ */
134
+ const toPageMarkdown = async (documents, options) => {
135
+ return (await Promise.all(documents.map((document) => {
136
+ return toMDX(rewriteLinks(document, options.linkURL), {
137
+ title: "omit",
138
+ properties: "omit",
139
+ imageURL: options.imageURL,
140
+ encode: { elements: { Visibility: (node, context) => {
141
+ return (node.attrs?.props)?.for === "agents" ? context.children(node) : null;
142
+ } } }
143
+ });
144
+ }))).map((part) => part.trim()).filter(Boolean).join("\n\n");
145
+ };
146
+ const toAbsolute = (target, base) => {
147
+ return /^[a-z][\w+.-]*:/i.test(target) || target.startsWith("{") ? target : new URL(target, base).href;
148
+ };
149
+ /** Makes link and image targets absolute, outside of inline code. */
150
+ const rewriteLine = (line, base) => {
151
+ return line.split(/(`+[^`]*`+)/).map((part, index) => {
152
+ return index % 2 ? part : part.replace(/(\]\()(<[^>]*>|[^)\s]+)/g, (_, start, target) => {
153
+ const wrapped = target.startsWith("<");
154
+ const url = toAbsolute(wrapped ? target.slice(1, -1) : target, base);
155
+ return `${start}${wrapped ? `<${url}>` : url}`;
156
+ }).replace(/^(\s{0,3}\[[^\]]+\]:\s*)(<[^>]*>|\S+)/, (_, start, target) => {
157
+ const wrapped = target.startsWith("<");
158
+ const url = toAbsolute(wrapped ? target.slice(1, -1) : target, base);
159
+ return `${start}${wrapped ? `<${url}>` : url}`;
160
+ }).replace(/\b(href|src)=(["'])([^"']+)\2/g, (_, name, quote, target) => {
161
+ return `${name}=${quote}${toAbsolute(target, base)}${quote}`;
162
+ });
163
+ }).join("");
164
+ };
165
+ /**
166
+ * Prepares the source of a file page as its Markdown alternative. It keeps the content for
167
+ * agents, removes the leading MDX `import` and `export` statements, which only work in the
168
+ * build, and makes links absolute.
169
+ */
170
+ const toFileMarkdown = (body = "", pageURL) => {
171
+ let fence;
172
+ return filterVisibility(body, "agents").replace(/^(?:(?:import|export)\s[\s\S]*?(?:\n\s*\n|$))+/, "").split("\n").map((line) => {
173
+ const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1];
174
+ if (fence) {
175
+ if (marker?.[0] === fence[0] && marker.length >= fence.length) fence = void 0;
176
+ return line;
177
+ }
178
+ if (marker) {
179
+ fence = marker;
180
+ return line;
181
+ }
182
+ return rewriteLine(line, pageURL);
183
+ }).join("\n").trim();
184
+ };
185
+ //#endregion
186
+ //#region src/sources/andesine/load.ts
187
+ const isIndex = (slugPath) => slugPath.endsWith("/index");
188
+ /** Reads the standard documentation properties. Missing properties use their defaults. */
189
+ const readMetadata = (entry) => {
190
+ const properties = toStructuredContent(entry).properties;
191
+ const text = (key) => {
192
+ const value = properties[key];
193
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
194
+ };
195
+ const flag = (key, fallback) => {
196
+ const value = properties[key];
197
+ return typeof value === "boolean" ? value : fallback;
198
+ };
199
+ return {
200
+ description: text("description"),
201
+ searchHidden: flag("searchHidden", false),
202
+ toc: flag("toc", true),
203
+ layout: text("layout") === "wide" ? "wide" : "docs"
204
+ };
205
+ };
206
+ /**
207
+ * Returns the site URL of a published slug path, relative to the source's root collection. An
208
+ * `index` entry has its collection's URL, like an `index` file.
209
+ */
210
+ const createHrefResolver = (source, base, rootSlugPath) => {
211
+ const label = `Source "${source.id}"`;
212
+ const prefix = [...toSegments(base, "Site base"), ...toSegments(source.mount, `${label} mount`)];
213
+ const root = toSegments(rootSlugPath, `${label} root`);
214
+ return (slugPath) => {
215
+ const segments = toSegments(slugPath, label).slice(root.length);
216
+ return toHref(prefix, isIndex(slugPath) ? segments.slice(0, -1) : segments);
217
+ };
218
+ };
219
+ /** Lists the entries of a collection and its descendants, with their collections. */
220
+ const listTreeEntries = (collection) => {
221
+ return [...collection.entries.map((entry) => ({
222
+ entry,
223
+ collection
224
+ })), ...collection.collections.flatMap(listTreeEntries)];
225
+ };
226
+ /** An `index` entry takes its collection's name, so its page is not titled "Index". */
227
+ const getTitle = ({ entry, collection }) => {
228
+ return isIndex(entry.slugPath) ? collection.name : entry.name;
229
+ };
230
+ /** Prepares the content of published entries as pages. */
231
+ const createAndesinePages = async (source, tree, entries, options) => {
232
+ const label = `Source "${source.id}"`;
233
+ const toPageHref = createHrefResolver(source, options.base, tree.slugPath);
234
+ const treeEntries = new Map(listTreeEntries(tree).map((item) => [item.entry.id, item]));
235
+ const links = new Map([...treeEntries.values()].flatMap(({ entry }) => {
236
+ const href = toPageHref(entry.slugPath);
237
+ return [[entry.path, href], [entry.slugPath, href]];
238
+ }));
239
+ const linkURL = (href) => {
240
+ const [, path = "", suffix = ""] = /^([^?#]*)(.*)$/s.exec(href) ?? [];
241
+ return links.has(path) ? `${links.get(path)}${suffix}` : href;
242
+ };
243
+ return Promise.all(entries.map(async (entry) => {
244
+ const data = readMetadata(entry);
245
+ const treeEntry = treeEntries.get(entry.id);
246
+ const findAsset = (assetID) => {
247
+ const asset = entry.assets.find((item) => item.assetID === assetID && item.variant === "display");
248
+ if (!asset) throw new Error(`${label}, entry ${entry.id}: image ${assetID} is missing.`);
249
+ return asset;
250
+ };
251
+ const href = toPageHref(entry.slugPath);
252
+ const pageURL = new URL(href, options.site);
253
+ const fragments = splitFragments(entry);
254
+ const imageURL = async (image) => {
255
+ return options.assets.resolve(findAsset(image.attrs?.assetID), entry.id);
256
+ };
257
+ const prepare = (document) => {
258
+ return document && prepareContent(document, {
259
+ imageURL,
260
+ linkURL,
261
+ highlight: options.highlight
262
+ });
263
+ };
264
+ const [[body, summary, aside], markdown] = await Promise.all([Promise.all([
265
+ prepare(fragments.body),
266
+ prepare(fragments.summary),
267
+ prepare(fragments.aside)
268
+ ]), toPageMarkdown([
269
+ fragments.summary,
270
+ fragments.body,
271
+ fragments.aside
272
+ ].filter((item) => item !== void 0), {
273
+ imageURL: async (image) => new URL(await imageURL(image), pageURL).href,
274
+ linkURL: (link) => new URL(linkURL(link), pageURL).href
275
+ })]).catch((error) => {
276
+ throw new Error(`${label}, entry ${entry.id}: ${error.message}`);
277
+ });
278
+ return {
279
+ sourceID: source.id,
280
+ id: entry.id,
281
+ href,
282
+ title: treeEntry ? getTitle(treeEntry) : entry.name,
283
+ description: data.description,
284
+ toc: data.toc,
285
+ layout: data.layout,
286
+ searchHidden: data.searchHidden,
287
+ markdown,
288
+ updatedAt: entry.version.createdAt,
289
+ content: {
290
+ type: "andesine",
291
+ ...body,
292
+ iconCSS: [
293
+ body,
294
+ summary,
295
+ aside
296
+ ].map((item) => item?.iconCSS ?? "").join(""),
297
+ summary: summary?.nodes,
298
+ aside: aside?.nodes
299
+ }
300
+ };
301
+ }));
302
+ };
303
+ /**
304
+ * Maps a publication tree to navigation, like the app's explorer: collections first, then
305
+ * entries. The pages come from the same snapshot as the tree, so each entry has one.
306
+ */
307
+ const createAndesineSource = (source, tree, base, prepared) => {
308
+ const toPageHref = createHrefResolver(source, base, tree.slugPath);
309
+ const pagesByID = new Map(prepared.map((page) => [page.id, { ...page }]));
310
+ const pages = listTreeEntries(tree).map((item) => pagesByID.get(item.entry.id));
311
+ const toNode = (page) => {
312
+ return {
313
+ id: `${source.id}:${page.id}`,
314
+ label: page.title,
315
+ page
316
+ };
317
+ };
318
+ const build = (collection) => {
319
+ const groups = collection.collections.flatMap((child) => {
320
+ const landing = child.entries.find((entry) => isIndex(entry.slugPath));
321
+ const children = build(child);
322
+ const node = {
323
+ id: `${source.id}:${child.id}`,
324
+ label: child.name,
325
+ page: landing && pagesByID.get(landing.id),
326
+ children
327
+ };
328
+ return node.page || children.length ? [node] : [];
329
+ });
330
+ const isRoot = collection === tree;
331
+ const leaves = [...collection.entries].reverse().filter((entry) => isRoot || !isIndex(entry.slugPath)).map((entry) => toNode(pagesByID.get(entry.id)));
332
+ return [...groups, ...leaves];
333
+ };
334
+ return {
335
+ id: source.id,
336
+ href: toPageHref(tree.slugPath),
337
+ pages,
338
+ navigation: build(tree)
339
+ };
340
+ };
341
+ //#endregion
342
+ //#region src/sources/andesine/stored.ts
343
+ const recordID = "source";
344
+ /** Reads a publication that the build loaded. */
345
+ const loadAndesineSource = async (source, base) => {
346
+ const [record] = await getCollection(source.id);
347
+ const stored = record?.data;
348
+ if (!stored) throw new Error(`Source "${source.id}": the build did not load the publication.`);
349
+ return createAndesineSource(source, stored.tree, base, stored.pages);
350
+ };
351
+ //#endregion
352
+ export { filterVisibility as a, toFileMarkdown as i, recordID as n, createAndesinePages as r, loadAndesineSource as t };
353
+
354
+ //# sourceMappingURL=stored-CabomV-C.js.map