@lexibase/rdx-next 1.0.0-beta.9 → 1.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.
package/README.md CHANGED
@@ -2,20 +2,33 @@
2
2
 
3
3
  The official Next.js runtime for RDX documentation projects.
4
4
 
5
+ Requires Node.js 22.18+ (22.x) or 24.11+ (24.x). Use the latest patch of either line.
6
+ The MDX renderer keeps JavaScript expression blocking enabled. Markdown, fenced
7
+ code and registered JSX components with literal props remain supported, but
8
+ expressions such as `{value}` or `style={{ ... }}` are not evaluated. Documentation
9
+ must still be trusted project-owned content, not untrusted uploads.
10
+
5
11
  It exposes the `rdx` command used by generated projects to create their
6
12
  internal `.rdx` application and run Next.js.
7
13
 
8
14
  Run `pnpm rdx add versioning` (or `npm exec -- rdx add versioning`) from a
9
15
  documentation project to install and activate the official optional plugin.
10
16
  The command detects the package manager, preserves config and plugin options,
11
- creates a config backup when changing it, validates the result and prepares
17
+ validates the result before saving it without creating backups, and prepares
12
18
  the runtime. Repeating it does not duplicate or upgrade an installed plugin.
13
19
  Use `--package-manager npm|pnpm|yarn` to resolve ambiguous lockfiles. Dynamic
14
20
  configurations receive manual instructions and are not automatically edited.
15
21
  Installing a package with npm/pnpm/yarn alone never edits `rdx.config.ts`.
16
22
 
23
+ First plugin installation follows the installed runtime: stable releases use
24
+ `latest`, betas use `beta`, and release candidates use `rc`. Unknown prerelease
25
+ channels require manual installation; missing tags do not fall back to another
26
+ channel. A missing but declared plugin is restored with the manager's `install`
27
+ command, without replacing its dependency range or section. Package-manager
28
+ lockfile rules and security policies still apply normally.
29
+
17
30
  Generated sites use the same theme and components as the development website,
18
- through `@lexibase/rdx-ui`: logo, navbar, Radix version dropdown, mobile/desktop
31
+ through `@lexibase/rdx-ui`: logo, navbar, Base UI version dropdown, mobile/desktop
19
32
  sidebar, landing page, MDX styles and theme provider. The adapter owns content
20
33
  and route data; it does not implement a parallel UI theme.
21
34
 
@@ -27,9 +40,76 @@ explicitly configured plugins enable the shared navbar dropdown.
27
40
  `.rdx/` is generated. Configure the site or edit a project-owned `home.tsx`
28
41
  instead of modifying files inside `.rdx/`.
29
42
 
43
+ The generated project loader supports both the documentation project root and
44
+ the internal `.rdx/` application as the server working directory. This matters
45
+ for serverless Next workers, which can start inside `.rdx/`. Content and version
46
+ manifests stay in the project root; do not copy them into the generated folder
47
+ or initialize versioning again to work around a runtime path error.
48
+
30
49
  Place images, favicon and downloads in the project-root `public/` directory.
31
50
  The CLI links `.rdx/public` to it using a junction on Windows or a directory
32
51
  symlink elsewhere. `/images/logo.svg` serves `public/images/logo.svg` in dev and
33
52
  production. No asset copies are cached and original files are never removed.
34
53
  Restart/redeploy production after adding/removing assets. Documentation versions
35
54
  share this directory; snapshots do not copy its contents.
