@lexibase/create-rdx 4.2.0 → 4.2.2-beta.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 CHANGED
@@ -41,7 +41,12 @@ the runtime. Follow the commands printed by the generator.
41
41
 
42
42
  The creator reports file creation, dependency installation, and runtime preparation
43
43
  as separate stages, with elapsed time and periodic activity messages during long
44
- operations. Package-manager output and prompts stay visible. npm and pnpm projects
44
+ operations. During pnpm installation, RDX shows a rotating activity indicator and
45
+ a bar based on the number of packages linked. Resolution, cache and download
46
+ counts come from pnpm's structured logs; the bar waits for a known package total.
47
+ Warnings, errors and lifecycle output remain visible. Logs without a terminal use
48
+ plain status lines instead of animation. npm and Yarn retain their native output.
49
+ npm and pnpm projects
45
50
  prepare using their installed RDX executable directly; Yarn provides its project
46
51
  execution environment for Plug'n'Play compatibility.
47
52
 
package/bin/index.js CHANGED
@@ -11,6 +11,7 @@ import { parseArguments, validateProjectName } from './options.mjs'
11
11
  import { normalizePnpmRdxApprovals } from './pnpm-policy.mjs'
12
12
  import { installedRuntime } from './installed-runtime.mjs'
13
13
  import { runStep } from './progress.mjs'
14
+ import { createPnpmProgress } from './pnpm-progress.mjs'
14
15
 
