@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 +6 -1
- package/bin/index.js +19 -4
- package/bin/pnpm-progress.mjs +245 -0
- package/bin/progress.mjs +7 -4
- package/package.json +1 -1
- package/templates/default/package.json +1 -1
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.
|
|
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
|
-
|
|
42
|
-
|
|
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 =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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