55
+
56
+ Production builds trace only configured documentation (`.mdx` and
57
+ `_category.json`) and runtime files declared by configured plugins. The versioning
58
+ plugin declares its manifest, including a custom `versionsFile`; current, active
59
+ and archived content sources are included. Live documentation always uses `docs/`;
60
+ custom plugin snapshot paths remain supported without copying the whole project
61
+ into the server deployment.
62
+ Rebuild/redeploy after changing production content or version manifests.
63
+
64
+ Native Next.js applications using the RDX core (such as `apps/web`) must spread
65
+ `await getRdxTracingConfig({ rootDir })` from `@lexibase/rdx-next/config` into
66
+ their Next configuration. If Next runs in a subdirectory, also pass `appDir` and
67
+ set `outputFileTracingRoot` to include the documentation project root. Generated
68
+ `.rdx/next.config.mjs` already does this. Do not remove the explicit tracing
69
+ configuration: the core intentionally does not ask Turbopack to trace arbitrary
70
+ project-relative filesystem paths.
71
+
72
+ ## Site metadata
73
+
74
+ Configure SEO in `rdx.config.ts`; the generated layout uses Next.js's native
75
+ Metadata API. `metadata.applicationName` is the single site name, while
76
+ `description` sets the default site description at the top level.
77
+
78
+ ```ts
79
+ export default defineConfig({
80
+ description: 'Learn how to use our project.',
81
+ metadata: {
82
+ applicationName: 'My documentation',
83
+ metadataBase: 'https://docs.example.com',
84
+ authors: [{ name: 'My team', url: 'https://example.com' }],
85
+ keywords: ['documentation', 'my-project'],
86
+ icons: { icon: '/favicon.ico' },
87
+ robots: { index: true, follow: true },
88
+ openGraph: {
89
+ type: 'website',
90
+ images: [{ url: '/og.png', width: 1200, height: 630, alt: 'My project' }],
91
+ },
92
+ twitter: { card: 'summary_large_image', images: ['/og.png'] },
93
+ },
94
+ })
95
+ ```
96
+
97
+ The starter provides `metadata.applicationName: 'My documentation'`. This name
98
+ is required and supplies browser titles (including the `%s | Name` page title
99
+ template), social preview defaults, accessible logo labels, the footer and the
100
+ built-in homepage fallback. `navbar.title` is independent and optional. Custom
101
+ homepage components own their contents; RDX does not inject this name into them.
102
+ When upgrading, move the former root `title` into `metadata.applicationName`;
103
+ there is no separate `metadata.title` to repeat. Set
104
+ `metadataBase` to your real public URL when deploying; RDX does not invent a
105
+ production domain or force localhost. Image, favicon and manifest paths refer
106
+ to files you create in `public/`. Nothing creates those assets automatically.
107
+
108
+ Supported fields are `metadataBase` (an absolute http(s) string),
109
+ `applicationName`, `authors`, `keywords`, `icons`, `manifest`, `robots`,
110
+ `openGraph` and `twitter`. TypeScript describes the supported nested options,
111
+ and invalid fields fail during configuration validation. This is a portable
112
+ subset, not an unrestricted pass-through of all Next.js metadata options.
113
+ Open Graph and Twitter inherit `metadata.applicationName` and the site description
114
+ unless explicitly overridden within their own options.
115
+ No site-wide canonical or fixed Open Graph URL is emitted for every document.
@@ -11,6 +11,44 @@ import { createJiti } from 'jiti'
11
11
  import { activateVersioning, versioningPackage } from './plugin-config.mjs'
12
12
 
13
13
  const managers = new Set(['npm', 'pnpm', 'yarn'])
14
+ const runtimeVersion = createRequire(import.meta.url)('../package.json').version
15
+
16
+ // Internal installer policy; channels belong to the installed runtime release,
17
+ // not the user's docs version, invoking command or dependency range.
18
+ export function versioningInstallRequest(version, declared) {
19
+ if (declared !== undefined) {
20
+ if (typeof declared !== 'string' || !declared.trim())
21
+ throw new Error(
22
+ 'The declared @lexibase/rdx-versioning dependency must be a non-empty string.'
23
+ )
24
+ return { kind: 'restore' }
25
+ }
26
+ const match =
27
+ typeof version === 'string' &&
28
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*)?$/.exec(
29
+ version
30
+ )
31
+ if (
32
+ !match ||
33
+ match[4]
34
+ ?.split('.')
35
+ .some(
36
+ (part) => /^\d+$/.test(part) && part.length > 1 && part.startsWith('0')
37
+ )
38
+ )
39
+ throw new Error(
40
+ 'Cannot select a plugin channel: the RDX runtime version is invalid.'
41
+ )
42
+ const prerelease = match[4]?.split('.')[0]
43
+ if (prerelease && !['beta', 'rc'].includes(prerelease))
44
+ throw new Error(
45
+ `Unsupported RDX runtime prerelease channel "${prerelease}". Install and configure the official plugin manually; no channel fallback was attempted.`
46
+ )
47
+ return {
48
+ kind: 'add',
49
+ specifier: `${versioningPackage}@${prerelease ?? 'latest'}`,
50
+ }
51
+ }
14
52
 
