@lexibase/rdx-next 4.2.0 → 5.0.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.
@@ -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')
@@ -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,164 @@ 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
+ function publicUrl(project, path) {
79
+ const base = project.config.metadata.metadataBase;
80
+ return base ? new URL(path, base).href : path;
81
+ }
82
+ function indexPath(project) {
83
+ return `${project.locale ? `/${project.locale.path}` : ""}/docs/llms.txt`;
84
+ }
85
+ function defaultLocale(project) {
86
+ const locale = project.localization?.locales.find(
87
+ (entry) => entry.code === project.localization.defaultLocale
88
+ );
89
+ return !project.locale && locale ? getLocalizedProject(project, locale.path) : project;
90
+ }
91
+ function text(value) {
92
+ return value.replace(/\s+/g, " ").trim().replace(/[\\`*_[\]<>]/g, "\\$&");
93
+ }
94
+ function getMarkdownHref(htmlPath) {
95
+ return `/rdx-markdown${htmlPath}.md`;
96
+ }
97
+ function response(body, type, status = 200) {
98
+ return new Response(body, {
99
+ status,
100
+ headers: {
101
+ "Content-Type": `${type}; charset=utf-8`,
102
+ "X-Robots-Tag": "noindex, follow"
103
+ }
104
+ });
105
+ }
106
+ function getLlmIndexResponse(project, documentPath) {
107
+ project = defaultLocale(project);
108
+ const source = project.sources.find(
109
+ (entry) => entry.id !== "current" && entry.redirectTo === void 0 && entry.badge !== "Archived"
110
+ ) ?? project.sources[0];
111
+ const { metadata, description } = project.config;
112
+ const lines = [`# ${text(metadata.applicationName)} Documentation`, ""];
113
+ if (description) lines.push(`> ${text(description)}`, "");
114
+ if (source) {
115
+ for (const category of getDocsIndex(getSourceOptions(project, source))) {
116
+ const pages = category.pages.filter(
117
+ (page) => !isFallbackPage(project, source, page.sourcePath)
118
+ );
119
+ if (!pages.length) continue;
120
+ lines.push(`## ${text(category.title)}`, "");
121
+ for (const page of pages) {
122
+ const href = documentPath?.(source, page) ?? getDocsHref(project, source, page.slug);
123
+ lines.push(
124
+ `- [${text(page.title)}](<${publicUrl(project, getMarkdownHref(href))}>)${page.description ? `: ${text(page.description)}` : ""}`
125
+ );
126
+ }
127
+ lines.push("");
128
+ }
129
+ }
130
+ return response(lines.join("\n"), "text/plain");
131
+ }
132
+ function getMarkdownPaths(project, documentPath) {
133
+ const paths = /* @__PURE__ */ new Set();
134
+ for (const source of getContentSources(project)) {
135
+ if (source.redirectTo !== void 0) continue;
136
+ for (const category of getDocsIndex(getSourceOptions(project, source))) {
137
+ for (const page of category.pages) {
138
+ if (isFallbackPage(project, source, page.sourcePath)) continue;
139
+ const href = documentPath?.(source, page) ?? getDocsHref(project, source, page.slug);
140
+ paths.add(`${href.slice(1)}.md`);
141
+ }
142
+ }
143
+ }
144
+ return [...paths].map((path) => ({
145
+ path: path.split("/").map(decodeURIComponent)
146
+ }));
147
+ }
148
+ function parseMarkdownPath(path) {
149
+ const last = path.at(-1);
150
+ return last?.endsWith(".md") && last.length > 3 ? [...path.slice(0, -1), last.slice(0, -3)] : void 0;
151
+ }
152
+ function getMarkdownResponse(project, path) {
153
+ const segments = parseMarkdownPath(path);
154
+ if (!segments) return markdownNotFound();
155
+ if (project.localization) {
156
+ const locale = segments.shift();
157
+ if (!project.localization.locales.some((entry) => entry.path === locale))
158
+ return markdownNotFound();
159
+ project = getLocalizedProject(project, locale);
160
+ }
161
+ if (segments.shift() !== "docs" || !segments.length) return markdownNotFound();
162
+ const edition = project.sources.find(
163
+ (source) => source.routePrefix && source.routePrefix === segments[0]
164
+ );
165
+ if (edition) segments.shift();
166
+ return getDocumentMarkdownResponse(
167
+ project,
168
+ edition ?? project.sources[0],
169
+ segments.join("/")
170
+ );
171
+ }
172
+ function markdownNotFound() {
173
+ return response(null, "text/markdown", 404);
174
+ }
175
+ function getDocumentMarkdownResponse(project, source, slug, documentPath) {
176
+ if (!source || !slug || source.redirectTo !== void 0)
177
+ return markdownNotFound();
178
+ const options = getSourceOptions(project, source);
179
+ const document = getDocument(slug, options);
180
+ if (!document || isFallbackPage(project, source, document.sourcePath))
181
+ return markdownNotFound();
182
+ const pages = getDocsIndex(options).flatMap((category) => category.pages);
183
+ const bySlug = new Map(pages.map((page2) => [page2.slug, page2]));
184
+ const href = (page2) => publicUrl(
185
+ project,
186
+ documentPath?.(source, page2) ?? getDocsHref(project, source, page2.slug)
187
+ );
188
+ const page = bySlug.get(document.slug);
189
+ const frontmatter = {
190
+ title: page.title,
191
+ ...page.description ? { description: page.description } : {},
192
+ url: href(document),
193
+ docs_index: publicUrl(project, indexPath(project))
194
+ };
195
+ const fields = Object.entries(frontmatter).map(
196
+ ([key, value]) => `${key}: ${JSON.stringify(value)}`
197
+ );
198
+ const body = exportMarkdownBody(document.content, {
199
+ sourceRoot: options.docsDir,
200
+ sourcePath: document.sourcePath,
201
+ virtualPaths: !!options.fallbackDocsDir,
202
+ documents: pages,
203
+ documentHref: (slug2) => href(bySlug.get(slug2))
204
+ });
205
+ return response(`---
206
+ ${fields.join("\n")}
207
+ ---
208
+
209
+ ${body}`, "text/markdown");
210
+ }
211
+
64
212
  // src/runtime/index.tsx
65
213
  import {
66
214
  createRdxProject,
67
215
  getProjectDocsPaths,
68
- getLocalizedProject as getLocalizedProject3
216
+ getLocalizedProject as getLocalizedProject4
69
217
  } from "@lexibase/rdx-core";
70
218
 
71
219
  // src/runtime/search.ts
72
220
  import {
73
- getDocsIndex,
74
- getDocument,
75
- getSourceOptions,
76
- getDocsHref
221
+ getDocsIndex as getDocsIndex2,
222
+ getDocument as getDocument2,
223
+ getSourceOptions as getSourceOptions2,
224
+ getDocsHref as getDocsHref2
77
225
  } from "@lexibase/rdx-core";
78
226
  import { buildSearchIndex } from "@lexibase/rdx-core/search";
79
227
  function getSearchSources(project) {
@@ -89,16 +237,16 @@ function getSearchResponse(project, key) {
89
237
  if (!source) return new Response(null, { status: 404, headers });
90
238
  if (source.fallbackOnly)
91
239
  return Response.json(buildSearchIndex([]), { headers });
92
- const { docsDir } = getSourceOptions(project, source);
240
+ const { docsDir } = getSourceOptions2(project, source);
93
241
  const options = { docsDir };
94
- const documents = getDocsIndex(options).flatMap(
242
+ const documents = getDocsIndex2(options).flatMap(
95
243
  (category) => category.pages.map((page) => ({
96
244
  title: page.title,
97
245
  description: page.description,
98
246
  category: category.title,
99
- href: getDocsHref(project, source, page.slug),
247
+ href: getDocsHref2(project, source, page.slug),
100
248
  sourcePath: page.sourcePath,
101
- content: getDocument(page.slug, options)?.content ?? ""
249
+ content: getDocument2(page.slug, options)?.content ?? ""
102
250
  }))
103
251
  );
104
252
  return Response.json(buildSearchIndex(documents), { headers });
@@ -106,10 +254,10 @@ function getSearchResponse(project, key) {
106
254
 
107
255
  // src/runtime/indexing.ts
108
256
  import {
109
- getDocsIndex as getDocsIndex2,
110
- getSourceOptions as getSourceOptions2,
111
- getContentSources,
112
- isFallbackPage
257
+ getDocsIndex as getDocsIndex3,
258
+ getSourceOptions as getSourceOptions3,
259
+ getContentSources as getContentSources2,
260
+ isFallbackPage as isFallbackPage2
113
261
  } from "@lexibase/rdx-core";
114
262
  function getSourceRobots(project, source) {
115
263
  const published = project.sources.some(
@@ -138,11 +286,11 @@ function getRdxSitemap(project, {
138
286
  if (!base) return [];
139
287
  const urls = /* @__PURE__ */ new Set();
140
288
  if (project.config.homepage?.enabled) urls.add(new URL("/", base).href);
141
- for (const source of getContentSources(project)) {
289
+ for (const source of getContentSources2(project)) {
142
290
  if (!getSourceRobots(project, source).index) continue;
143
- for (const category of getDocsIndex2(getSourceOptions2(project, source))) {
291
+ for (const category of getDocsIndex3(getSourceOptions3(project, source))) {
144
292
  for (const page of category.pages) {
145
- if (isFallbackPage(project, source, page.sourcePath)) continue;
293
+ if (isFallbackPage2(project, source, page.sourcePath)) continue;
146
294
  urls.add(new URL(documentPath(source, page), base).href);
147
295
  }
148
296
  }
@@ -153,11 +301,11 @@ function getRdxSitemap(project, {
153
301
  // src/runtime/metadata.ts
154
302
  import {
155
303
  resolveDocsRoute,
156
- getLocalizedProject,
157
- getDocsHref as getDocsHref2,
158
- isFallbackPage as isFallbackPage2,
159
- getDocument as getDocument2,
160
- getSourceOptions as getSourceOptions3
304
+ getLocalizedProject as getLocalizedProject2,
305
+ getDocsHref as getDocsHref3,
306
+ isFallbackPage as isFallbackPage3,
307
+ getDocument as getDocument3,
308
+ getSourceOptions as getSourceOptions4
161
309
  } from "@lexibase/rdx-core";
162
310
  import { notFound } from "next/navigation";
163
311
  function getDocumentMetadata({
@@ -172,24 +320,24 @@ function getDocumentMetadata({
172
320
  if (!route.slug) return { robots: { index: false, follow: true } };
173
321
  if (!route.document) notFound();
174
322
  const page = route.categories.flatMap((category) => category.pages).find((page2) => page2.slug === route.slug);
175
- const fallback = isFallbackPage2(project, route.source, page.sourcePath);
323
+ const fallback = isFallbackPage3(project, route.source, page.sourcePath);
176
324
  const primary = project.localization?.locales.find(
177
325
  (locale) => locale.code === project.localization.defaultLocale
178
326
  );
179
- const canonicalProject = fallback ? getLocalizedProject(project, primary.path) : project;
327
+ const canonicalProject = fallback ? getLocalizedProject2(project, primary.path) : project;
180
328
  const base = project.config.metadata.metadataBase;
181
329
  const languages = project.localization && base ? Object.fromEntries(
182
330
  project.localization.locales.flatMap((locale) => {
183
- const view = getLocalizedProject(project, locale.path);
331
+ const view = getLocalizedProject2(project, locale.path);
184
332
  const source = view.sources.find(
185
333
  (entry) => entry.id === route.source.id
186
334
  );
187
- return !source.fallbackOnly && getDocument2(page.slug, {
188
- docsDir: getSourceOptions3(view, source).docsDir
335
+ return !source.fallbackOnly && getDocument3(page.slug, {
336
+ docsDir: getSourceOptions4(view, source).docsDir
189
337
  }) ? [
190
338
  [
191
339
  locale.code,
192
- new URL(getDocsHref2(view, source, page.slug), base).href
340
+ new URL(getDocsHref3(view, source, page.slug), base).href
193
341
  ]
194
342
  ] : [];
195
343
  })
@@ -200,7 +348,7 @@ function getDocumentMetadata({
200
348
  ...project.localization && base ? {
201
349
  alternates: {
202
350
  canonical: new URL(
203
- getDocsHref2(
351
+ getDocsHref3(
204
352
  canonicalProject,
205
353
  canonicalProject.sources.find(
206
354
  (entry) => entry.id === route.source.id
@@ -280,13 +428,13 @@ function RdxHeader({
280
428
  const messages = getMessages(config.lang);
281
429
  const { source, slug: currentSlug } = resolveDocsRoute2(project, slug);
282
430
  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];
431
+ const docs = getSourceOptions5(project, edition);
432
+ const destination = currentSlug && getDocument4(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
285
433
  return {
286
434
  id: edition.id,
287
435
  label: edition.id === "current" ? messages.current : formatMessage(messages.version, { version: edition.label }),
288
436
  kind: edition.id === "current" ? "live" : edition.badge === "Archived" ? "archived" : "version",
289
- href: getDocsHref3(project, edition, destination)
437
+ href: getDocsHref4(project, edition, destination)
290
438
  };
291
439
  });
292
440
  return /* @__PURE__ */ jsx(
@@ -303,8 +451,8 @@ function RdxHeader({
303
451
  ) : void 0,
304
452
  siteTitle: config.metadata.applicationName,
305
453
  logo: /* @__PURE__ */ jsx(BrandLogo, { source: config.navbar?.logo, className: "size-5" }),
306
- homeHref: config.homepage?.enabled ? "/" : getDocsHref3(project),
307
- docsHref: getDocsHref3(project, source),
454
+ homeHref: config.homepage?.enabled ? "/" : getDocsHref4(project),
455
+ docsHref: getDocsHref4(project, source),
308
456
  languageMenu: project.localization ? /* @__PURE__ */ jsx(
309
457
  LanguageDropdown,
310
458
  {
@@ -322,7 +470,7 @@ function RdxHeader({
322
470
  }
323
471
  function HomePage({ project }) {
324
472
  const { config } = project;
325
- if (!config.homepage?.enabled) redirect(getDocsHref3(project));
473
+ if (!config.homepage?.enabled) redirect(getDocsHref4(project));
326
474
  const homepage = config.homepage;
327
475
  const CustomHome = homepage.component;
328
476
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -381,7 +529,7 @@ async function DocsPage({
381
529
  project
382
530
  }) {
383
531
  if (project.localization && !project.locale)
384
- permanentRedirect(getDocsHref3(project, project.sources[0], slug?.join("/")));
532
+ permanentRedirect(getDocsHref4(project, project.sources[0], slug?.join("/")));
385
533
  const messages = getMessages(project.config.lang);
386
534
  const {
387
535
  source,
@@ -391,7 +539,7 @@ async function DocsPage({
391
539
  } = resolveDocsRoute2(project, slug);
392
540
  if (source.redirectTo !== void 0)
393
541
  permanentRedirect(
394
- getDocsHref3(
542
+ getDocsHref4(
395
543
  project,
396
544
  project.sources.find(
397
545
  (entry) => entry.routePrefix === source.redirectTo
@@ -408,15 +556,16 @@ async function DocsPage({
408
556
  ] })
409
557
  ] });
410
558
  if (!document) notFound2();
411
- const fallback = isFallbackPage3(project, source, document.sourcePath);
559
+ const fallback = isFallbackPage4(project, source, document.sourcePath);
560
+ const editHref = fallback ? void 0 : getEditPageHref(project, source, document.sourcePath);
412
561
  const primaryLocale = (source.localization ?? project.localization)?.locales.find(
413
562
  (locale) => locale.code === project.localization.defaultLocale
414
563
  );
415
- const originalProject = fallback && primaryLocale ? getLocalizedProject2(project, primaryLocale.path) : project;
564
+ const originalProject = fallback && primaryLocale ? getLocalizedProject3(project, primaryLocale.path) : project;
416
565
  const contentSource = originalProject.sources.find(
417
566
  (entry) => entry.id === source.id
418
567
  );
419
- const contentOptions = getSourceOptions4(originalProject, contentSource);
568
+ const contentOptions = getSourceOptions5(originalProject, contentSource);
420
569
  const contentCategories = fallback ? resolveDocsRoute2(originalProject, slug).categories : categories;
421
570
  const contentLanguage = fallback ? primaryLocale.code : project.config.lang;
422
571
  const { content } = await compileDocumentation(
@@ -431,18 +580,20 @@ async function DocsPage({
431
580
  documents: contentCategories.flatMap((category2) => category2.pages)
432
581
  }
433
582
  );
434
- const title = typeof document.frontmatter.title === "string" ? document.frontmatter.title : currentSlug;
435
583
  const description = typeof document.frontmatter.description === "string" ? document.frontmatter.description : null;
436
- const hrefFor = (pageSlug) => getDocsHref3(project, source, pageSlug);
584
+ const hrefFor = (pageSlug) => getDocsHref4(project, source, pageSlug);
585
+ const markdownHref = fallback ? void 0 : getMarkdownHref(hrefFor(currentSlug));
437
586
  const sidebarCategories = categories.map((category2) => ({
438
587
  title: category2.title,
439
588
  links: category2.pages.map((page) => ({
440
- label: page.title,
589
+ label: page.navTitle ?? page.title,
590
+ highlighting: page.highlighting,
441
591
  href: hrefFor(page.slug)
442
592
  }))
443
593
  }));
444
594
  const pages = categories.flatMap((category2) => category2.pages);
445
595
  const index = pages.findIndex((page) => page.slug === currentSlug);
596
+ const title = pages[index]?.title ?? currentSlug;
446
597
  const previous = pages[index - 1];
447
598
  const next = pages[index + 1];
448
599
  const category = categories.find(
@@ -500,7 +651,7 @@ async function DocsPage({
500
651
  Button,
501
652
  {
502
653
  variant: "link",
503
- href: getDocsHref3(
654
+ href: getDocsHref4(
504
655
  originalProject,
505
656
  contentSource,
506
657
  currentSlug
@@ -534,7 +685,10 @@ async function DocsPage({
534
685
  /* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
535
686
  source.badge && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: source.badge === "Archived" ? messages.archivedVersion : source.badge }),
536
687
  /* @__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 }),
688
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex flex-wrap items-start gap-x-4 gap-y-2", children: [
689
+ /* @__PURE__ */ jsx("h1", { className: "min-w-0 max-w-full break-words text-4xl font-bold uppercase", children: title }),
690
+ markdownHref && /* @__PURE__ */ jsx(CopyPage, { href: markdownHref }, markdownHref)
691
+ ] }),
538
692
  description && /* @__PURE__ */ jsx("p", { className: "text-sm text-ring max-w-[726px]", children: description })
539
693
  ] }),
540
694
  /* @__PURE__ */ jsx("div", { className: "h-full w-full flex-1 max-w-[960px]", children: content })
@@ -542,6 +696,7 @@ async function DocsPage({
542
696
  }
543
697
  ),
544
698
  /* @__PURE__ */ jsxs("footer", { className: "flex flex-col", children: [
699
+ editHref && /* @__PURE__ */ jsx(EditPageLink, { href: editHref, lang: project.config.lang }),
545
700
  /* @__PURE__ */ jsxs("section", { className: "flex items-center justify-between", children: [
546
701
  previous ? /* @__PURE__ */ jsx(
547
702
  Button,
@@ -590,8 +745,13 @@ export {
590
745
  NotFoundPage,
591
746
  RdxLayout,
592
747
  createRdxProject,
748
+ getDocumentMarkdownResponse,
593
749
  getDocumentMetadata,
594
- getLocalizedProject3 as getLocalizedProject,
750
+ getLlmIndexResponse,
751
+ getLocalizedProject4 as getLocalizedProject,
752
+ getMarkdownHref,
753
+ getMarkdownPaths,
754
+ getMarkdownResponse,
595
755
  getPageMetadata,
596
756
  getProjectDocsPaths,
597
757
  getRdxMetadata,
@@ -599,5 +759,7 @@ export {
599
759
  getRdxSitemap,
600
760
  getSearchResponse,
601
761
  getSearchSources,
602
- getSourceRobots
762
+ getSourceRobots,
763
+ markdownNotFound,
764
+ parseMarkdownPath
603
765
  };
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.0",
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-types": "2.3.0",
56
+ "@lexibase/rdx-core": "3.0.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.0",
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
+ }