@lexibase/rdx-next 1.0.0-beta.9 → 1.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.
- package/README.md +82 -2
- package/bin/add-plugin.mjs +57 -14
- package/bin/rdx.mjs +2 -3
- package/config.d.mts +5 -0
- package/config.mjs +80 -0
- package/dist/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +16 -7
- package/dist/runtime/index.js +175 -44
- package/package.json +33 -18
- package/src/runtime/styles.d.css.ts +2 -0
- package/templates/runtime/app/globals.d.css.ts +2 -0
- package/templates/runtime/app/layout.tsx +2 -5
- package/templates/runtime/app/rdx-search/[source]/route.ts +16 -0
- package/templates/runtime/next.config.mjs +12 -2
- package/templates/runtime/rdx-project.ts +7 -1
- package/templates/runtime/tsconfig.json +12 -2
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
|
-
|
|
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,
|
|
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.
|
package/bin/add-plugin.mjs
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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 {
|
|
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 } =
|
|
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
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 {
|
|
2
|
+
export { RDXConfig, RdxHomepage, RdxMetadata, RdxPlugin, RdxPluginContext, RdxRuntimeConfig } from '@lexibase/rdx-types';
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { RdxProject,
|
|
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
|
+
/** Translate the shared configuration through Next's native Metadata API. */
|
|
13
|
+
declare function getRdxMetadata(config: RDXConfig): Metadata;
|
|
5
14
|
|
|
6
15
|
declare function RdxLayout({ children, config, bodyClassName, }: {
|
|
7
16
|
children: ReactNode;
|
|
8
|
-
config:
|
|
17
|
+
config: RDXConfig;
|
|
9
18
|
bodyClassName?: string;
|
|
10
|
-
}):
|
|
19
|
+
}): react.JSX.Element;
|
|
11
20
|
declare function HomePage({ project }: {
|
|
12
21
|
project: RdxProject;
|
|
13
|
-
}):
|
|
22
|
+
}): react.JSX.Element;
|
|
14
23
|
declare function DocsPage({ slug, project, }: {
|
|
15
24
|
slug?: string[];
|
|
16
25
|
project: RdxProject;
|
|
17
|
-
}): Promise<
|
|
26
|
+
}): Promise<react.JSX.Element>;
|
|
18
27
|
|
|
19
|
-
export { DocsPage, HomePage, RdxLayout };
|
|
28
|
+
export { DocsPage, HomePage, RdxLayout, getRdxMetadata, getSearchResponse, getSearchSources };
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// src/runtime/index.tsx
|
|
2
2
|
import {
|
|
3
|
-
getDocument,
|
|
3
|
+
getDocument as getDocument2,
|
|
4
4
|
getDocsPaths,
|
|
5
|
-
getSourceOptions,
|
|
5
|
+
getSourceOptions as getSourceOptions2,
|
|
6
6
|
resolveDocsRoute
|
|
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,90 @@ import {
|
|
|
15
18
|
VersionDropdown
|
|
16
19
|
} from "@lexibase/rdx-ui/components/version-dropdown";
|
|
17
20
|
import { ThemeProvider } from "@lexibase/rdx-ui/providers/theme-provider";
|
|
18
|
-
import { compileMDX } from "next-mdx-remote/rsc";
|
|
19
|
-
import Link from "next/link";
|
|
20
21
|
import { notFound, redirect } from "next/navigation";
|
|
22
|
+
|
|
23
|
+
// src/runtime/mdx.ts
|
|
24
|
+
import { compileMDX } from "next-mdx-remote/rsc";
|
|
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
|
+
function getRdxMetadata(config) {
|
|
80
|
+
const { metadataBase, ...metadata } = config.metadata;
|
|
81
|
+
const name = metadata.applicationName;
|
|
82
|
+
return {
|
|
83
|
+
title: { default: name, template: `%s | ${name}` },
|
|
84
|
+
description: config.description,
|
|
85
|
+
...metadata,
|
|
86
|
+
...metadataBase ? { metadataBase: new URL(metadataBase) } : {},
|
|
87
|
+
...metadata.openGraph ? {
|
|
88
|
+
openGraph: {
|
|
89
|
+
title: name,
|
|
90
|
+
description: config.description,
|
|
91
|
+
...metadata.openGraph
|
|
92
|
+
}
|
|
93
|
+
} : {},
|
|
94
|
+
...metadata.twitter ? {
|
|
95
|
+
twitter: {
|
|
96
|
+
title: name,
|
|
97
|
+
description: config.description,
|
|
98
|
+
...metadata.twitter
|
|
99
|
+
}
|
|
100
|
+
} : {}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/runtime/index.tsx
|
|
22
105
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
23
106
|
function RdxLayout({
|
|
24
107
|
children,
|
|
@@ -46,8 +129,8 @@ function RdxHeader({
|
|
|
46
129
|
const { config } = project;
|
|
47
130
|
const { source, slug: currentSlug } = resolveDocsRoute(project, slug);
|
|
48
131
|
const options = project.sources.map((edition) => {
|
|
49
|
-
const docs =
|
|
50
|
-
const destination = currentSlug &&
|
|
132
|
+
const docs = getSourceOptions2(project, edition);
|
|
133
|
+
const destination = currentSlug && getDocument2(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
|
|
51
134
|
return {
|
|
52
135
|
id: edition.id,
|
|
53
136
|
label: edition.id === "current" ? "Current" : `Version ${edition.label}`,
|
|
@@ -60,7 +143,14 @@ function RdxHeader({
|
|
|
60
143
|
Navbar,
|
|
61
144
|
{
|
|
62
145
|
title: config.navbar?.title ?? "",
|
|
63
|
-
|
|
146
|
+
search: config.search ? /* @__PURE__ */ jsx(
|
|
147
|
+
DocsSearch,
|
|
148
|
+
{
|
|
149
|
+
indexUrl: `/rdx-search/${encodeURIComponent(source.routePrefix || "_")}`,
|
|
150
|
+
version: source.label
|
|
151
|
+
}
|
|
152
|
+
) : void 0,
|
|
153
|
+
siteTitle: config.metadata.applicationName,
|
|
64
154
|
logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-5" }),
|
|
65
155
|
homeHref: config.homepage?.enabled ? "/" : "/docs",
|
|
66
156
|
docsHref: ["/docs", source.routePrefix].filter(Boolean).join("/"),
|
|
@@ -87,7 +177,7 @@ function HomePage({ project }) {
|
|
|
87
177
|
children: CustomHome ? /* @__PURE__ */ jsx(CustomHome, {}) : /* @__PURE__ */ jsx(
|
|
88
178
|
Home,
|
|
89
179
|
{
|
|
90
|
-
title: homepage.title ?? config.
|
|
180
|
+
title: homepage.title ?? config.metadata.applicationName,
|
|
91
181
|
logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-52 hover:scale-105 hover:rotate-1 transition-[scale,rotation] duration-1000" }),
|
|
92
182
|
description: homepage.description,
|
|
93
183
|
buttons: homepage.buttons
|
|
@@ -116,10 +206,16 @@ async function DocsPage({
|
|
|
116
206
|
] })
|
|
117
207
|
] });
|
|
118
208
|
if (!document) notFound();
|
|
119
|
-
const { content } = await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
209
|
+
const { content } = await compileDocumentation(
|
|
210
|
+
document.content,
|
|
211
|
+
mdxComponents,
|
|
212
|
+
{
|
|
213
|
+
sourceRoot: getSourceOptions2(project, source).docsDir,
|
|
214
|
+
sourcePath: document.sourcePath,
|
|
215
|
+
routePrefix: source.routePrefix,
|
|
216
|
+
documents: categories.flatMap((category2) => category2.pages)
|
|
217
|
+
}
|
|
218
|
+
);
|
|
123
219
|
const title = typeof document.frontmatter.title === "string" ? document.frontmatter.title : currentSlug;
|
|
124
220
|
const description = typeof document.frontmatter.description === "string" ? document.frontmatter.description : null;
|
|
125
221
|
const hrefFor = (pageSlug) => ["/docs", source.routePrefix, pageSlug].filter(Boolean).join("/");
|
|
@@ -147,37 +243,69 @@ async function DocsPage({
|
|
|
147
243
|
pathname: hrefFor(currentSlug)
|
|
148
244
|
}
|
|
149
245
|
),
|
|
150
|
-
/* @__PURE__ */
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
246
|
+
/* @__PURE__ */ jsxs(
|
|
247
|
+
"div",
|
|
248
|
+
{
|
|
249
|
+
"data-rdx-docs-scroll": true,
|
|
250
|
+
className: "relative flex-1 min-w-0 h-full overflow-y-auto pt-12 md:pt-0 outline-none",
|
|
251
|
+
children: [
|
|
252
|
+
project.config.TOC && /* @__PURE__ */ jsx(DocsToc, {}, hrefFor(currentSlug)),
|
|
253
|
+
/* @__PURE__ */ jsxs(
|
|
254
|
+
"section",
|
|
255
|
+
{
|
|
256
|
+
className: `w-full min-h-full px-7 flex flex-col gap-5 pb-8 md:py-8 ${project.config.TOC ? "xl:pr-20" : ""}`,
|
|
257
|
+
children: [
|
|
258
|
+
/* @__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: [
|
|
259
|
+
/* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
|
|
260
|
+
source.badge && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: source.badge }),
|
|
261
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-600 font-semibold uppercase block -mb-0.5", children: category?.title }),
|
|
262
|
+
/* @__PURE__ */ jsx("h1", { className: "text-4xl uppercase font-bold mb-2", children: title }),
|
|
263
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-sm text-ring max-w-[726px]", children: description })
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ jsx(
|
|
266
|
+
"main",
|
|
267
|
+
{
|
|
268
|
+
id: "rdx-main",
|
|
269
|
+
tabIndex: -1,
|
|
270
|
+
className: "h-full w-full flex-1 max-w-[960px]",
|
|
271
|
+
children: content
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
] }),
|
|
275
|
+
/* @__PURE__ */ jsxs("footer", { className: "flex flex-col", children: [
|
|
276
|
+
/* @__PURE__ */ jsxs("section", { className: "flex items-center justify-between", children: [
|
|
277
|
+
previous ? /* @__PURE__ */ jsx(
|
|
278
|
+
ButtonLink,
|
|
279
|
+
{
|
|
280
|
+
variant: "outline",
|
|
281
|
+
size: "sm",
|
|
282
|
+
href: hrefFor(previous.slug),
|
|
283
|
+
children: previous.title
|
|
284
|
+
}
|
|
285
|
+
) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: true, children: "Previous" }),
|
|
286
|
+
next ? /* @__PURE__ */ jsx(
|
|
287
|
+
ButtonLink,
|
|
288
|
+
{
|
|
289
|
+
variant: "outline",
|
|
290
|
+
size: "sm",
|
|
291
|
+
href: hrefFor(next.slug),
|
|
292
|
+
children: next.title
|
|
293
|
+
}
|
|
294
|
+
) : /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", disabled: true, children: "Next" })
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ jsx("section", { className: "flex items-center justify-center w-full py-5", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
297
|
+
"\xA9 ",
|
|
298
|
+
(/* @__PURE__ */ new Date()).getFullYear(),
|
|
299
|
+
" ",
|
|
300
|
+
/* @__PURE__ */ jsx("strong", { children: project.config.metadata.applicationName })
|
|
301
|
+
] }) })
|
|
302
|
+
] })
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
)
|
|
181
309
|
] })
|
|
182
310
|
] });
|
|
183
311
|
}
|
|
@@ -186,5 +314,8 @@ export {
|
|
|
186
314
|
HomePage,
|
|
187
315
|
RdxLayout,
|
|
188
316
|
createRdxProject,
|
|
189
|
-
getProjectDocsPaths
|
|
317
|
+
getProjectDocsPaths,
|
|
318
|
+
getRdxMetadata,
|
|
319
|
+
getSearchResponse,
|
|
320
|
+
getSearchSources
|
|
190
321
|
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexibase/rdx-next",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
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":
|
|
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": "^
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"@lexibase/rdx-core": "1.0.0
|
|
46
|
+
"next-mdx-remote": "^6.0.0",
|
|
47
|
+
"remark-gfm": "^4",
|
|
48
|
+
"typescript": "6.0.3",
|
|
49
|
+
"@lexibase/rdx-core": "1.0.0",
|
|
50
|
+
"@lexibase/rdx-ui": "1.0.0",
|
|
51
|
+
"@lexibase/rdx-types": "1.0.0"
|
|
38
52
|
},
|
|
39
53
|
"peerDependencies": {
|
|
40
|
-
"next": "^
|
|
41
|
-
"react": "^19.1
|
|
42
|
-
"react-dom": "^19.1
|
|
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.
|
|
59
|
-
"@types/react-dom": "19.
|
|
60
|
-
"eslint": "^9.
|
|
61
|
-
"next": "^
|
|
62
|
-
"react": "19.1
|
|
63
|
-
"react-dom": "19.1
|
|
64
|
-
"@rdx/
|
|
65
|
-
"@rdx/
|
|
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",
|
|
@@ -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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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": "
|
|
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": [
|
|
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
|
}
|