15
53
  export function parseAddArguments(args) {
16
54
  const [plugin, ...rest] = args
@@ -83,12 +121,17 @@ export async function detectPackageManager(
83
121
  return declared ?? locks[0] ?? (managers.has(agent) ? agent : 'npm')
84
122
  }
85
123
 
86
- export function installPackage(manager, rootDir, env = process.env) {
124
+ export function installPackage(
125
+ manager,
126
+ rootDir,
127
+ env = process.env,
128
+ request = versioningInstallRequest(runtimeVersion)
129
+ ) {
87
130
  return new Promise((resolve, reject) => {
88
- const args = [
89
- manager === 'npm' ? 'install' : 'add',
90
- `${versioningPackage}@beta`,
91
- ]
131
+ const args =
132
+ request.kind === 'restore'
133
+ ? ['install']
134
+ : [manager === 'npm' ? 'install' : 'add', request.specifier]
92
135
  const nodeEntry =
93
136
  env.npm_config_user_agent?.startsWith(`${manager}/`) &&
94
137
  env.npm_execpath &&
@@ -191,13 +234,17 @@ export async function addVersioning(rootDir, options = {}) {
191
234
  throw error
192
235
  }
193
236
  if (!declared || !installed) {
194
- log(`Installing ${versioningPackage}@beta with ${manager}...`)
195
- await install(manager, rootDir, env)
237
+ const request = versioningInstallRequest(runtimeVersion, declared)
238
+ log(
239
+ request.kind === 'restore'
240
+ ? `Restoring the declared ${versioningPackage} dependency with ${manager}...`
241
+ : `Installing ${request.specifier} with ${manager}...`
242
+ )
243
+ await install(manager, rootDir, env, request)
196
244
  } else
197
245
  log(`${versioningPackage} is already installed; preserving its version.`)
198
246
 
199
247
  const temporary = path.join(rootDir, `rdx.config.add-${randomUUID()}.ts`)
200
- let backup
201
248
  try {
202
249
  await writeFile(temporary, updated, { flag: 'wx', mode: info.mode })
203
250
  try {
@@ -217,12 +264,8 @@ export async function addVersioning(rootDir, options = {}) {
217
264
  'rdx.config.ts was replaced during installation. No configuration was overwritten.'
218
265
  )
219
266
  if (updated !== original) {
220
- backup = path.join(rootDir, `rdx.config.ts.${randomUUID()}.bak`)
221
- await writeFile(backup, original, { flag: 'wx', mode: info.mode })
222
267
  await rename(temporary, filename)
223
- log(
224
- `Versioning activated. Original configuration saved to ${path.basename(backup)}.`
225
- )
268
+ log('Versioning activated in rdx.config.ts.')
226
269
  } else
227
270
  log('Versioning is already configured; no configuration changes needed.')
228
271
  } finally {
@@ -230,5 +273,5 @@ export async function addVersioning(rootDir, options = {}) {
230
273
  if (error.code !== 'ENOENT') throw error
231
274
  })
232
275
  }
233
- return { backup, changed: updated !== original }
276
+ return { changed: updated !== original }
234
277
  }
package/bin/rdx.mjs CHANGED
@@ -40,9 +40,8 @@ async function main() {
40
40
  }
41
41
 
42
42
  if (command === 'add') {
43
- const { addVersioning, parseAddArguments } = await import(
44
- './add-plugin.mjs'
45
- )
43
+ const { addVersioning, parseAddArguments } =
44
+ await import('./add-plugin.mjs')
46
45
  const options = parseAddArguments(commandArgs)
47
46
  await addVersioning(projectRoot, options)
48
47
  command = 'prepare'
package/config.d.mts ADDED
@@ -0,0 +1,5 @@
1
+ /** Explicit deployment files for a native Next.js application using RDX. */
2
+ export function getRdxTracingConfig(options: {
3
+ rootDir: string
4
+ appDir?: string
5
+ }): Promise<{ outputFileTracingIncludes: Record<string, string[]> }>
package/config.mjs ADDED
@@ -0,0 +1,80 @@
1
+ import { readdir, stat } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+
4
+ import { createRdxProject, resolveProjectPath } from '@lexibase/rdx-core/config'
5
+ import { createJiti } from 'jiti'
6
+
7
+ // These files are enumerated by Next's configuration loader, not by a server
8
+ // bundle. The core's dynamic readers deliberately leave tracing to this adapter.
9
+ async function contentFiles(directory) {
10
+ let entries
11
+ try {
12
+ entries = await readdir(directory, { withFileTypes: true })
13
+ } catch (error) {
14
+ if (error.code === 'ENOENT') return []
15
+ throw error
16
+ }
17
+ const files = await Promise.all(
18
+ entries.map(async (entry) => {
19
+ const filename = path.join(directory, entry.name)
20
+ if (entry.isDirectory()) return contentFiles(filename)
21
+ return entry.isFile() &&
22
+ (entry.name.endsWith('.mdx') || entry.name === '_category.json')
23
+ ? [filename]
24
+ : []
25
+ })
26
+ )
27
+ return files.flat()
28
+ }
29
+
30
+ // outputFileTracingIncludes takes globs. Escape configured names so a literal
31
+ // bracket, wildcard or parenthesis cannot silently widen the deployment trace.
32
+ function literalPattern(filename) {
33
+ return filename
34
+ .split(path.sep)
35
+ .join('/')
36
+ .replace(/([*?[\]{}()!+@])/g, '\\$1')
37
+ }
38
+
39
+ /** Next.js tracing for configured RDX content, independent of plugin names. */
40
+ export async function getRdxTracingConfig({ rootDir, appDir = rootDir }) {
41
+ rootDir = path.resolve(rootDir)
42
+ appDir = path.resolve(appDir)
43
+ const jiti = createJiti(import.meta.url, {
44
+ moduleCache: false,
45
+ fsCache: false,
46
+ jsx: { runtime: 'automatic' },
47
+ })
48
+ const input = await jiti.import(path.join(rootDir, 'rdx.config.ts'), {
49
+ default: true,
50
+ })
51
+ const project = await createRdxProject(input, { rootDir })
52
+ const files = (
53
+ await Promise.all(
54
+ project.sources.map((source) =>
55
+ contentFiles(
56
+ resolveProjectPath(rootDir, source.docsDir, 'content source docsDir')
57
+ )
58
+ )
59
+ )
60
+ ).flat()
61
+ for (const filename of project.runtimeFiles) {
62
+ const resolved = resolveProjectPath(
63
+ rootDir,
64
+ filename,
65
+ 'plugin runtimeFiles'
66
+ )
67
+ if (!(await stat(resolved)).isFile())
68
+ throw new Error(`Plugin runtimeFiles must name a file: ${filename}`)
69
+ files.push(resolved)
70
+ }
71
+ return {
72
+ outputFileTracingIncludes: {
73
+ '/*': [
74
+ ...new Set(
75
+ files.map((file) => literalPattern(path.relative(appDir, file)))
76
+ ),
77
+ ].sort(),
78
+ },
79
+ }
80
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { defineConfig } from '@lexibase/rdx-core/config';
2
- export { RdxConfig, RdxHomepage, RdxPlugin, RdxPluginContext, RdxRuntimeConfig } from '@lexibase/rdx-types';
2
+ export { RDXConfig, RdxHomepage, RdxMetadata, RdxPlugin, RdxPluginContext, RdxRuntimeConfig } from '@lexibase/rdx-types';
@@ -1,19 +1,33 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { RdxProject, RdxRuntimeConfig } from '@lexibase/rdx-types';
3
+ import { RdxProject, RDXConfig } from '@lexibase/rdx-types';
4
4
  export { createRdxProject, getProjectDocsPaths } from '@lexibase/rdx-core';
5
+ import { Metadata } from 'next';
6
+
7
+ declare function getSearchSources(project: RdxProject): {
8
+ source: string;
9
+ }[];
10
+ declare function getSearchResponse(project: RdxProject, key: string): Response;
11
+
12
+ /** Read metadata from the same content source and index used by the page. */
13
+ declare function getDocumentMetadata({ project, slug, }: {
14
+ project: RdxProject;
15
+ slug?: string[];
16
+ }): Metadata;
17
+ /** Translate the shared configuration through Next's native Metadata API. */
18
+ declare function getRdxMetadata(config: RDXConfig): Metadata;
5
19
 
6
20
  declare function RdxLayout({ children, config, bodyClassName, }: {
7
21
  children: ReactNode;
8
- config: RdxRuntimeConfig;
22
+ config: RDXConfig;
9
23
  bodyClassName?: string;
10
- }): react_jsx_runtime.JSX.Element;
24
+ }): react.JSX.Element;
11
25
  declare function HomePage({ project }: {
12
26
  project: RdxProject;
13
- }): react_jsx_runtime.JSX.Element;
27
+ }): react.JSX.Element;
14
28
  declare function DocsPage({ slug, project, }: {
15
29
  slug?: string[];
16
30
  project: RdxProject;
17
- }): Promise<react_jsx_runtime.JSX.Element>;
31
+ }): Promise<react.JSX.Element>;
18
32
 
