@lexibase/create-rdx 4.2.0 → 4.2.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
@@ -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,193 @@
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 = 100,
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
+ const elapsed = () => `${((now() - started) / 1000).toFixed(1)}s`
30
+ const clear = () => {
31
+ if (interactive) output.write('\r\x1b[2K')
32
+ }
33
+ function render() {
34
+ const total = totals.size
35
+ ? [...totals.values()].reduce((sum, value) => sum + value, 0)
36
+ : undefined
37
+ const width = output.columns || 100
38
+ const cells = width < 100 ? 10 : 16
39
+ const filled =
40
+ total > 0
41
+ ? Math.min(cells, Math.floor((counts.linked / total) * cells))
42
+ : 0
43
+ const bar = `[${'#'.repeat(filled)}${'-'.repeat(cells - filled)}]`
44
+ const packages =
45
+ total === undefined ? `${counts.linked}/?` : `${counts.linked}/${total}`
46
+ const detail = `${width >= 110 ? `resolved ${counts.resolved}, ` : ''}cached ${counts.cached}, downloaded ${counts.downloaded}`
47
+ const shortPhase = phase.split(' ')[0]
48
+ const text =
49
+ width < 70
50
+ ? `${packages} packages | ${elapsed()}`
51
+ : `${shortPhase} ${packages} | ${elapsed()} | ${detail}`
52
+ return `${['|', '/', '-', '\\'][frame++ % 4]} ${bar} ${text}`.slice(
53
+ 0,
54
+ Math.max(1, width - 1)
55
+ )
56
+ }
57
+ function draw(force = false) {
58
+ if (finished || promptOpen) return
59
+ if (interactive) {
60
+ // pnpm emits many debug records per package. Paint on the timer instead
61
+ // of turning each record into terminal writes during large installations.
62
+ if (!force && intervalMs > 0 && previousPhase === phase) return
63
+ clear()
64
+ output.write(render())
65
+ } else if (force || previousPhase !== phase) {
66
+ output.write(
67
+ `[RDX] ${phase}: resolved ${counts.resolved}, cached ${counts.cached}, downloaded ${counts.downloaded}, linked ${counts.linked} (${elapsed()}).\n`
68
+ )
69
+ }
70
+ previousPhase = phase
71
+ }
72
+ function message(text, stream = output) {
73
+ if (!promptOpen) clear()
74
+ stream.write(text)
75
+ // Keep prompts without a newline visible and leave the cursor after them.
76
+ promptOpen = !text.endsWith('\n')
77
+ if (text.endsWith('\n')) draw(true)
78
+ }
79
+ function record(line) {
80
+ if (!line.trim()) {
81
+ if (promptOpen) message('\n')
82
+ return
83
+ }
84
+ let event
85
+ try {
86
+ event = JSON.parse(line)
87
+ } catch {
88
+ message(line + '\n')
89
+ return
90
+ }
91
+ if (
92
+ !event ||
93
+ typeof event !== 'object' ||
94
+ typeof event.name !== 'string' ||
95
+ !(event.name === 'pnpm' || event.name.startsWith('pnpm:'))
96
+ ) {
97
+ message(line + '\n')
98
+ return
99
+ }
100
+ if (promptOpen) {
101
+ output.write('\n')
102
+ promptOpen = false
103
+ }
104
+ if (
105
+ event.name === 'pnpm:progress' &&
106
+ Object.hasOwn(statuses, event.status)
107
+ ) {
108
+ const key = statuses[event.status]
109
+ const id = event.to || event.packageId
110
+ const identity = id && JSON.stringify([event.requester, event.status, id])
111
+ if (!identity || !seen.has(identity)) {
112
+ counts[key]++
113
+ if (identity) seen.add(identity)
114
+ }
115
+ if (key === 'linked') phase = 'Linking packages'
116
+ } else if (
117
+ event.name === 'pnpm:fetching-progress' &&
118
+ event.status === 'started' &&
119
+ event.size >= 1024 * 1024
120
+ ) {
121
+ message(
122
+ `[RDX] Downloading ${event.packageId} (${(event.size / 1024 / 1024).toFixed(1)} MB).\n`
123
+ )
124
+ } else if (event.name === 'pnpm:stats' && Number.isFinite(event.added)) {
125
+ totals.set(event.prefix || '', Math.max(0, event.added))
126
+ } else if (event.name === 'pnpm:stage') {
127
+ if (event.stage === 'importing_started') phase = 'Linking packages'
128
+ if (event.stage === 'importing_done') phase = 'Finalizing installation'
129
+ } else if (event.name === 'pnpm:lifecycle' && event.line) {
130
+ message(`[pnpm:${event.stage || 'script'}] ${event.line}\n`)
131
+ } else if (event.level === 'warn' || event.level === 'error') {
132
+ const error = event.err || event
133
+ const text = [
134
+ error.code,
135
+ error.message || event.message,
136
+ error.hint,
137
+ error.pkgsStack && JSON.stringify(error.pkgsStack),
138
+ ]
139
+ .filter(Boolean)
140
+ .join('\n')
141
+ message(`[pnpm] ${text || JSON.stringify(event)}\n`, errors)
142
+ } else if (event.name === 'pnpm:peer-dependency-issues') {
143
+ message(
144
+ `[pnpm] Peer dependency issues:\n${JSON.stringify(event.issuesByProjects || event, null, 2)}\n`,
145
+ errors
146
+ )
147
+ } else if (event.level === 'info' && event.message) {
148
+ message(`[pnpm] ${event.message}\n`)
149
+ }
150
+ draw()
151
+ }
152
+ draw(true)
153
+ const timer =
154
+ intervalMs > 0
155
+ ? setInterval(() => draw(true), interactive ? intervalMs : heartbeatMs)
156
+ : undefined
157
+ timer?.unref()
158
+ return {
159
+ stdout(chunk) {
160
+ buffer += chunk
161
+ let end
162
+ while ((end = buffer.indexOf('\n')) !== -1) {
163
+ const line = buffer.slice(0, end).replace(/\r$/, '')
164
+ buffer = buffer.slice(end + 1)
165
+ record(line)
166
+ }
167
+ if (buffer && !buffer.trimStart().startsWith('{')) {
168
+ message(buffer)
169
+ buffer = ''
170
+ }
171
+ },
172
+ stderr(chunk) {
173
+ message(chunk, errors)
174
+ },
175
+ finish(success) {
176
+ if (finished) return
177
+ clearInterval(timer)
178
+ if (buffer) {
179
+ record(buffer)
180
+ buffer = ''
181
+ }
182
+ finished = true
183
+ if (promptOpen) {
184
+ output.write('\n')
185
+ promptOpen = false
186
+ }
187
+ clear()
188
+ output.write(
189
+ `[RDX] ${success ? 'Dependencies installed' : 'Installation stopped'}: ${counts.linked} linked, ${counts.cached} cached, ${counts.downloaded} downloaded (${elapsed()}).\n`
190
+ )
191
+ },
192
+ }
193
+ }
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.1",
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"