@lexibase/rdx-next 4.2.0 → 5.0.1

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.
@@ -1,4 +1,4 @@
1
- import { lstat, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
1
+ import { lstat, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
4
  const defaultFiles = [
@@ -14,6 +14,7 @@ const generated = [
14
14
  'app/(rdx-locales)/[locale]/not-found.tsx',
15
15
  'app/(rdx-locales)/[locale]/docs/[[...slug]]/page.tsx',
16
16
  'app/(rdx-locales)/[locale]/rdx-search/[source]/route.ts',
17
+ 'app/(rdx-locales)/[locale]/docs/llms.txt/route.ts',
17
18
  ]
18
19
 
19
20
  /** Root layouts keep HTML language correct on the server, without middleware. */
@@ -34,6 +35,18 @@ export async function prepareLocaleRoutes(project, runtime, template) {
34
35
  }
35
36
  return current
36
37
  }
38
+ // An existing public index owns this URL. Keep it when upgrading a project.
39
+ const publicIndex = path.join(project.rootDir, 'public', 'docs', 'llms.txt')
40
+ const hasPublicIndex = await stat(publicIndex).then(
41
+ (entry) => entry.isFile(),
42
+ (error) => {
43
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return false
44
+ throw error
45
+ }
46
+ )
47
+ if (hasPublicIndex)
48
+ await rm(await safe('app/docs/llms.txt/route.ts'), { force: true })
49
+
37
50
  const marker = await safe('.rdx-localization.json')
38
51
  let owned = false
39
52
  try {
@@ -130,6 +143,23 @@ export async function generateStaticParams() {
130
143
  return project.localization!.locales.flatMap(locale => getSearchSources(getLocalizedProject(project, locale.path)).map(source => ({ locale: locale.path, ...source })))
131
144
  }
132
145
  export async function GET(_request: Request, { params }: { params: Promise<{ locale: string, source: string }> }) { const { locale, source } = await params; return getSearchResponse(getLocalizedProject(await getProject(), locale), source) }
146
+ `
147
+ )
148
+ await emit(
149
+ 'app/(rdx-locales)/[locale]/docs/llms.txt/route.ts',
150
+ `import { getLlmIndexResponse, getLocalizedProject } from '${provider}'
151
+ import { getProject } from '../../../../../rdx-project'
152
+ export const dynamic = 'force-static'
153
+ export const dynamicParams = false
154
+ export async function generateStaticParams() {
155
+ return (await getProject()).localization!.locales.map(locale => ({ locale: locale.path }))
156
+ }
157
+ export async function GET(_request: Request, { params }: { params: Promise<{ locale: string }> }) {
158
+ const project = await getProject()
159
+ const { locale } = await params
160
+ if (!project.localization?.locales.some(entry => entry.path === locale)) return new Response(null, { status: 404 })
161
+ return getLlmIndexResponse(getLocalizedProject(project, locale))
162
+ }
133
163
  `
134
164
  )
135
165
  await writeFile(marker, JSON.stringify({ version: 1 }) + '\n')
package/config.mjs CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  createRdxProject,
7
7
  resolveProjectPath,
8
8
  getContentSources,
9
+ snapshotDatesFilename,
9
10
  } from '@lexibase/rdx-core/config'
10
11
  import { createJiti } from 'jiti'
11
12
 
@@ -49,7 +50,9 @@ async function contentFiles(directory) {
49
50
  const filename = path.join(directory, entry.name)
50
51
  if (entry.isDirectory()) return contentFiles(filename)
51
52
  return entry.isFile() &&
52
- (/\.mdx?$/.test(entry.name) || entry.name === '_category.json')
53
+ (/\.mdx?$/.test(entry.name) ||
54
+ entry.name === '_category.json' ||
55
+ entry.name === snapshotDatesFilename)
53
56
  ? [filename]
54
57
  : []
55
58
  })
@@ -21,6 +21,17 @@ declare function getRdxSitemap(project: RdxProject, { documentPath, }?: {
21
21
  documentPath?: (source: RdxContentSource, page: RdxDocPage) => string;
22
22
  }): MetadataRoute.Sitemap;
23
23
 
24
+ type DocumentPath = (source: RdxContentSource, page: Pick<RdxDocPage, 'slug' | 'sourcePath'>) => string;
25
+ declare function getMarkdownHref(htmlPath: string): string;
26
+ declare function getLlmIndexResponse(project: RdxProject, documentPath?: DocumentPath): Response;
27
+ declare function getMarkdownPaths(project: RdxProject, documentPath?: DocumentPath): {
28
+ path: string[];
29
+ }[];
30
+ declare function parseMarkdownPath(path: string[]): string[] | undefined;
31
+ declare function getMarkdownResponse(project: RdxProject, path: string[]): Response;
32
+ declare function markdownNotFound(): Response;
33
+ declare function getDocumentMarkdownResponse(project: RdxProject, source: RdxContentSource | undefined, slug: string, documentPath?: DocumentPath): Response;
34
+
24
35
  /** Read metadata from the same content source and index used by the page. */
25
36
  declare function getDocumentMetadata({ project, slug, }: {
26
37
  project: RdxProject;
@@ -47,4 +58,4 @@ declare function DocsPage({ slug, project, }: {
47
58
  project: RdxProject;
48
59
  }): Promise<react.JSX.Element>;
49
60
 
50
- export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMetadata, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots };
61
+ export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMarkdownResponse, getDocumentMetadata, getLlmIndexResponse, getMarkdownHref, getMarkdownPaths, getMarkdownResponse, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots, markdownNotFound, parseMarkdownPath };
@@ -1,14 +1,15 @@
1
1
  // src/runtime/index.tsx
2
2
  import {
3
- getDocument as getDocument3,
3
+ getDocument as getDocument4,
4
4
  getDocsPaths,
5
- getSourceOptions as getSourceOptions4,
5
+ getSourceOptions as getSourceOptions5,
6
6
  resolveDocsRoute as resolveDocsRoute2,
7
- getLocalizedProject as getLocalizedProject2,
8
- getDocsHref as getDocsHref3,
7
+ getLocalizedProject as getLocalizedProject3,
8
+ getDocsHref as getDocsHref4,
9
9
  getLanguageLinks,
10
- isFallbackPage as isFallbackPage3
10
+ isFallbackPage as isFallbackPage4
11
11
  } from "@lexibase/rdx-core";
12
+ import { getEditPageHref } from "@lexibase/rdx-core/config";
12
13
  import { Button } from "@lexibase/rdx-ui/components/button";
13
14
  import { Callout } from "@lexibase/rdx-ui/components/callout";
14
15
  import { LanguageDropdown } from "@lexibase/rdx-ui/components/language-dropdown";
@@ -19,6 +20,8 @@ import { DocsSearch } from "@lexibase/rdx-ui/components/docs-search";
19
20
  import { Home } from "@lexibase/rdx-ui/components/home";
20
21
  import { SkipLink } from "@lexibase/rdx-ui/components/skip-link";
21
22
  import { FooterCredit } from "@lexibase/rdx-ui/components/footer-credit";
23
+ import { EditPageLink } from "@lexibase/rdx-ui/components/edit-page-link";
24
+ import { CopyPage } from "@lexibase/rdx-ui/components/copy-page";
22
25
  import { BrandLogo } from "@lexibase/rdx-ui/components/brand-logo";
23
26
  import { getMdxComponents } from "@lexibase/rdx-ui/components/mdx-components";
24
27
  import { getMessages, formatMessage } from "@lexibase/rdx-ui/lib/i18n";
@@ -61,19 +64,170 @@ function compileDocumentation(source, components, links) {
61
64
  });
62
65
  }
63
66
 
67
+ // src/runtime/markdown.ts
68
+ import {
69
+ getContentSources,
70
+ getDocsHref,
71
+ getDocsIndex,
72
+ getDocument,
73
+ getLocalizedProject,
74
+ getSourceOptions,
75
+ isFallbackPage
76
+ } from "@lexibase/rdx-core";
77
+ import { exportMarkdownBody } from "@lexibase/rdx-core/mdx";
78
+ import {
79
+ createDocumentDateReader,
80
+ getSnapshotDocumentDate
81
+ } from "@lexibase/rdx-core/config";
82
+ function publicUrl(project, path) {
83
+ const base = project.config.metadata.metadataBase;
84
+ return base ? new URL(path, base).href : path;
85
+ }
86
+ function indexPath(project) {
87
+ return `${project.locale ? `/${project.locale.path}` : ""}/docs/llms.txt`;
88
+ }
89
+ function defaultLocale(project) {
90
+ const locale = project.localization?.locales.find(
91
+ (entry) => entry.code === project.localization.defaultLocale
92
+ );
93
+ return !project.locale && locale ? getLocalizedProject(project, locale.path) : project;
94
+ }
95
+ function text(value) {
96
+ return value.replace(/\s+/g, " ").trim().replace(/[\\`*_[\]<>]/g, "\\$&");
97
+ }
98
+ function getMarkdownHref(htmlPath) {
99
+ return `/rdx-markdown${htmlPath}.md`;
100
+ }
101
+ function response(body, type, status = 200) {
102
+ return new Response(body, {
103
+ status,
104
+ headers: {
105
+ "Content-Type": `${type}; charset=utf-8`,
106
+ "X-Robots-Tag": "noindex, follow"
107
+ }
108
+ });
109
+ }
110
+ function getLlmIndexResponse(project, documentPath) {
111
+ project = defaultLocale(project);
112
+ const source = project.sources.find(
113
+ (entry) => entry.id !== "current" && entry.redirectTo === void 0 && entry.badge !== "Archived"
114
+ ) ?? project.sources[0];
115
+ const { metadata, description } = project.config;
116
+ const lines = [`# ${text(metadata.applicationName)} Documentation`, ""];
117
+ if (description) lines.push(`> ${text(description)}`, "");
118
+ if (source) {
119
+ for (const category of getDocsIndex(getSourceOptions(project, source))) {
120
+ const pages = category.pages.filter(
121
+ (page) => !isFallbackPage(project, source, page.sourcePath)
122
+ );
123
+ if (!pages.length) continue;
124
+ lines.push(`## ${text(category.title)}`, "");
125
+ for (const page of pages) {
126
+ const href = documentPath?.(source, page) ?? getDocsHref(project, source, page.slug);
127
+ lines.push(
128
+ `- [${text(page.title)}](<${publicUrl(project, getMarkdownHref(href))}>)${page.description ? `: ${text(page.description)}` : ""}`
129
+ );
130
+ }
131
+ lines.push("");
132
+ }
133
+ }
134
+ return response(lines.join("\n"), "text/plain");
135
+ }
136
+ function getMarkdownPaths(project, documentPath) {
137
+ const paths = /* @__PURE__ */ new Set();
138
+ for (const source of getContentSources(project)) {
139
+ if (source.redirectTo !== void 0) continue;
140
+ for (const category of getDocsIndex(getSourceOptions(project, source))) {
141
+ for (const page of category.pages) {
142
+ if (isFallbackPage(project, source, page.sourcePath)) continue;
143
+ const href = documentPath?.(source, page) ?? getDocsHref(project, source, page.slug);
144
+ paths.add(`${href.slice(1)}.md`);
145
+ }
146
+ }
147
+ }
148
+ return [...paths].map((path) => ({
149
+ path: path.split("/").map(decodeURIComponent)
150
+ }));
151
+ }
152
+ function parseMarkdownPath(path) {
153
+ const last = path.at(-1);
154
+ return last?.endsWith(".md") && last.length > 3 ? [...path.slice(0, -1), last.slice(0, -3)] : void 0;
155
+ }
156
+ function getMarkdownResponse(project, path) {
157
+ const segments = parseMarkdownPath(path);
158
+ if (!segments) return markdownNotFound();
159
+ if (project.localization) {
160
+ const locale = segments.shift();
161
+ if (!project.localization.locales.some((entry) => entry.path === locale))
162
+ return markdownNotFound();
163
+ project = getLocalizedProject(project, locale);
164
+ }
165
+ if (segments.shift() !== "docs" || !segments.length) return markdownNotFound();
166
+ const edition = project.sources.find(
167
+ (source) => source.routePrefix && source.routePrefix === segments[0]
168
+ );
169
+ if (edition) segments.shift();
170
+ return getDocumentMarkdownResponse(
171
+ project,
172
+ edition ?? project.sources[0],
173
+ segments.join("/")
174
+ );
175
+ }
176
+ function markdownNotFound() {
177
+ return response(null, "text/markdown", 404);
178
+ }
179
+ function getDocumentMarkdownResponse(project, source, slug, documentPath) {
180
+ if (!source || !slug || source.redirectTo !== void 0)
181
+ return markdownNotFound();
182
+ const options = getSourceOptions(project, source);
183
+ const document = getDocument(slug, options);
184
+ if (!document || isFallbackPage(project, source, document.sourcePath))
185
+ return markdownNotFound();
186
+ const pages = getDocsIndex(options).flatMap((category) => category.pages);
187
+ const bySlug = new Map(pages.map((page2) => [page2.slug, page2]));
188
+ const href = (page2) => publicUrl(
189
+ project,
190
+ documentPath?.(source, page2) ?? getDocsHref(project, source, page2.slug)
191
+ );
192
+ const page = bySlug.get(document.slug);
193
+ const lastUpdated = source.version ? getSnapshotDocumentDate(options.docsDir, document.sourcePath) : createDocumentDateReader(project.rootDir)(document.sourcePath);
194
+ const frontmatter = {
195
+ title: page.title,
196
+ ...page.description ? { description: page.description } : {},
197
+ url: href(document),
198
+ docs_index: publicUrl(project, indexPath(project)),
199
+ ...lastUpdated ? { lastUpdated } : {}
200
+ };
201
+ const fields = Object.entries(frontmatter).map(
202
+ ([key, value]) => `${key}: ${JSON.stringify(value)}`
203
+ );
204
+ const body = exportMarkdownBody(document.content, {
205
+ sourceRoot: options.docsDir,
206
+ sourcePath: document.sourcePath,
207
+ virtualPaths: !!options.fallbackDocsDir,
208
+ documents: pages,
209
+ documentHref: (slug2) => href(bySlug.get(slug2))
210
+ });
211
+ return response(`---
212
+ ${fields.join("\n")}
213
+ ---
214
+
215
+ ${body}`, "text/markdown");
216
+ }
217
+
64
218
  // src/runtime/index.tsx