19
- export { DocsPage, HomePage, RdxLayout };
33
+ export { DocsPage, HomePage, RdxLayout, getDocumentMetadata, getRdxMetadata, getSearchResponse, getSearchSources };
@@ -1,12 +1,15 @@
1
1
  // src/runtime/index.tsx
2
2
  import {
3
- getDocument,
3
+ getDocument as getDocument2,
4
4
  getDocsPaths,
5
- getSourceOptions,
6
- resolveDocsRoute
5
+ getSourceOptions as getSourceOptions2,
6
+ resolveDocsRoute as resolveDocsRoute2
7
7
  } from "@lexibase/rdx-core";
8
8
  import { Button } from "@lexibase/rdx-ui/components/button";
9
+ import { ButtonLink } from "@lexibase/rdx-ui/components/button-link";
9
10
  import { DocsSidebar } from "@lexibase/rdx-ui/components/docs-sidebar";
11
+ import { DocsToc } from "@lexibase/rdx-ui/components/docs-toc";
12
+ import { DocsSearch } from "@lexibase/rdx-ui/components/docs-search";
10
13
  import { Home } from "@lexibase/rdx-ui/components/home";
11
14
  import { Logo } from "@lexibase/rdx-ui/components/logo";
12
15
  import { mdxComponents } from "@lexibase/rdx-ui/components/mdx-components";
@@ -15,10 +18,105 @@ import {
15
18
  VersionDropdown
16
19
  } from "@lexibase/rdx-ui/components/version-dropdown";
17
20
  import { ThemeProvider } from "@lexibase/rdx-ui/providers/theme-provider";
21
+ import { notFound as notFound2, redirect } from "next/navigation";
22
+
23
+ // src/runtime/mdx.ts
18
24
  import { compileMDX } from "next-mdx-remote/rsc";
19
- import Link from "next/link";
20
- import { notFound, redirect } from "next/navigation";
25
+ import remarkGfm from "remark-gfm";
26
+ import {
27
+ remarkDocumentLinks,
28
+ rehypeDocumentationHeadings,
29
+ rehypeDocumentationCode
30
+ } from "@lexibase/rdx-core/mdx";
31
+ function compileDocumentation(source, components, links) {
32
+ return compileMDX({
33
+ source,
34
+ components,
35
+ options: {
36
+ blockJS: true,
37
+ blockDangerousJS: true,
38
+ mdxOptions: {
39
+ remarkPlugins: [remarkGfm, [remarkDocumentLinks, links]],
40
+ rehypePlugins: [rehypeDocumentationHeadings, rehypeDocumentationCode]
41
+ }
42
+ }
43
+ });
44
+ }
45
+
46
+ // src/runtime/index.tsx
21
47
  import { createRdxProject, getProjectDocsPaths } from "@lexibase/rdx-core";