15
16
  function getPackageManager() {
16
17
  const userAgent = process.env.npm_config_user_agent || ''
@@ -32,14 +33,27 @@ function run(command, args, cwd, label = `${command} ${args.join(' ')}`) {
32
33
  /\.[cm]?js$/i.test(packageManagerEntry)
33
34
  const executable = useNodeEntry ? process.execPath : command
34
35
  const commandArgs = useNodeEntry ? [packageManagerEntry, ...args] : args
36
+ const progress =
37
+ command === 'pnpm' && args[0] === 'install'
38
+ ? createPnpmProgress()
39
+ : undefined
40
+ if (progress) commandArgs.push('--reporter=ndjson')
35
41
  const child = spawn(executable, commandArgs, {
36
42
  cwd,
37
- stdio: 'inherit',
43
+ stdio: progress ? ['inherit', 'pipe', 'pipe'] : 'inherit',
38
44
  windowsHide: true,
39
45
  })
40
46
 
41
- child.on('error', reject)
42
- child.on('exit', (code) => {
47
+ if (progress) {
48
+ child.stdout.setEncoding('utf8').on('data', progress.stdout)
49
+ child.stderr.setEncoding('utf8').on('data', progress.stderr)
50
+ }
51
+ child.on('error', (error) => {
52
+ progress?.finish(false)
53
+ reject(error)
54
+ })
55
+ child.on('close', (code) => {
56
+ progress?.finish(code === 0)
43
57
  if (code === 0) resolve()
44
58
  else reject(new Error(`${label} exited with ${code}`))
45
59
  })
@@ -165,7 +179,8 @@ async function main() {
165
179
  await run(packageManager, ['install'], targetDir)
166
180
  if (packageManager === 'pnpm')
167
181
  await normalizePnpmRdxApprovals(targetDir)
168
- }
182
+ },
183
+ { intervalMs: packageManager === 'pnpm' ? 0 : 15_000 }
169
184
  )
170
185
  await runStep('[3/3] Preparing RDX...', async () => {
171
186
  if (packageManager === 'yarn') {
@@ -0,0 +1,245 @@
1
+ import { performance } from 'node:perf_hooks'
2
+ import process from 'node:process'
3
+
4
+ /** Adapt pnpm's NDJSON logs; the bar measures package linking, not elapsed time. */
5
+ export function createPnpmProgress({
6
+ output = process.stdout,
7
+ errors = process.stderr,
8
+ interactive = Boolean(output.isTTY) && process.env.TERM !== 'dumb',
9
+ intervalMs = 125,
10
+ heartbeatMs = 15_000,
11
+ now = () => performance.now(),
12
+ } = {}) {
13
+ const started = now()
14
+ const counts = { resolved: 0, cached: 0, downloaded: 0, linked: 0 }
15
+ const statuses = {
16
+ resolved: 'resolved',
17
+ found_in_store: 'cached',
18
+ fetched: 'downloaded',
19
+ imported: 'linked',
20
+ }
21
+ const totals = new Map()
22
+ const seen = new Set()
23
+ let phase = 'Resolving dependencies'
24
+ let buffer = ''
25
+ let frame = 0
26
+ let finished = false
27
+ let promptOpen = false
28
+ let previousPhase
29
+ let frameWidth = 0
30
+ let platformWarning
31
+ let skippedPlatforms = 0
32
+ const elapsed = () => `${((now() - started) / 1000).toFixed(1)}s`
33
+ const clear = () => {
34
+ if (interactive && frameWidth) {
35
+ output.write('\r\x1b[2K')
36
+ frameWidth = 0
37
+ }
38
+ }
39
+ function render() {
40
+ const total = totals.size
41
+ ? [...totals.values()].reduce((sum, value) => sum + value, 0)
42
+ : undefined
43
+ const width = output.columns || 100
44
+ const cells = width < 100 ? 10 : 16
45
+ const filled =
46
+ total > 0
47
+ ? Math.min(cells, Math.floor((counts.linked / total) * cells))
48
+ : 0
49
+ const bar = `[${'#'.repeat(filled)}${'-'.repeat(cells - filled)}]`
50
+ const packages =
51
+ total === undefined ? `${counts.linked}/?` : `${counts.linked}/${total}`
52
+ const detail = `${width >= 110 ? `resolved ${counts.resolved}, ` : ''}cached ${counts.cached}, downloaded ${counts.downloaded}`
53
+ const shortPhase = phase.split(' ')[0]
54
+ const text =
55
+ width < 70
56
+ ? `${packages} packages | ${elapsed()}`
57
+ : `${shortPhase} ${packages} | ${elapsed()} | ${detail}`
58
+ return `${['|', '/', '-', '\\'][frame++ % 4]} ${bar} ${text}`.slice(
59
+ 0,
60
+ Math.max(1, width - 1)
61
+ )
62
+ }
63
+ function draw(force = false) {
64
+ if (finished || promptOpen) return
65
+ if (interactive) {
66
+ // pnpm emits many debug records per package. Paint on the timer instead
67
+ // of turning each record into terminal writes during large installations.
68
+ if (!force && intervalMs > 0 && previousPhase === phase) return
69
+ const line = render()
70
+ const previousWidth = Math.min(
71
+ frameWidth,
72
+ Math.max(1, (output.columns || 100) - 1)
73
+ )
74
+ // Write the replacement and any trailing padding atomically. Clearing the
75
+ // row in a separate write exposes a blank frame in Windows terminals.
76
+ output.write('\r' + line.padEnd(previousWidth, ' '))
77
+ frameWidth = line.length
78
+ } else if (force || previousPhase !== phase) {
79
+ output.write(
80
+ `[RDX] ${phase}: resolved ${counts.resolved}, cached ${counts.cached}, downloaded ${counts.downloaded}, linked ${counts.linked} (${elapsed()}).\n`
81
+ )
82
+ }
83
+ previousPhase = phase
84
+ }
85
+ function message(text, stream = output) {
86
+ flushPlatformWarning()
87
+ if (!promptOpen) clear()
88
+ stream.write(text)
89
+ // Keep prompts without a newline visible and leave the cursor after them.
90
+ promptOpen = !text.endsWith('\n')
91
+ if (text.endsWith('\n')) draw(true)
92
+ }
93
+ function diagnostic(event) {
94
+ const error = event.err || event
95
+ const text = [
96
+ error.code,
97
+ error.message || event.message,
98
+ error.hint,
99
+ error.pkgsStack && JSON.stringify(error.pkgsStack),
100
+ ]
101
+ .filter(Boolean)
102
+ .join('\n')
103
+ message(`[pnpm] ${text || JSON.stringify(event)}\n`, errors)
104
+ }
105
+ function flushPlatformWarning() {
106
+ if (!platformWarning) return
107
+ const { event } = platformWarning
108
+ platformWarning = undefined
109
+ diagnostic(event)
110
+ }
111
+ function record(line) {
112
+ if (!line.trim()) {
113
+ if (promptOpen) message('\n')
114
+ return
115
+ }
116
+ let event
117
+ try {
118
+ event = JSON.parse(line)
119
+ } catch {
120
+ message(line + '\n')
121
+ return
122
+ }
123
+ if (
124
+ !event ||
125
+ typeof event !== 'object' ||
126
+ typeof event.name !== 'string' ||
127
+ !(event.name === 'pnpm' || event.name.startsWith('pnpm:'))
128
+ ) {
129
+ message(line + '\n')
130
+ return
131
+ }
132
+ if (promptOpen) {
133
+ output.write('\n')
134
+ promptOpen = false
135
+ }
136
+ if (platformWarning) {
137
+ if (
138
+ event.name === 'pnpm:skipped-optional-dependency' &&
139
+ event.reason === 'unsupported_platform' &&
140
+ event.package?.id === platformWarning.id &&
141
+ event.prefix === platformWarning.event.prefix
142
+ ) {
143
+ platformWarning = undefined
144
+ skippedPlatforms++
145
+ return
146
+ }
147
+ flushPlatformWarning()
148
+ }
149
+ if (event.name === 'pnpm:install-check' && event.level === 'warn') {
150
+ const match = /^Unsupported platform for (.+?): wanted /.exec(
151
+ event.message || ''
152
+ )
153
+ if (match) {
154
+ // pnpm emits the warning before confirming that this is an optional
155
+ // package for another OS/CPU. Suppress only the confirmed matching pair.
156
+ platformWarning = { id: match[1], event }
157
+ return
158
+ }
159
+ }
160
+ if (
161
+ event.name === 'pnpm:progress' &&
162
+ Object.hasOwn(statuses, event.status)
163
+ ) {
164
+ const key = statuses[event.status]
165
+ const id = event.to || event.packageId
166
+ const identity = id && JSON.stringify([event.requester, event.status, id])
167
+ if (!identity || !seen.has(identity)) {
168
+ counts[key]++
169
+ if (identity) seen.add(identity)
170
+ }
171
+ if (key === 'linked') phase = 'Linking packages'
172
+ } else if (
173
+ event.name === 'pnpm:fetching-progress' &&
174
+ event.status === 'started' &&
175
+ event.size >= 1024 * 1024
176
+ ) {
177
+ message(
178
+ `[RDX] Downloading ${event.packageId} (${(event.size / 1024 / 1024).toFixed(1)} MB).\n`
179
+ )
180
+ } else if (event.name === 'pnpm:stats' && Number.isFinite(event.added)) {
181
+ totals.set(event.prefix || '', Math.max(0, event.added))
182
+ } else if (event.name === 'pnpm:stage') {
183
+ if (event.stage === 'importing_started') phase = 'Linking packages'
184
+ if (event.stage === 'importing_done') phase = 'Finalizing installation'
185
+ } else if (event.name === 'pnpm:lifecycle' && event.line) {
186
+ message(`[pnpm:${event.stage || 'script'}] ${event.line}\n`)
187
+ } else if (event.level === 'warn' || event.level === 'error') {
188
+ diagnostic(event)
189
+ } else if (event.name === 'pnpm:peer-dependency-issues') {
190
+ message(
191
+ `[pnpm] Peer dependency issues:\n${JSON.stringify(event.issuesByProjects || event, null, 2)}\n`,
192
+ errors
193
+ )
194
+ } else if (event.level === 'info' && event.message) {
195
+ message(`[pnpm] ${event.message}\n`)
196
+ }
197
+ draw()
198
+ }
199
+ draw(true)
200
+ const timer =
201
+ intervalMs > 0
202
+ ? setInterval(() => draw(true), interactive ? intervalMs : heartbeatMs)
203
+ : undefined
204
+ timer?.unref()
205
+ return {
206
+ stdout(chunk) {
207
+ buffer += chunk
208
+ let end
209
+ while ((end = buffer.indexOf('\n')) !== -1) {
210
+ const line = buffer.slice(0, end).replace(/\r$/, '')
211
+ buffer = buffer.slice(end + 1)
212
+ record(line)
213
+ }
214
+ if (buffer && !buffer.trimStart().startsWith('{')) {
215
+ message(buffer)
216
+ buffer = ''
217
+ }
218
+ },
219
+ stderr(chunk) {
220
+ message(chunk, errors)
221
+ },
222
+ finish(success) {
223
+ if (finished) return
224
+ clearInterval(timer)
225
+ if (buffer) {
226
+ record(buffer)
227
+ buffer = ''
228
+ }
229
+ flushPlatformWarning()
230
+ finished = true
231
+ if (promptOpen) {
232
+ output.write('\n')
233
+ promptOpen = false
234
+ }
235
+ clear()
236
+ if (skippedPlatforms)
237
+ output.write(
238
+ `[RDX] Skipped ${skippedPlatforms} optional package${skippedPlatforms === 1 ? '' : 's'} for other platforms.\n`
239
+ )
240
+ output.write(
241
+ `[RDX] ${success ? 'Dependencies installed' : 'Installation stopped'}: ${counts.linked} linked, ${counts.cached} cached, ${counts.downloaded} downloaded (${elapsed()}).\n`
242
+ )
243
+ },
244
+ }
245
+ }
package/bin/progress.mjs CHANGED
@@ -9,10 +9,13 @@ export async function runStep(
9
9
  const started = performance.now()
10
10
  const elapsed = () => ((performance.now() - started) / 1000).toFixed(1)
11
11
  log(`\n${label}`)
12
- const timer = setInterval(() => {
13
- log(`[RDX] ${label} (${elapsed()}s elapsed)`)
14
- }, intervalMs)
15
- timer.unref()
12
+ const timer =
13
+ intervalMs > 0
14
+ ? setInterval(() => {
15
+ log(`[RDX] ${label} (${elapsed()}s elapsed)`)
16
+ }, intervalMs)
17
+ : undefined
18
+ timer?.unref()
16
19
  try {
17
20
  const result = await operation()
18
21
  log(`[RDX] Completed in ${elapsed()}s.`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexibase/create-rdx",
3
- "version": "4.2.0",
3
+ "version": "4.2.2-beta.0",
4
4
  "description": "Create a new RDX documentation project.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  "start": "rdx start"
10
10
  },
11
11
  "dependencies": {
12
- "@lexibase/rdx": "4.2.0",
12
+ "@lexibase/rdx": "4.2.1",
13
13
  "next": "^16.4.2",
14
14
  "react": "19.3.1",
15
15
  "react-dom": "19.3.1"