65
219
  import {
66
220
  createRdxProject,
67
221
  getProjectDocsPaths,
68
- getLocalizedProject as getLocalizedProject3
222
+ getLocalizedProject as getLocalizedProject4
69
223
  } from "@lexibase/rdx-core";
70
224
 
71
225
  // src/runtime/search.ts
72
226
  import {
73
- getDocsIndex,
74
- getDocument,
75
- getSourceOptions,
76
- getDocsHref
227
+ getDocsIndex as getDocsIndex2,
228
+ getDocument as getDocument2,
229
+ getSourceOptions as getSourceOptions2,
230
+ getDocsHref as getDocsHref2
77
231
  } from "@lexibase/rdx-core";
78
232
  import { buildSearchIndex } from "@lexibase/rdx-core/search";
79
233
  function getSearchSources(project) {
@@ -89,16 +243,16 @@ function getSearchResponse(project, key) {
89
243
  if (!source) return new Response(null, { status: 404, headers });
90
244
  if (source.fallbackOnly)
91
245
  return Response.json(buildSearchIndex([]), { headers });
92
- const { docsDir } = getSourceOptions(project, source);
246
+ const { docsDir } = getSourceOptions2(project, source);
93
247
  const options = { docsDir };
94
- const documents = getDocsIndex(options).flatMap(
248
+ const documents = getDocsIndex2(options).flatMap(
95
249
  (category) => category.pages.map((page) => ({
96
250
  title: page.title,
97
251
  description: page.description,
98
252
  category: category.title,
99
- href: getDocsHref(project, source, page.slug),
253
+ href: getDocsHref2(project, source, page.slug),
100
254
  sourcePath: page.sourcePath,
101
- content: getDocument(page.slug, options)?.content ?? ""
255
+ content: getDocument2(page.slug, options)?.content ?? ""
102
256
  }))
103
257
  );
104
258
  return Response.json(buildSearchIndex(documents), { headers });
@@ -106,10 +260,10 @@ function getSearchResponse(project, key) {
106
260
 
107
261
  // src/runtime/indexing.ts
108
262
  import {
109
- getDocsIndex as getDocsIndex2,
110
- getSourceOptions as getSourceOptions2,
111
- getContentSources,
112
- isFallbackPage
263
+ getDocsIndex as getDocsIndex3,
264
+ getSourceOptions as getSourceOptions3,
265
+ getContentSources as getContentSources2,
266
+ isFallbackPage as isFallbackPage2
113
267
  } from "@lexibase/rdx-core";
114
268
  function getSourceRobots(project, source) {
115
269
  const published = project.sources.some(
@@ -138,11 +292,11 @@ function getRdxSitemap(project, {
138
292
  if (!base) return [];
139
293
  const urls = /* @__PURE__ */ new Set();
140
294
  if (project.config.homepage?.enabled) urls.add(new URL("/", base).href);
141
- for (const source of getContentSources(project)) {
295
+ for (const source of getContentSources2(project)) {
142
296
  if (!getSourceRobots(project, source).index) continue;
143
- for (const category of getDocsIndex2(getSourceOptions2(project, source))) {
297
+ for (const category of getDocsIndex3(getSourceOptions3(project, source))) {
144
298
  for (const page of category.pages) {
145
- if (isFallbackPage(project, source, page.sourcePath)) continue;
299
+ if (isFallbackPage2(project, source, page.sourcePath)) continue;
146
300
  urls.add(new URL(documentPath(source, page), base).href);
147
301
  }
148
302
  }
@@ -153,11 +307,11 @@ function getRdxSitemap(project, {
153
307
  // src/runtime/metadata.ts
154
308
  import {
155
309
  resolveDocsRoute,
156
- getLocalizedProject,
157
- getDocsHref as getDocsHref2,
158
- isFallbackPage as isFallbackPage2,
159
- getDocument as getDocument2,
160
- getSourceOptions as getSourceOptions3
310
+ getLocalizedProject as getLocalizedProject2,
311
+ getDocsHref as getDocsHref3,
312
+ isFallbackPage as isFallbackPage3,
313
+ getDocument as getDocument3,
314
+ getSourceOptions as getSourceOptions4
161
315
  } from "@lexibase/rdx-core";
162
316
  import { notFound } from "next/navigation";
163
317
  function getDocumentMetadata({
@@ -172,24 +326,24 @@ function getDocumentMetadata({
172
326
  if (!route.slug) return { robots: { index: false, follow: true } };
173
327
  if (!route.document) notFound();
174
328
  const page = route.categories.flatMap((category) => category.pages).find((page2) => page2.slug === route.slug);
175
- const fallback = isFallbackPage2(project, route.source, page.sourcePath);
329
+ const fallback = isFallbackPage3(project, route.source, page.sourcePath);
176
330
  const primary = project.localization?.locales.find(
177
331
  (locale) => locale.code === project.localization.defaultLocale
178
332
  );
179
- const canonicalProject = fallback ? getLocalizedProject(project, primary.path) : project;
333
+ const canonicalProject = fallback ? getLocalizedProject2(project, primary.path) : project;
180
334
  const base = project.config.metadata.metadataBase;
181
335
  const languages = project.localization && base ? Object.fromEntries(
182
336
  project.localization.locales.flatMap((locale) => {
183
- const view = getLocalizedProject(project, locale.path);
337
+ const view = getLocalizedProject2(project, locale.path);
184
338
  const source = view.sources.find(
185
339
  (entry) => entry.id === route.source.id
186
340
  );
187
- return !source.fallbackOnly && getDocument2(page.slug, {
188
- docsDir: getSourceOptions3(view, source).docsDir
341
+ return !source.fallbackOnly && getDocument3(page.slug, {
342
+ docsDir: getSourceOptions4(view, source).docsDir
189
343
  }) ? [
190
344
  [
191
345
  locale.code,
192
- new URL(getDocsHref2(view, source, page.slug), base).href
346
+ new URL(getDocsHref3(view, source, page.slug), base).href
193
347
  ]
194
348
  ] : [];
195
349
  })
@@ -200,7 +354,7 @@ function getDocumentMetadata({
200
354
  ...project.localization && base ? {
201
355
  alternates: {
202
356
  canonical: new URL(
203
- getDocsHref2(
357
+ getDocsHref3(
204
358
  canonicalProject,
205
359
  canonicalProject.sources.find(
206
360
  (entry) => entry.id === route.source.id
@@ -280,13 +434,13 @@ function RdxHeader({
280
434
  const messages = getMessages(config.lang);
281
435
  const { source, slug: currentSlug } = resolveDocsRoute2(project, slug);
282
436
  const options = project.sources.filter((edition) => edition.redirectTo === void 0).map((edition) => {
283
- const docs = getSourceOptions4(project, edition);
284
- const destination = currentSlug && getDocument3(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
437
+ const docs = getSourceOptions5(project, edition);
438
+ const destination = currentSlug && getDocument4(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
285
439
  return {
286
440
  id: edition.id,
287
441
  label: edition.id === "current" ? messages.current : formatMessage(messages.version, { version: edition.label }),
288
442
  kind: edition.id === "current" ? "live" : edition.badge === "Archived" ? "archived" : "version",
289
- href: getDocsHref3(project, edition, destination)
443
+ href: getDocsHref4(project, edition, destination)
290
444
  };
291
445
  });
292
446
  return /* @__PURE__ */ jsx(
@@ -303,8 +457,8 @@ function RdxHeader({
303
457
  ) : void 0,
304
458
  siteTitle: config.metadata.applicationName,
305
459
  logo: /* @__PURE__ */ jsx(BrandLogo, { source: config.navbar?.logo, className: "size-5" }),
306
- homeHref: config.homepage?.enabled ? "/" : getDocsHref3(project),
307
- docsHref: getDocsHref3(project, source),
460
+ homeHref: config.homepage?.enabled ? "/" : getDocsHref4(project),
461
+ docsHref: getDocsHref4(project, source),
308
462
  languageMenu: project.localization ? /* @__PURE__ */ jsx(
309
463
  LanguageDropdown,
310
464
  {
@@ -322,7 +476,7 @@ function RdxHeader({
322
476
  }
323
477
  function HomePage({ project }) {
324
478
  const { config } = project;
325
- if (!config.homepage?.enabled) redirect(getDocsHref3(project));
479
+ if (!config.homepage?.enabled) redirect(getDocsHref4(project));
326
480
  const homepage = config.homepage;
327
481
  const CustomHome = homepage.component;
328
482
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -381,7 +535,7 @@ async function DocsPage({
381
535
  project
382
536
  }) {
383
537
  if (project.localization && !project.locale)
384
- permanentRedirect(getDocsHref3(project, project.sources[0], slug?.join("/")));
538
+ permanentRedirect(getDocsHref4(project, project.sources[0], slug?.join("/")));
385
539
  const messages = getMessages(project.config.lang);
386
540
  const {
387
541
  source,
@@ -391,7 +545,7 @@ async function DocsPage({
391
545
  } = resolveDocsRoute2(project, slug);
392
546
  if (source.redirectTo !== void 0)
393
547
  permanentRedirect(
394
- getDocsHref3(
548
+ getDocsHref4(
395
549
  project,
396
550
  project.sources.find(
397
551
  (entry) => entry.routePrefix === source.redirectTo
@@ -408,15 +562,16 @@ async function DocsPage({
408
562
  ] })
409
563
  ] });
410
564
  if (!document) notFound2();
411
- const fallback = isFallbackPage3(project, source, document.sourcePath);
565
+ const fallback = isFallbackPage4(project, source, document.sourcePath);
566
+ const editHref = fallback ? void 0 : getEditPageHref(project, source, document.sourcePath);
412
567
  const primaryLocale = (source.localization ?? project.localization)?.locales.find(
413
568
  (locale) => locale.code === project.localization.defaultLocale
414
569
  );
415
- const originalProject = fallback && primaryLocale ? getLocalizedProject2(project, primaryLocale.path) : project;
570
+ const originalProject = fallback && primaryLocale ? getLocalizedProject3(project, primaryLocale.path) : project;
416
571
  const contentSource = originalProject.sources.find(
417
572
  (entry) => entry.id === source.id
418
573
  );
419
- const contentOptions = getSourceOptions4(originalProject, contentSource);
574
+ const contentOptions = getSourceOptions5(originalProject, contentSource);
420
575
  const contentCategories = fallback ? resolveDocsRoute2(originalProject, slug).categories : categories;
421
576
  const contentLanguage = fallback ? primaryLocale.code : project.config.lang;
422
577
  const { content } = await compileDocumentation(
@@ -431,18 +586,20 @@ async function DocsPage({
431
586
  documents: contentCategories.flatMap((category2) => category2.pages)
432
587
  }
433
588
  );
434
- const title = typeof document.frontmatter.title === "string" ? document.frontmatter.title : currentSlug;
435
589
  const description = typeof document.frontmatter.description === "string" ? document.frontmatter.description : null;
436
- const hrefFor = (pageSlug) => getDocsHref3(project, source, pageSlug);
590
+ const hrefFor = (pageSlug) => getDocsHref4(project, source, pageSlug);
591
+ const markdownHref = fallback ? void 0 : getMarkdownHref(hrefFor(currentSlug));
437
592
  const sidebarCategories = categories.map((category2) => ({
438
593
  title: category2.title,
439
594
  links: category2.pages.map((page) => ({
440
- label: page.title,
595
+ label: page.navTitle ?? page.title,
596
+ highlighting: page.highlighting,
441
597
  href: hrefFor(page.slug)
442
598
  }))
443
599
  }));
444
600
  const pages = categories.flatMap((category2) => category2.pages);
445
601
  const index = pages.findIndex((page) => page.slug === currentSlug);
602
+ const title = pages[index]?.title ?? currentSlug;
446
603
  const previous = pages[index - 1];
447
604
  const next = pages[index + 1];
448
605
  const category = categories.find(
@@ -500,7 +657,7 @@ async function DocsPage({
500
657
  Button,
501
658
  {
502
659
  variant: "link",
503
- href: getDocsHref3(
660
+ href: getDocsHref4(
504
661
  originalProject,
505
662
  contentSource,
506
663
  currentSlug
@@ -534,14 +691,18 @@ async function DocsPage({
534
691
  /* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
535
692
  source.badge && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: source.badge === "Archived" ? messages.archivedVersion : source.badge }),
536
693
  /* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-600 font-semibold uppercase block -mb-0.5", children: category?.title }),
537
- /* @__PURE__ */ jsx("h1", { className: "text-4xl uppercase font-bold mb-2", children: title }),
538
- description && /* @__PURE__ */ jsx("p", { className: "text-sm text-ring max-w-[726px]", children: description })
694
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex flex-wrap items-start gap-x-4 gap-y-2", children: [
695
+ /* @__PURE__ */ jsx("h1", { className: "min-w-0 max-w-full break-words text-4xl font-bold uppercase", children: title }),
696
+ markdownHref && /* @__PURE__ */ jsx(CopyPage, { href: markdownHref }, markdownHref)
697
+ ] }),
698
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground max-w-[726px]", children: description })
539
699
  ] }),
540
700
  /* @__PURE__ */ jsx("div", { className: "h-full w-full flex-1 max-w-[960px]", children: content })
541
701
  ]
542
702
  }
543
703
  ),
544
704
  /* @__PURE__ */ jsxs("footer", { className: "flex flex-col", children: [
705
+ editHref && /* @__PURE__ */ jsx(EditPageLink, { href: editHref, lang: project.config.lang }),
545
706
  /* @__PURE__ */ jsxs("section", { className: "flex items-center justify-between", children: [
546
707
  previous ? /* @__PURE__ */ jsx(
547
708
  Button,
@@ -590,8 +751,13 @@ export {
590
751
  NotFoundPage,
591
752
  RdxLayout,
592
753
  createRdxProject,
754
+ getDocumentMarkdownResponse,
593
755
  getDocumentMetadata,
594
- getLocalizedProject3 as getLocalizedProject,
756
+ getLlmIndexResponse,
757
+ getLocalizedProject4 as getLocalizedProject,
758
+ getMarkdownHref,
759
+ getMarkdownPaths,
760
+ getMarkdownResponse,
595
761
  getPageMetadata,
596
762
  getProjectDocsPaths,
597
763
  getRdxMetadata,
@@ -599,5 +765,7 @@ export {
599
765
  getRdxSitemap,
600
766
  getSearchResponse,
601
767
  getSearchSources,
602
- getSourceRobots
768
+ getSourceRobots,
769
+ markdownNotFound,
770
+ parseMarkdownPath
603
771
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexibase/rdx-next",
3
- "version": "4.2.0",
3
+ "version": "5.0.1",
4
4
  "description": "The Next.js runtime for RDX documentation projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,11 +52,11 @@
52
52
  "next-mdx-remote": "^6.0.0",
53
53
  "remark-gfm": "^4",
54
54
  "typescript": "6.0.3",
55
- "@lexibase/rdx-types": "2.2.0",
56
- "@lexibase/rdx-core": "2.2.0"
55
+ "@lexibase/rdx-core": "3.0.1",
56
+ "@lexibase/rdx-types": "2.3.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@lexibase/rdx-ui": "^3.4.0",
59
+ "@lexibase/rdx-ui": "^3.5.0",
60
60
  "next": "^16.4.2",
61
61
  "react": "^19.3.1",
62
62
  "react-dom": "^19.3.1"
@@ -81,9 +81,9 @@
81
81
  "next": "^16.4.2",
82
82
  "react": "19.3.1",
83
83
  "react-dom": "19.3.1",
84
- "@rdx/typescript-config": "1.0.0",
84
+ "@lexibase/rdx-ui": "3.5.1",
85
85
  "@rdx/eslint-config": "1.0.0",
86
- "@lexibase/rdx-ui": "3.4.0"
86
+ "@rdx/typescript-config": "1.0.0"
87
87
  },
88
88
  "scripts": {
89
89
  "dev": "tsup src/index.ts src/runtime/index.tsx --format esm --dts --watch --external @lexibase/rdx-core --external @lexibase/rdx-types --external @lexibase/rdx-ui --external next --external react --external react-dom --external next-mdx-remote",
@@ -0,0 +1,8 @@
1
+ import { getLlmIndexResponse } from '@lexibase/rdx-next/runtime'
2
+ import { getProject } from '../../../rdx-project'
3
+
4
+ export const dynamic = 'force-static'
5
+
6
+ export async function GET() {
7
+ return getLlmIndexResponse(await getProject())
8
+ }
@@ -0,0 +1,19 @@
1
+ import {
2
+ getMarkdownPaths,
3
+ getMarkdownResponse,
4
+ } from '@lexibase/rdx-next/runtime'
5
+ import { getProject } from '../../../rdx-project'
6
+
7
+ export const dynamic = 'force-static'
8
+ export const dynamicParams = false
9
+
10
+ export async function generateStaticParams() {
11
+ return getMarkdownPaths(await getProject())
12
+ }
13
+
14
+ export async function GET(
15
+ _request: Request,
16
+ { params }: { params: Promise<{ path: string[] }> }
17
+ ) {
18
+ return getMarkdownResponse(await getProject(), (await params).path)
19
+ }