48
+
49
+ // src/runtime/search.ts
50
+ import { getDocsIndex, getDocument, getSourceOptions } from "@lexibase/rdx-core";
51
+ import { buildSearchIndex } from "@lexibase/rdx-core/search";
52
+ function getSearchSources(project) {
53
+ return project.config.search ? project.sources.map((source) => ({ source: source.routePrefix || "_" })) : [];
54
+ }
55
+ function getSearchResponse(project, key) {
56
+ if (!project.config.search) return new Response(null, { status: 404 });
57
+ const source = project.sources.find(
58
+ (entry) => (entry.routePrefix || "_") === key
59
+ );
60
+ if (!source) return new Response(null, { status: 404 });
61
+ const options = getSourceOptions(project, source);
62
+ const documents = getDocsIndex(options).flatMap(
63
+ (category) => category.pages.map((page) => ({
64
+ title: page.title,
65
+ description: page.description,
66
+ category: category.title,
67
+ href: [
68
+ "/docs",
69
+ source.routePrefix,
70
+ page.slug.split("/").map(encodeURIComponent).join("/")
71
+ ].filter(Boolean).join("/"),
72
+ content: getDocument(page.slug, options)?.content ?? ""
73
+ }))
74
+ );
75
+ return Response.json(buildSearchIndex(documents));
76
+ }
77
+
78
+ // src/runtime/metadata.ts
79
+ import { resolveDocsRoute } from "@lexibase/rdx-core";
80
+ import { notFound } from "next/navigation";
81
+ function getDocumentMetadata({
82
+ project,
83
+ slug
84
+ }) {
85
+ const route = resolveDocsRoute(project, slug);
86
+ if (!route.slug) return {};
87
+ if (!route.document) notFound();
88
+ const page = route.categories.flatMap((category) => category.pages).find((page2) => page2.slug === route.slug);
89
+ return {
90
+ title: page.title,
91
+ description: page.description || project.config.description
92
+ };
93
+ }
94
+ function getRdxMetadata(config) {
95
+ const { metadataBase, ...metadata } = config.metadata;
96
+ const name = metadata.applicationName;
97
+ return {
98
+ title: { default: name, template: `%s | ${name}` },
99
+ description: config.description,
100
+ ...metadata,
101
+ ...metadataBase ? { metadataBase: new URL(metadataBase) } : {},
102
+ ...metadata.openGraph ? {
103
+ openGraph: {
104
+ title: name,
105
+ description: config.description,
106
+ ...metadata.openGraph
107
+ }
108
+ } : {},
109
+ ...metadata.twitter ? {
110
+ twitter: {
111
+ title: name,
112
+ description: config.description,
113
+ ...metadata.twitter
114
+ }
115
+ } : {}
116
+ };
117
+ }
118
+
119
+ // src/runtime/index.tsx
22
120
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
121
  function RdxLayout({
24
122
  children,
@@ -44,10 +142,10 @@ function RdxHeader({
44
142
  slug
45
143
  }) {
46
144
  const { config } = project;
47
- const { source, slug: currentSlug } = resolveDocsRoute(project, slug);
145
+ const { source, slug: currentSlug } = resolveDocsRoute2(project, slug);
48
146
  const options = project.sources.map((edition) => {
49
- const docs = getSourceOptions(project, edition);
50
- const destination = currentSlug && getDocument(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
147
+ const docs = getSourceOptions2(project, edition);
148
+ const destination = currentSlug && getDocument2(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
51
149
  return {
52
150
  id: edition.id,
53
151
  label: edition.id === "current" ? "Current" : `Version ${edition.label}`,
@@ -60,7 +158,14 @@ function RdxHeader({
60
158
  Navbar,
61
159
  {
62
160
  title: config.navbar?.title ?? "",
63
- siteTitle: config.title,
161
+ search: config.search ? /* @__PURE__ */ jsx(
162
+ DocsSearch,
163
+ {
164
+ indexUrl: `/rdx-search/${encodeURIComponent(source.routePrefix || "_")}`,
165
+ version: source.label
166
+ }
167
+ ) : void 0,
168
+ siteTitle: config.metadata.applicationName,
64
169
  logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-5" }),
65
170
  homeHref: config.homepage?.enabled ? "/" : "/docs",
66
171
  docsHref: ["/docs", source.routePrefix].filter(Boolean).join("/"),
@@ -87,7 +192,7 @@ function HomePage({ project }) {
87
192
  children: CustomHome ? /* @__PURE__ */ jsx(CustomHome, {}) : /* @__PURE__ */ jsx(
88
193
  Home,
89
194
  {
90
- title: homepage.title ?? config.title,
195
+ title: homepage.title ?? config.metadata.applicationName,
91
196
  logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-52 hover:scale-105 hover:rotate-1 transition-[scale,rotation] duration-1000" }),
92
197
  description: homepage.description,
93
198
  buttons: homepage.buttons
@@ -106,7 +211,7 @@ async function DocsPage({
106
211
  slug: currentSlug,
107
212
  document,
108
213
  categories
109
- } = resolveDocsRoute(project, slug);
214
+ } = resolveDocsRoute2(project, slug);
110
215
  if (!currentSlug)
111
216
  return /* @__PURE__ */ jsxs(Fragment, { children: [
112
217
  /* @__PURE__ */ jsx(RdxHeader, { project, slug }),
@@ -115,11 +220,17 @@ async function DocsPage({
115
220
  /* @__PURE__ */ jsx("p", { children: "Add an .mdx file inside your configured documentation directory." })
116
221
  ] })
117
222
  ] });
118
- if (!document) notFound();
119
- const { content } = await compileMDX({
120
- source: document.content,
121
- components: mdxComponents
122
- });
223
+ if (!document) notFound2();
224
+ const { content } = await compileDocumentation(
225
+ document.content,
226
+ mdxComponents,
227
+ {
228
+ sourceRoot: getSourceOptions2(project, source).docsDir,
229
+ sourcePath: document.sourcePath,
230
+ routePrefix: source.routePrefix,
231
+ documents: categories.flatMap((category2) => category2.pages)
232
+ }
233
+ );
123
234
  const title = typeof document.frontmatter.title === "string" ? document.frontmatter.title : currentSlug;
124
235
  const description = typeof document.frontmatter.description === "string" ? document.frontmatter.description : null;
125
236
  const hrefFor = (pageSlug) => ["/docs", source.routePrefix, pageSlug].filter(Boolean).join("/");
@@ -147,37 +258,69 @@ async function DocsPage({
147
258
  pathname: hrefFor(currentSlug)
148
259
  }
149
260
  ),
150
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-full overflow-y-auto pt-12 md:pt-0 outline-none", children: /* @__PURE__ */ jsxs("section", { className: "w-full min-h-full px-7 flex flex-col gap-5 pb-8 md:py-8", children: [
151
- /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-5 md:mx-auto md:max-w-[860px] pt-14 md:pt-0 flex-1", children: [
152
- /* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
153
- source.badge && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: source.badge }),
154
- /* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-600 font-semibold uppercase block -mb-0.5", children: category?.title }),
155
- /* @__PURE__ */ jsx("h1", { className: "text-4xl uppercase font-bold mb-2", children: title }),
156
- description && /* @__PURE__ */ jsx("p", { className: "text-sm text-ring max-w-[726px]", children: description })
157
- ] }),
158
- /* @__PURE__ */ jsx(
159
- "main",
160
- {
161
- id: "rdx-main",
162
- tabIndex: -1,
163
- className: "h-full w-full flex-1 max-w-[960px]",
164
- children: content
165
- }
166
- )
167
- ] }),
168
- /* @__PURE__ */ jsxs("footer", { className: "flex flex-col", children: [
169
- /* @__PURE__ */ jsxs("section", { className: "flex items-center justify-between", children: [
170
- previous ? /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(Link, { href: hrefFor(previous.slug), children: previous.title }) }) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: true, children: "Previous" }),
171
- next ? /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(Link, { href: hrefFor(next.slug), children: next.title }) }) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: true, children: "Next" })
172
- ] }),
173
- /* @__PURE__ */ jsx("section", { className: "flex items-center justify-center w-full py-5", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
174
- "\xA9 ",
175
- (/* @__PURE__ */ new Date()).getFullYear(),
176
- " ",
177
- /* @__PURE__ */ jsx("strong", { children: project.config.title })
178
- ] }) })
179
- ] })
180
- ] }) })
261
+ /* @__PURE__ */ jsxs(
262
+ "div",
263
+ {
264
+ "data-rdx-docs-scroll": true,
265
+ className: "relative flex-1 min-w-0 h-full overflow-y-auto pt-12 md:pt-0 outline-none",
266
+ children: [
267
+ project.config.TOC && /* @__PURE__ */ jsx(DocsToc, {}, hrefFor(currentSlug)),
268
+ /* @__PURE__ */ jsxs(
269
+ "section",
270
+ {
271
+ className: `w-full min-h-full px-7 flex flex-col gap-5 pb-8 md:py-8 ${project.config.TOC ? "xl:pr-20" : ""}`,
272
+ children: [
273
+ /* @__PURE__ */ jsxs("div", { className: "w-full flex flex-col gap-5 md:mx-auto md:max-w-[860px] pt-14 md:pt-0 flex-1", children: [
274
+ /* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
275
+ source.badge && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: source.badge }),
276
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-600 font-semibold uppercase block -mb-0.5", children: category?.title }),
277
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl uppercase font-bold mb-2", children: title }),
278
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-ring max-w-[726px]", children: description })
279
+ ] }),
280
+ /* @__PURE__ */ jsx(
281
+ "main",
282
+ {
283
+ id: "rdx-main",
284
+ tabIndex: -1,
285
+ className: "h-full w-full flex-1 max-w-[960px]",
286
+ children: content
287
+ }
288
+ )
289
+ ] }),
290
+ /* @__PURE__ */ jsxs("footer", { className: "flex flex-col", children: [
291
+ /* @__PURE__ */ jsxs("section", { className: "flex items-center justify-between", children: [
292
+ previous ? /* @__PURE__ */ jsx(
293
+ ButtonLink,
294
+ {
295
+ variant: "outline",
296
+ size: "sm",
297
+ href: hrefFor(previous.slug),
298
+ children: previous.title
299
+ }
300
+ ) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: true, children: "Previous" }),
301
+ next ? /* @__PURE__ */ jsx(
302
+ ButtonLink,
303
+ {
304
+ variant: "outline",
305
+ size: "sm",
306
+ href: hrefFor(next.slug),
307
+ children: next.title
308
+ }
309
+ ) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: true, children: "Next" })
310
+ ] }),
311
+ /* @__PURE__ */ jsx("section", { className: "flex items-center justify-center w-full py-5", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
312
+ "\xA9 ",
313
+ (/* @__PURE__ */ new Date()).getFullYear(),
314
+ " ",
315
+ /* @__PURE__ */ jsx("strong", { children: project.config.metadata.applicationName })
316
+ ] }) })
317
+ ] })
318
+ ]
319
+ }
320
+ )
321
+ ]
322
+ }
323
+ )
181
324
  ] })
