@lexibase/rdx-next 1.0.0-beta.0 → 1.0.0-beta.10
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 +36 -1
- package/bin/add-plugin.mjs +229 -0
- package/bin/plugin-config.mjs +155 -0
- package/bin/public-assets.mjs +58 -0
- package/bin/rdx.mjs +97 -23
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -0
- package/dist/runtime/index.d.ts +15 -4
- package/dist/runtime/index.js +279 -62
- package/package.json +34 -17
- package/postcss.config.mjs +1 -0
- package/src/runtime/styles.css +16 -0
- package/src/runtime/styles.d.css.ts +2 -0
- package/templates/runtime/app/docs/[[...slug]]/page.tsx +8 -5
- package/templates/runtime/app/globals.css +4 -214
- package/templates/runtime/app/globals.d.css.ts +2 -0
- package/templates/runtime/app/layout.tsx +14 -9
- package/templates/runtime/app/page.tsx +5 -3
- package/templates/runtime/app/rdx-search/[source]/route.ts +16 -0
- package/templates/runtime/next.config.mjs +11 -3
- package/templates/runtime/postcss.config.mjs +1 -0
- package/templates/runtime/rdx-project.ts +7 -0
- package/templates/runtime/tsconfig.json +16 -3
package/README.md
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
|
-
# `@rdx
|
|
1
|
+
# `@lexibase/rdx-next`
|
|
2
2
|
|
|
3
3
|
The official Next.js runtime for RDX documentation projects.
|
|
4
4
|
|
|
5
|
+
Requires Node.js 20.9 or newer; use a currently supported Node.js LTS release.
|
|
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.
|
|
13
|
+
|
|
14
|
+
Run `pnpm rdx add versioning` (or `npm exec -- rdx add versioning`) from a
|
|
15
|
+
documentation project to install and activate the official optional plugin.
|
|
16
|
+
The command detects the package manager, preserves config and plugin options,
|
|
17
|
+
validates the result before saving it without creating backups, and prepares
|
|
18
|
+
the runtime. Repeating it does not duplicate or upgrade an installed plugin.
|
|
19
|
+
Use `--package-manager npm|pnpm|yarn` to resolve ambiguous lockfiles. Dynamic
|
|
20
|
+
configurations receive manual instructions and are not automatically edited.
|
|
21
|
+
Installing a package with npm/pnpm/yarn alone never edits `rdx.config.ts`.
|
|
22
|
+
|
|
23
|
+
Generated sites use the same theme and components as the development website,
|
|
24
|
+
through `@lexibase/rdx-ui`: logo, navbar, Base UI version dropdown, mobile/desktop
|
|
25
|
+
sidebar, landing page, MDX styles and theme provider. The adapter owns content
|
|
26
|
+
and route data; it does not implement a parallel UI theme.
|
|
27
|
+
|
|
28
|
+
Use `homepage: { enabled: true }` in `rdx.config.ts` for the centered-logo
|
|
29
|
+
homepage, or import a project-owned React component with `homepage.component`.
|
|
30
|
+
Without the option, `/` redirects to `/docs`. Additional content sources from
|
|
31
|
+
explicitly configured plugins enable the shared navbar dropdown.
|
|
32
|
+
|
|
33
|
+
`.rdx/` is generated. Configure the site or edit a project-owned `home.tsx`
|
|
34
|
+
instead of modifying files inside `.rdx/`.
|
|
35
|
+
|
|
36
|
+
Place images, favicon and downloads in the project-root `public/` directory.
|
|
37
|
+
The CLI links `.rdx/public` to it using a junction on Windows or a directory
|
|
38
|
+
symlink elsewhere. `/images/logo.svg` serves `public/images/logo.svg` in dev and
|
|
39
|
+
production. No asset copies are cached and original files are never removed.
|
|
40
|
+
Restart/redeploy production after adding/removing assets. Documentation versions
|
|
41
|
+
share this directory; snapshots do not copy its contents.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import { lstat, readFile, writeFile, rename, unlink } from 'node:fs/promises'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
|
|
7
|
+
import { createRdxProject, resolveConfig } from '@lexibase/rdx-core/config'
|
|
8
|
+
import spawn from 'cross-spawn'
|
|
9
|
+
import { createJiti } from 'jiti'
|
|
10
|
+
|
|
11
|
+
import { activateVersioning, versioningPackage } from './plugin-config.mjs'
|
|
12
|
+
|
|
13
|
+
const managers = new Set(['npm', 'pnpm', 'yarn'])
|
|
14
|
+
|
|
15
|
+
export function parseAddArguments(args) {
|
|
16
|
+
const [plugin, ...rest] = args
|
|
17
|
+
if (plugin !== 'versioning')
|
|
18
|
+
throw new Error(
|
|
19
|
+
'Usage: rdx add versioning [--package-manager npm|pnpm|yarn]. Only the official versioning plugin is currently supported.'
|
|
20
|
+
)
|
|
21
|
+
if (!rest.length) return {}
|
|
22
|
+
if (
|
|
23
|
+
rest.length === 2 &&
|
|
24
|
+
rest[0] === '--package-manager' &&
|
|
25
|
+
managers.has(rest[1])
|
|
26
|
+
)
|
|
27
|
+
return { packageManager: rest[1] }
|
|
28
|
+
throw new Error(
|
|
29
|
+
'Usage: rdx add versioning [--package-manager npm|pnpm|yarn].'
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function exists(filename) {
|
|
34
|
+
try {
|
|
35
|
+
await lstat(filename)
|
|
36
|
+
return true
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT') return false
|
|
39
|
+
throw error
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function detectPackageManager(
|
|
44
|
+
rootDir,
|
|
45
|
+
pkg,
|
|
46
|
+
env = process.env,
|
|
47
|
+
explicit
|
|
48
|
+
) {
|
|
49
|
+
if (explicit) {
|
|
50
|
+
if (!managers.has(explicit)) throw new Error('Unsupported package manager.')
|
|
51
|
+
return explicit
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
pkg.packageManager !== undefined &&
|
|
55
|
+
typeof pkg.packageManager !== 'string'
|
|
56
|
+
)
|
|
57
|
+
throw new Error('packageManager in package.json must be a string.')
|
|
58
|
+
const declared = pkg.packageManager?.split('@')[0]
|
|
59
|
+
if (declared && !managers.has(declared))
|
|
60
|
+
throw new Error(
|
|
61
|
+
'Unsupported packageManager in package.json. Choose --package-manager npm, pnpm or yarn explicitly.'
|
|
62
|
+
)
|
|
63
|
+
const locks = []
|
|
64
|
+
for (const [manager, filenames] of [
|
|
65
|
+
['npm', ['package-lock.json', 'npm-shrinkwrap.json']],
|
|
66
|
+
['pnpm', ['pnpm-lock.yaml']],
|
|
67
|
+
['yarn', ['yarn.lock']],
|
|
68
|
+
]) {
|
|
69
|
+
if (
|
|
70
|
+
(
|
|
71
|
+
await Promise.all(
|
|
72
|
+
filenames.map((filename) => exists(path.join(rootDir, filename)))
|
|
73
|
+
)
|
|
74
|
+
).some(Boolean)
|
|
75
|
+
)
|
|
76
|
+
locks.push(manager)
|
|
77
|
+
}
|
|
78
|
+
if (locks.length > 1 || (declared && locks.length && locks[0] !== declared))
|
|
79
|
+
throw new Error(
|
|
80
|
+
'Conflicting package managers. Keep the intended lockfile or choose --package-manager npm, pnpm or yarn explicitly.'
|
|
81
|
+
)
|
|
82
|
+
const agent = env.npm_config_user_agent?.split('/')[0]
|
|
83
|
+
return declared ?? locks[0] ?? (managers.has(agent) ? agent : 'npm')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function installPackage(manager, rootDir, env = process.env) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const args = [
|
|
89
|
+
manager === 'npm' ? 'install' : 'add',
|
|
90
|
+
`${versioningPackage}@beta`,
|
|
91
|
+
]
|
|
92
|
+
const nodeEntry =
|
|
93
|
+
env.npm_config_user_agent?.startsWith(`${manager}/`) &&
|
|
94
|
+
env.npm_execpath &&
|
|
95
|
+
/\.[cm]?js$/i.test(env.npm_execpath)
|
|
96
|
+
const child = spawn(
|
|
97
|
+
nodeEntry ? process.execPath : manager,
|
|
98
|
+
nodeEntry ? [env.npm_execpath, ...args] : args,
|
|
99
|
+
{ cwd: rootDir, env, stdio: 'inherit' }
|
|
100
|
+
)
|
|
101
|
+
child.once('error', (error) =>
|
|
102
|
+
reject(
|
|
103
|
+
new Error(
|
|
104
|
+
`Could not run ${manager}: ${error.message}. Install it or choose --package-manager explicitly.`
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
child.once('exit', (code, signal) => {
|
|
109
|
+
if (code === 0) resolve()
|
|
110
|
+
else
|
|
111
|
+
reject(
|
|
112
|
+
new Error(
|
|
113
|
+
`${manager} installation failed (${signal ?? code}). rdx.config.ts was not changed. Package/lock files may have changed; inspect them before retrying. If pnpm reports a release-age policy error, approve only the trusted RDX release in your project policy.`
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function validateCandidate(filename) {
|
|
121
|
+
const jiti = createJiti(import.meta.url, {
|
|
122
|
+
moduleCache: false,
|
|
123
|
+
fsCache: false,
|
|
124
|
+
jsx: { runtime: 'automatic' },
|
|
125
|
+
})
|
|
126
|
+
const input = await jiti.import(filename, { default: true })
|
|
127
|
+
const config = resolveConfig(input)
|
|
128
|
+
// Validate paths and command registrations without running plugin setup hooks.
|
|
129
|
+
await createRdxProject(
|
|
130
|
+
{
|
|
131
|
+
...config,
|
|
132
|
+
plugins: config.plugins.map((plugin) => ({
|
|
133
|
+
...plugin,
|
|
134
|
+
setup: undefined,
|
|
135
|
+
})),
|
|
136
|
+
},
|
|
137
|
+
{ rootDir: path.dirname(filename), command: 'prepare' }
|
|
138
|
+
)
|
|
139
|
+
if (
|
|
140
|
+
config.plugins.filter((plugin) => plugin.name === versioningPackage)
|
|
141
|
+
.length !== 1
|
|
142
|
+
)
|
|
143
|
+
throw new Error(
|
|
144
|
+
'Expected exactly one official versioning plugin in the resulting configuration.'
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function addVersioning(rootDir, options = {}) {
|
|
149
|
+
const {
|
|
150
|
+
install = installPackage,
|
|
151
|
+
log = console.log,
|
|
152
|
+
env = process.env,
|
|
153
|
+
} = options
|
|
154
|
+
const filename = path.join(rootDir, 'rdx.config.ts')
|
|
155
|
+
let info
|
|
156
|
+
try {
|
|
157
|
+
info = await lstat(filename)
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error.code === 'ENOENT')
|
|
160
|
+
throw new Error(
|
|
161
|
+
'Missing rdx.config.ts. Run rdx add from the documentation project root.'
|
|
162
|
+
)
|
|
163
|
+
throw error
|
|
164
|
+
}
|
|
165
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
166
|
+
throw new Error('rdx.config.ts must be a regular project file, not a link.')
|
|
167
|
+
const original = await readFile(filename, 'utf8')
|
|
168
|
+
// Refuse unsupported configurations before running the package manager.
|
|
169
|
+
const updated = activateVersioning(original)
|
|
170
|
+
const pkg = JSON.parse(
|
|
171
|
+
await readFile(path.join(rootDir, 'package.json'), 'utf8')
|
|
172
|
+
)
|
|
173
|
+
const manager = await detectPackageManager(
|
|
174
|
+
rootDir,
|
|
175
|
+
pkg,
|
|
176
|
+
env,
|
|
177
|
+
options.packageManager
|
|
178
|
+
)
|
|
179
|
+
const declared =
|
|
180
|
+
pkg.dependencies?.[versioningPackage] ??
|
|
181
|
+
pkg.devDependencies?.[versioningPackage]
|
|
182
|
+
let installed = false
|
|
183
|
+
try {
|
|
184
|
+
createRequire(path.join(rootDir, 'package.json')).resolve(versioningPackage)
|
|
185
|
+
installed = true
|
|
186
|
+
} catch (error) {
|
|
187
|
+
// The official package exposes ESM imports only. require.resolve can find
|
|
188
|
+
// its manifest but reject the require condition; validation below uses Jiti.
|
|
189
|
+
if (error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') installed = true
|
|
190
|
+
else if (!['MODULE_NOT_FOUND', 'ERR_MODULE_NOT_FOUND'].includes(error.code))
|
|
191
|
+
throw error
|
|
192
|
+
}
|
|
193
|
+
if (!declared || !installed) {
|
|
194
|
+
log(`Installing ${versioningPackage}@beta with ${manager}...`)
|
|
195
|
+
await install(manager, rootDir, env)
|
|
196
|
+
} else
|
|
197
|
+
log(`${versioningPackage} is already installed; preserving its version.`)
|
|
198
|
+
|
|
199
|
+
const temporary = path.join(rootDir, `rdx.config.add-${randomUUID()}.ts`)
|
|
200
|
+
try {
|
|
201
|
+
await writeFile(temporary, updated, { flag: 'wx', mode: info.mode })
|
|
202
|
+
try {
|
|
203
|
+
await validateCandidate(temporary)
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Plugin configuration validation failed: ${error.message}. rdx.config.ts was not changed. Installed dependencies were kept; fix the error and retry.`
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
if ((await readFile(filename, 'utf8')) !== original)
|
|
210
|
+
throw new Error(
|
|
211
|
+
'rdx.config.ts changed during installation. No configuration was overwritten; retry after reviewing your edits.'
|
|
212
|
+
)
|
|
213
|
+
const currentInfo = await lstat(filename)
|
|
214
|
+
if (!currentInfo.isFile() || currentInfo.isSymbolicLink())
|
|
215
|
+
throw new Error(
|
|
216
|
+
'rdx.config.ts was replaced during installation. No configuration was overwritten.'
|
|
217
|
+
)
|
|
218
|
+
if (updated !== original) {
|
|
219
|
+
await rename(temporary, filename)
|
|
220
|
+
log('Versioning activated in rdx.config.ts.')
|
|
221
|
+
} else
|
|
222
|
+
log('Versioning is already configured; no configuration changes needed.')
|
|
223
|
+
} finally {
|
|
224
|
+
await unlink(temporary).catch((error) => {
|
|
225
|
+
if (error.code !== 'ENOENT') throw error
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
return { changed: updated !== original }
|
|
229
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import ts from 'typescript'
|
|
2
|
+
|
|
3
|
+
export const versioningPackage = '@lexibase/rdx-versioning'
|
|
4
|
+
const manual = `Cannot safely edit rdx.config.ts. Add "import { versioning } from '${versioningPackage}'" and versioning() to plugins manually, then run rdx prepare.`
|
|
5
|
+
|
|
6
|
+
function unwrap(node) {
|
|
7
|
+
while (
|
|
8
|
+
ts.isParenthesizedExpression(node) ||
|
|
9
|
+
ts.isAsExpression(node) ||
|
|
10
|
+
ts.isSatisfiesExpression(node)
|
|
11
|
+
)
|
|
12
|
+
node = node.expression
|
|
13
|
+
return node
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Edit only known literal shapes; never print/reconstruct the user's entire file.
|
|
17
|
+
export function activateVersioning(source) {
|
|
18
|
+
const file = ts.createSourceFile(
|
|
19
|
+
'rdx.config.ts',
|
|
20
|
+
source,
|
|
21
|
+
ts.ScriptTarget.Latest,
|
|
22
|
+
true,
|
|
23
|
+
ts.ScriptKind.TS
|
|
24
|
+
)
|
|
25
|
+
if (file.parseDiagnostics.length) throw new Error(manual)
|
|
26
|
+
const imports = file.statements.filter(ts.isImportDeclaration)
|
|
27
|
+
const definitions = new Set()
|
|
28
|
+
let factory
|
|
29
|
+
for (const entry of imports) {
|
|
30
|
+
const name = entry.moduleSpecifier.text
|
|
31
|
+
const clause = entry.importClause
|
|
32
|
+
if (!clause || clause.isTypeOnly) continue
|
|
33
|
+
const bindings = clause.namedBindings
|
|
34
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
35
|
+
for (const specifier of bindings.elements) {
|
|
36
|
+
if (specifier.isTypeOnly) continue
|
|
37
|
+
const imported = (specifier.propertyName ?? specifier.name).text
|
|
38
|
+
if (
|
|
39
|
+
['@lexibase/rdx-next', '@lexibase/rdx-core/config'].includes(name) &&
|
|
40
|
+
imported === 'defineConfig'
|
|
41
|
+
)
|
|
42
|
+
definitions.add(specifier.name.text)
|
|
43
|
+
if (name === versioningPackage && imported === 'versioning') {
|
|
44
|
+
if (factory) throw new Error(manual)
|
|
45
|
+
factory = specifier.name.text
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (
|
|
49
|
+
bindings &&
|
|
50
|
+
ts.isNamespaceImport(bindings) &&
|
|
51
|
+
name === versioningPackage
|
|
52
|
+
) {
|
|
53
|
+
if (factory) throw new Error(manual)
|
|
54
|
+
factory = `${bindings.name.text}.versioning`
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const exports = file.statements.filter(ts.isExportAssignment)
|
|
58
|
+
if (exports.length !== 1 || exports[0].isExportEquals) throw new Error(manual)
|
|
59
|
+
let config = unwrap(exports[0].expression)
|
|
60
|
+
if (ts.isCallExpression(config)) {
|
|
61
|
+
if (
|
|
62
|
+
!ts.isIdentifier(config.expression) ||
|
|
63
|
+
!definitions.has(config.expression.text) ||
|
|
64
|
+
config.arguments.length !== 1
|
|
65
|
+
)
|
|
66
|
+
throw new Error(manual)
|
|
67
|
+
config = unwrap(config.arguments[0])
|
|
68
|
+
}
|
|
69
|
+
if (!ts.isObjectLiteralExpression(config)) throw new Error(manual)
|
|
70
|
+
const properties = config.properties
|
|
71
|
+
if (
|
|
72
|
+
properties.some(
|
|
73
|
+
(entry) =>
|
|
74
|
+
!ts.isPropertyAssignment(entry) || ts.isComputedPropertyName(entry.name)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
throw new Error(manual)
|
|
78
|
+
const pluginsProperties = properties.filter(
|
|
79
|
+
(entry) => entry.name.text === 'plugins'
|
|
80
|
+
)
|
|
81
|
+
if (pluginsProperties.length > 1) throw new Error(manual)
|
|
82
|
+
const plugins = pluginsProperties.length
|
|
83
|
+
? unwrap(pluginsProperties[0].initializer)
|
|
84
|
+
: undefined
|
|
85
|
+
if (
|
|
86
|
+
plugins &&
|
|
87
|
+
(!ts.isArrayLiteralExpression(plugins) ||
|
|
88
|
+
plugins.elements.some(ts.isSpreadElement))
|
|
89
|
+
)
|
|
90
|
+
throw new Error(manual)
|
|
91
|
+
if (plugins && factory) {
|
|
92
|
+
const matches = plugins.elements.filter(
|
|
93
|
+
(entry) =>
|
|
94
|
+
ts.isCallExpression(unwrap(entry)) &&
|
|
95
|
+
unwrap(entry).expression.getText(file) === factory
|
|
96
|
+
)
|
|
97
|
+
if (matches.length > 1)
|
|
98
|
+
throw new Error(
|
|
99
|
+
'Versioning is configured more than once. Remove the duplicate plugin before retrying.'
|
|
100
|
+
)
|
|
101
|
+
if (matches.length === 1) return source
|
|
102
|
+
}
|
|
103
|
+
const newline = source.includes('\r\n') ? '\r\n' : '\n'
|
|
104
|
+
const edits = []
|
|
105
|
+
if (!factory) {
|
|
106
|
+
const identifiers = new Set()
|
|
107
|
+
function visit(node) {
|
|
108
|
+
if (ts.isIdentifier(node)) identifiers.add(node.text)
|
|
109
|
+
ts.forEachChild(node, visit)
|
|
110
|
+
}
|
|
111
|
+
visit(file)
|
|
112
|
+
factory = 'versioning'
|
|
113
|
+
let suffix = 1
|
|
114
|
+
while (identifiers.has(factory)) factory = `rdxVersioning${suffix++}`
|
|
115
|
+
const specifier =
|
|
116
|
+
factory === 'versioning' ? 'versioning' : `versioning as ${factory}`
|
|
117
|
+
let position = imports.length
|
|
118
|
+
? imports.at(-1).end
|
|
119
|
+
: file.statements[0].getStart(file)
|
|
120
|
+
if (!imports.length) {
|
|
121
|
+
for (const statement of file.statements) {
|
|
122
|
+
if (
|
|
123
|
+
!ts.isExpressionStatement(statement) ||
|
|
124
|
+
!ts.isStringLiteral(statement.expression)
|
|
125
|
+
)
|
|
126
|
+
break
|
|
127
|
+
position = statement.end
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
edits.push({
|
|
131
|
+
position,
|
|
132
|
+
text: `${newline}import { ${specifier} } from '${versioningPackage}'${newline}`,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
if (plugins) {
|
|
136
|
+
const position = plugins.elements.length
|
|
137
|
+
? plugins.elements.at(-1).end
|
|
138
|
+
: plugins.getStart(file) + 1
|
|
139
|
+
edits.push({
|
|
140
|
+
position,
|
|
141
|
+
text: `${plugins.elements.length ? ',' : ''} ${factory}()`,
|
|
142
|
+
})
|
|
143
|
+
} else {
|
|
144
|
+
if (properties.length && !properties.hasTrailingComma)
|
|
145
|
+
edits.push({ position: properties.at(-1).end, text: ',' })
|
|
146
|
+
edits.push({
|
|
147
|
+
position: config.end - 1,
|
|
148
|
+
text: `${newline} plugins: [${factory}()],${newline}`,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
for (const edit of edits.sort((a, b) => b.position - a.position))
|
|
152
|
+
source =
|
|
153
|
+
source.slice(0, edit.position) + edit.text + source.slice(edit.position)
|
|
154
|
+
return source
|
|
155
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { lstat, mkdir, realpath, symlink, unlink } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
5
|
+
async function inspect(filePath) {
|
|
6
|
+
try {
|
|
7
|
+
return await lstat(filePath)
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error.code === 'ENOENT') return null
|
|
10
|
+
throw error
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Link generated Next public/ to project-owned assets; never copy or delete them. */
|
|
15
|
+
export async function preparePublicAssets(projectRoot, runtimeDirectory) {
|
|
16
|
+
const runtime = await inspect(runtimeDirectory)
|
|
17
|
+
if (!runtime?.isDirectory() || runtime.isSymbolicLink())
|
|
18
|
+
throw new Error(
|
|
19
|
+
'.rdx/ must be a real directory, not a symbolic link or junction.'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const publicDirectory = path.join(projectRoot, 'public')
|
|
23
|
+
const existing = await inspect(publicDirectory)
|
|
24
|
+
if (existing && (!existing.isDirectory() || existing.isSymbolicLink()))
|
|
25
|
+
throw new Error('public/ must be a real directory in the project root.')
|
|
26
|
+
await mkdir(publicDirectory, { recursive: true })
|
|
27
|
+
|
|
28
|
+
const destination = path.join(runtimeDirectory, 'public')
|
|
29
|
+
const link = await inspect(destination)
|
|
30
|
+
if (link) {
|
|
31
|
+
if (!link.isSymbolicLink())
|
|
32
|
+
throw new Error(
|
|
33
|
+
'Cannot prepare .rdx/public: a file or directory already exists there. Move any assets to the project public/ directory, then remove the conflicting .rdx/public entry and retry. RDX has not removed it.'
|
|
34
|
+
)
|
|
35
|
+
let target
|
|
36
|
+
try {
|
|
37
|
+
target = await realpath(destination)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code !== 'ENOENT') throw error
|
|
40
|
+
}
|
|
41
|
+
if (target === (await realpath(publicDirectory))) return
|
|
42
|
+
// Only unlink the internal pointer, never its old target or the user's files.
|
|
43
|
+
await unlink(destination)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const target = process.platform === 'win32' ? publicDirectory : '../public'
|
|
47
|
+
try {
|
|
48
|
+
await symlink(
|
|
49
|
+
target,
|
|
50
|
+
destination,
|
|
51
|
+
process.platform === 'win32' ? 'junction' : 'dir'
|
|
52
|
+
)
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Could not link .rdx/public to public/: ${error.message}. Use a filesystem that supports directory links (junctions on Windows); your public files have not been removed.`
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
package/bin/rdx.mjs
CHANGED
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process'
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
5
|
-
import { cp, mkdir } from 'node:fs/promises'
|
|
5
|
+
import { access, cp, mkdir } from 'node:fs/promises'
|
|
6
6
|
import path from 'node:path'
|
|
7
7
|
import { fileURLToPath } from 'node:url'
|
|
8
8
|
import process from 'node:process'
|
|
9
9
|
|
|
10
|
+
import { createRdxProject } from '@lexibase/rdx-core/config'
|
|
11
|
+
import { createJiti } from 'jiti'
|
|
12
|
+
|
|
13
|
+
import { preparePublicAssets } from './public-assets.mjs'
|
|
14
|
+
|
|
10
15
|
const supportedCommands = new Set(['dev', 'build', 'start'])
|
|
11
|
-
|
|
16
|
+
let command = process.argv[2]
|
|
12
17
|
const commandArgs = process.argv.slice(3)
|
|
13
18
|
const projectRoot = process.cwd()
|
|
14
19
|
const packageRoot = path.resolve(
|
|
@@ -20,31 +25,100 @@ const runtimeDirectory = path.join(projectRoot, '.rdx')
|
|
|
20
25
|
|
|
21
26
|
async function prepareRuntime() {
|
|
22
27
|
await mkdir(runtimeDirectory, { recursive: true })
|
|
28
|
+
await preparePublicAssets(projectRoot, runtimeDirectory)
|
|
23
29
|
await cp(runtimeTemplate, runtimeDirectory, { force: true, recursive: true })
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
async function main() {
|
|
33
|
+
if (!command || command === 'help' || command === '--help') {
|
|
34
|
+
console.log('Usage: rdx <dev|build|start|prepare|add|plugin-command>')
|
|
35
|
+
console.log(' rdx add versioning [--package-manager npm|pnpm|yarn]')
|
|
36
|
+
console.log(
|
|
37
|
+
'Plugin commands are registered through plugins in rdx.config.ts.'
|
|
38
|
+
)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
31
41
|
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
if (command === 'add') {
|
|
43
|
+
const { addVersioning, parseAddArguments } =
|
|
44
|
+
await import('./add-plugin.mjs')
|
|
45
|
+
const options = parseAddArguments(commandArgs)
|
|
46
|
+
await addVersioning(projectRoot, options)
|
|
47
|
+
command = 'prepare'
|
|
48
|
+
}
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
const configPath = path.join(projectRoot, 'rdx.config.ts')
|
|
51
|
+
try {
|
|
52
|
+
await access(configPath)
|
|
53
|
+
} catch {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Missing ${configPath}. Run rdx from the documentation project root.`
|
|
56
|
+
)
|
|
57
|
+
}
|
|
38
58
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
const jiti = createJiti(import.meta.url, {
|
|
60
|
+
moduleCache: false,
|
|
61
|
+
fsCache: false,
|
|
62
|
+
jsx: { runtime: 'automatic' },
|
|
63
|
+
})
|
|
64
|
+
let input
|
|
65
|
+
try {
|
|
66
|
+
input = await jiti.import(configPath, { default: true })
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const hint =
|
|
69
|
+
error.code === 'MODULE_NOT_FOUND' || error.code === 'ERR_MODULE_NOT_FOUND'
|
|
70
|
+
? ' If a configured plugin cannot be resolved, install its package in this project.'
|
|
71
|
+
: ''
|
|
72
|
+
throw new Error(`Could not load rdx.config.ts: ${error.message}${hint}`)
|
|
73
|
+
}
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})
|
|
75
|
+
const project = await createRdxProject(input, {
|
|
76
|
+
rootDir: projectRoot,
|
|
77
|
+
command,
|
|
78
|
+
})
|
|
79
|
+
if (command === 'prepare') {
|
|
80
|
+
await prepareRuntime()
|
|
81
|
+
console.log('RDX runtime prepared in .rdx/')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (!supportedCommands.has(command)) {
|
|
85
|
+
const pluginCommand = project.commands.find(
|
|
86
|
+
(entry) => entry.name === command
|
|
87
|
+
)
|
|
88
|
+
if (!pluginCommand)
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Unknown command "${command}". Available: dev, build, start, prepare, add${project.commands.map((entry) => `, ${entry.name}`).join('')}. Install and configure the plugin that provides this command.`
|
|
91
|
+
)
|
|
92
|
+
await pluginCommand.run({
|
|
93
|
+
rootDir: projectRoot,
|
|
94
|
+
config: project.config,
|
|
95
|
+
command,
|
|
96
|
+
args: commandArgs,
|
|
97
|
+
})
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await prepareRuntime()
|
|
102
|
+
const projectRequire = createRequire(path.join(projectRoot, 'package.json'))
|
|
103
|
+
const nextCli = projectRequire.resolve('next/dist/bin/next')
|
|
104
|
+
const child = spawn(
|
|
105
|
+
process.execPath,
|
|
106
|
+
[nextCli, command, runtimeDirectory, ...commandArgs],
|
|
107
|
+
{ cwd: projectRoot, stdio: 'inherit' }
|
|
108
|
+
)
|
|
109
|
+
child.on('error', (error) => {
|
|
110
|
+
console.error(`[RDX] Could not start Next.js: ${error.message}`)
|
|
111
|
+
process.exitCode = 1
|
|
112
|
+
})
|
|
113
|
+
child.on('exit', (code, signal) => {
|
|
114
|
+
if (signal) process.kill(process.pid, signal)
|
|
115
|
+
process.exit(code ?? 1)
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await main()
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(`[RDX] ${error.message}`)
|
|
123
|
+
process.exitCode = 1
|
|
124
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { defineConfig } from '@lexibase/rdx-core/config';
|
|
2
|
+
export { RdxConfig, RdxHomepage, RdxPlugin, RdxPluginContext, RdxRuntimeConfig } from '@lexibase/rdx-types';
|
package/dist/index.js
CHANGED
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { RdxRuntimeConfig } from '@lexibase/rdx-types';
|
|
3
|
+
import { RdxProject, RdxRuntimeConfig } from '@lexibase/rdx-types';
|
|
4
|
+
export { createRdxProject, getProjectDocsPaths } from '@lexibase/rdx-core';
|
|
4
5
|
|
|
5
|
-
declare function
|
|
6
|
+
declare function getSearchSources(project: RdxProject): {
|
|
7
|
+
source: string;
|
|
8
|
+
}[];
|
|
9
|
+
declare function getSearchResponse(project: RdxProject, key: string): Response;
|
|
10
|
+
|
|
11
|
+
declare function RdxLayout({ children, config, bodyClassName, }: {
|
|
6
12
|
children: ReactNode;
|
|
7
13
|
config: RdxRuntimeConfig;
|
|
14
|
+
bodyClassName?: string;
|
|
15
|
+
}): react.JSX.Element;
|
|
16
|
+
declare function HomePage({ project }: {
|
|
17
|
+
project: RdxProject;
|
|
8
18
|
}): react.JSX.Element;
|
|
9
|
-
declare function DocsPage({ slug }: {
|
|
19
|
+
declare function DocsPage({ slug, project, }: {
|
|
10
20
|
slug?: string[];
|
|
21
|
+
project: RdxProject;
|
|
11
22
|
}): Promise<react.JSX.Element>;
|
|
12
23
|
|
|
13
|
-
export { DocsPage, RdxLayout };
|
|
24
|
+
export { DocsPage, HomePage, RdxLayout, getSearchResponse, getSearchSources };
|