182
325
  ] });
183
326
  }
@@ -186,5 +329,9 @@ export {
186
329
  HomePage,
187
330
  RdxLayout,
188
331
  createRdxProject,
189
- getProjectDocsPaths
332
+ getDocumentMetadata,
333
+ getProjectDocsPaths,
334
+ getRdxMetadata,
335
+ getSearchResponse,
336
+ getSearchSources
190
337
  };
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "@lexibase/rdx-next",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.0.1",
4
4
  "description": "The Next.js runtime for RDX documentation projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "engines": {
8
+ "node": "^22.18.0 || ^24.11.0"
9
+ },
7
10
  "bin": {
8
11
  "rdx": "./bin/rdx.mjs"
9
12
  },
10
13
  "exports": {
14
+ "./config": {
15
+ "types": "./config.d.mts",
16
+ "import": "./config.mjs"
17
+ },
11
18
  "./postcss.config": "./postcss.config.mjs",
12
- "./styles.css": "./src/runtime/styles.css",
19
+ "./styles.css": {
20
+ "types": "./src/runtime/styles.d.css.ts",
21
+ "default": "./src/runtime/styles.css"
22
+ },
13
23
  ".": {
14
24
  "types": "./dist/index.d.ts",
15
25
  "import": "./dist/index.js"
@@ -20,26 +30,30 @@
20
30
  }
21
31
  },
22
32
  "files": [
33
+ "config.mjs",
34
+ "config.d.mts",
23
35
  "bin",
24
36
  "dist",
25
37
  "templates",
26
38
  "postcss.config.mjs",
27
39
  "src/runtime/styles.css",
40
+ "src/runtime/styles.d.css.ts",
28
41
  "README.md"
29
42
  ],
30
43
  "dependencies": {
31
44
  "cross-spawn": "^7.0.6",
32
- "typescript": "5.9.2",
33
45
  "jiti": "^2.6.1",
34
- "next-mdx-remote": "^5.0.0",
35
- "@lexibase/rdx-ui": "1.0.0-beta.1",
36
- "@lexibase/rdx-types": "1.0.0-beta.4",
37
- "@lexibase/rdx-core": "1.0.0-beta.6"
46
+ "next-mdx-remote": "^6.0.0",
47
+ "remark-gfm": "^4",
48
+ "typescript": "6.0.3",
49
+ "@lexibase/rdx-types": "1.0.0",
50
+ "@lexibase/rdx-core": "1.0.0",
51
+ "@lexibase/rdx-ui": "1.0.0"
38
52
  },
39
53
  "peerDependencies": {
40
- "next": "^15.5.0",
41
- "react": "^19.1.0",
42
- "react-dom": "^19.1.0"
54
+ "next": "^16.4.2",
55
+ "react": "^19.3.1",
56
+ "react-dom": "^19.3.1"
43
57
  },
44
58
  "publishConfig": {
45
59
  "access": "public"
@@ -55,16 +69,17 @@
55
69
  "homepage": "https://github.com/lexibase/rdx#readme",
56
70
  "devDependencies": {
57
71
  "@types/node": "^22.15.3",
58
- "@types/react": "19.1.0",
59
- "@types/react-dom": "19.1.1",
60
- "eslint": "^9.34.0",
61
- "next": "^15.5.0",
62
- "react": "19.1.0",
63
- "react-dom": "19.1.0",
64
- "@rdx/eslint-config": "1.0.0",
65
- "@rdx/typescript-config": "1.0.0"
72
+ "@types/react": "19.3.0",
73
+ "@types/react-dom": "19.3.0",
74
+ "eslint": "^9.39.5",
75
+ "next": "^16.4.2",
76
+ "react": "19.3.1",
77
+ "react-dom": "19.3.1",
78
+ "@rdx/typescript-config": "1.0.0",
79
+ "@rdx/eslint-config": "1.0.0"
66
80
  },
67
81
  "scripts": {
82
+ "dev": "tsup src/index.ts src/runtime/index.tsx --format esm --dts --watch --external @lexibase/rdx-core --external @lexibase/rdx-types --external next --external react --external react-dom --external next-mdx-remote",
68
83
  "build": "tsup src/index.ts src/runtime/index.tsx --format esm --dts --clean --external @lexibase/rdx-core --external @lexibase/rdx-types --external next --external react --external react-dom --external next-mdx-remote",
69
84
  "lint": "eslint . --max-warnings 0",
70
85
  "check-types": "tsc --noEmit",
@@ -0,0 +1,2 @@
1
+ // Global runtime styles are loaded for their side effects only.
2
+ export {}
@@ -1,7 +1,21 @@
1
- import { DocsPage, getProjectDocsPaths } from '@lexibase/rdx-next/runtime'
1
+ import type { Metadata } from 'next'
2
+ import {
3
+ DocsPage,
4
+ getDocumentMetadata,
5
+ getProjectDocsPaths,
6
+ } from '@lexibase/rdx-next/runtime'
2
7
 
3
8
  import { getProject } from '../../../rdx-project'
4
9
 
10
+ export async function generateMetadata({
11
+ params,
12
+ }: {
13
+ params: Promise<{ slug?: string[] }>
14
+ }): Promise<Metadata> {
15
+ const { slug } = await params
16
+ return getDocumentMetadata({ project: await getProject(), slug })
17
+ }
18
+
5
19
  export async function generateStaticParams() {
6
20
  return getProjectDocsPaths(await getProject()).map((slug) => ({
7
21
  slug: slug.split('/'),
@@ -0,0 +1,2 @@
1
+ // Next.js loads this global stylesheet for its side effects only.
2
+ export {}
@@ -1,6 +1,6 @@
1
1
  import type { Metadata } from 'next'
2
2
 
3
- import { RdxLayout } from '@lexibase/rdx-next/runtime'
3
+ import { getRdxMetadata, RdxLayout } from '@lexibase/rdx-next/runtime'
4
4
 
5
5
  import { getProject } from '../rdx-project'
6
6
 
@@ -8,10 +8,7 @@ import './globals.css'
8
8
 
9
9
  export async function generateMetadata(): Promise<Metadata> {
10
10
  const { config } = await getProject()
11
- return {
12
- title: { default: config.title, template: `%s | ${config.title}` },
13
- description: config.description,
14
- }
11
+ return getRdxMetadata(config)
15
12
  }
16
13
 
17
14
  export default async function RootLayout({
@@ -0,0 +1,16 @@
1
+ import { getSearchResponse, getSearchSources } from '@lexibase/rdx-next/runtime'
2
+ import { getProject } from '../../../rdx-project'
3
+
4
+ export const dynamic = 'force-static'
5
+ export const dynamicParams = false
6
+
7
+ export async function generateStaticParams() {
8
+ return getSearchSources(await getProject())
9
+ }
10
+
11
+ export async function GET(
12
+ _request: Request,
13
+ { params }: { params: Promise<{ source: string }> }
14
+ ) {
15
+ return getSearchResponse(await getProject(), (await params).source)
16
+ }
@@ -1,7 +1,17 @@
1
+ import { fileURLToPath } from 'node:url'
2
+
3
+ import { getRdxTracingConfig } from '@lexibase/rdx-next/config'
4
+
5
+ const rootDir = fileURLToPath(new URL('..', import.meta.url))
6
+ const appDir = fileURLToPath(new URL('.', import.meta.url))
7
+
1
8
  /** @type {import('next').NextConfig} */
2
9
  const nextConfig = {
3
- experimental: {
4
- externalDir: true,
10
+ ...(await getRdxTracingConfig({ rootDir, appDir })),
11
+ outputFileTracingRoot: rootDir,
12
+ agentRules: false,
13
+ turbopack: {
14
+ root: fileURLToPath(new URL('..', import.meta.url)),
5
15
  },
6
16
  transpilePackages: [
7
17
  '@lexibase/rdx-core',
@@ -1,7 +1,13 @@
1
+ import path from 'node:path'
2
+
1
3
  import { createRdxProject } from '@lexibase/rdx-next/runtime'
2
4
 
3
5
  import config from '../rdx.config'
4
6
 
5
7
  export function getProject() {
6
- return createRdxProject(config)
8
+ // The CLI starts at the project root; serverless Next workers start in .rdx.
9
+ // Keep project-owned content outside the generated application in both cases.
10
+ const cwd = process.cwd()
11
+ const rootDir = path.basename(cwd) === '.rdx' ? path.dirname(cwd) : cwd
12
+ return createRdxProject(config, { rootDir })
7
13
  }
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "rootDir": "..",
4
+ "types": ["node", "react"],
5
+ "allowArbitraryExtensions": true,
6
+ "noUncheckedSideEffectImports": true,
3
7
  "allowJs": true,
4
8
  "esModuleInterop": true,
5
9
  "incremental": true,
6
10
  "isolatedModules": true,
7
- "jsx": "preserve",
11
+ "jsx": "react-jsx",
8
12
  "lib": ["dom", "dom.iterable", "esnext"],
9
13
  "module": "esnext",
10
14
  "moduleResolution": "bundler",
@@ -15,6 +19,12 @@
15
19
  "strict": true,
16
20
  "target": "esnext"
17
21
  },
18
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "include": [
23
+ "next-env.d.ts",
24
+ "**/*.ts",
25
+ "**/*.tsx",
26
+ ".next/types/**/*.ts",
27
+ ".next/dev/types/**/*.ts"
28
+ ],
19
29
  "exclude": ["node_modules", "public"]
20
30
  }