1 |
|
2 | import * as ansi from '@bevry/ansi'
|
3 | import figures from '@bevry/figures'
|
4 | import versionClean from 'version-clean'
|
5 |
|
6 |
|
7 | import {
|
8 | runCommand,
|
9 | runVersion,
|
10 | runInstall,
|
11 | uniq,
|
12 | trim,
|
13 | loadVersion,
|
14 | lastLine,
|
15 | } from './util.js'
|
16 |
|
17 | export type Status =
|
18 | | 'pending'
|
19 | | 'loading'
|
20 | | 'missing'
|
21 | | 'failed'
|
22 | | 'loaded'
|
23 | | 'installing'
|
24 | | 'installed'
|
25 | | 'running'
|
26 | | 'passed'
|
27 |
|
28 | export type Row = [
|
29 | icon: string,
|
30 | version_or_alias: string,
|
31 | rendered_status: string,
|
32 | duration: string,
|
33 | ]
|
34 |
|
35 | function getTime() {
|
36 | return Date.now()
|
37 | }
|
38 |
|
39 |
|
40 | export class Version {
|
41 |
|
42 | version: string
|
43 |
|
44 |
|
45 | listeners: Array<Function> = []
|
46 |
|
47 |
|
48 | aliases: Array<string> = []
|
49 |
|
50 |
|
51 | status: Status = 'pending'
|
52 |
|
53 | |
54 |
|
55 |
|
56 |
|
57 | loadedVersion: string | null = null
|
58 |
|
59 |
|
60 | success: boolean | null = null
|
61 |
|
62 |
|
63 | error: Error | null = null
|
64 |
|
65 |
|
66 | stdout: string | null = null
|
67 |
|
68 |
|
69 | stderr: string | null = null
|
70 |
|
71 |
|
72 | started: number | null = null
|
73 |
|
74 |
|
75 | finished: number | null = null
|
76 |
|
77 |
|
78 | private messageCache: [status: Status, message: string] | null = null
|
79 |
|
80 |
|
81 | constructor(version: string | number, listeners: Array<Function> = []) {
|
82 | this.listeners.push(...listeners)
|
83 | this.version = String(version)
|
84 |
|
85 |
|
86 | if (!versionClean(this.version)) {
|
87 |
|
88 | this.alias = this.version
|
89 | }
|
90 | }
|
91 |
|
92 |
|
93 | get alias() {
|
94 | return this.aliases[0]
|
95 | }
|
96 | set alias(alias) {
|
97 | if (alias) {
|
98 | const aliases = this.aliases.concat(alias)
|
99 | this.aliases = uniq(aliases)
|
100 | }
|
101 | }
|
102 |
|
103 |
|
104 | reset() {
|
105 | this.success = null
|
106 | this.error = null
|
107 | this.stdout = null
|
108 | this.stderr = null
|
109 | this.started = null
|
110 | this.finished = null
|
111 | this.messageCache = null
|
112 | return this
|
113 | }
|
114 |
|
115 | |
116 |
|
117 |
|
118 |
|
119 |
|
120 | async update(status?: Status) {
|
121 | if (status) this.status = status
|
122 | await Promise.all(this.listeners.map((listener) => listener(this)))
|
123 | return this
|
124 | }
|
125 |
|
126 |
|
127 | async load() {
|
128 | this.status = 'loading'
|
129 | this.reset()
|
130 | await this.update()
|
131 |
|
132 | const result = await loadVersion(this.version)
|
133 | if (result.error) {
|
134 | if ((result.error || '').toString().includes('not yet installed')) {
|
135 | this.status = 'missing'
|
136 | this.success = false
|
137 | } else {
|
138 | this.status = 'failed'
|
139 | this.success = false
|
140 | }
|
141 | this.error = result.error
|
142 | this.stdout = (result.stdout || '').toString()
|
143 | this.stderr = (result.stderr || '').toString()
|
144 | } else {
|
145 | const result = await runVersion(this.version)
|
146 | if (result.error) {
|
147 | this.status = 'failed'
|
148 | this.success = false
|
149 | this.error = result.error
|
150 | this.stdout = (result.stdout || '').toString()
|
151 | this.stderr = (result.stderr || '').toString()
|
152 | } else {
|
153 | this.loadedVersion = this.loadedVersion || this.version
|
154 | this.version = lastLine(result.stdout)
|
155 | this.status = 'loaded'
|
156 | }
|
157 | }
|
158 |
|
159 | await this.update()
|
160 | return this
|
161 | }
|
162 |
|
163 | |
164 |
|
165 |
|
166 |
|
167 | async install() {
|
168 | if (this.status !== 'missing') return this
|
169 |
|
170 | this.status = 'installing'
|
171 | this.reset()
|
172 | await this.update()
|
173 |
|
174 | const result = await runInstall(this.version)
|
175 | if (result.error) {
|
176 | this.error = result.error
|
177 | this.status = 'missing'
|
178 | this.success = false
|
179 | this.stdout = (result.stdout || '').toString()
|
180 | this.stderr = (result.stderr || '').toString()
|
181 | } else {
|
182 | await this.update('installed')
|
183 | await this.load()
|
184 | }
|
185 |
|
186 | return this
|
187 | }
|
188 |
|
189 | |
190 |
|
191 |
|
192 |
|
193 | async test(command: string) {
|
194 | if (!command) {
|
195 | throw new Error('no command provided to the testen version runner')
|
196 | }
|
197 | if (this.status !== 'loaded') return this
|
198 |
|
199 | this.status = 'running'
|
200 | this.reset()
|
201 | await this.update()
|
202 |
|
203 | this.started = getTime()
|
204 | const result = await runCommand(this.loadedVersion || this.version, command)
|
205 | this.finished = getTime()
|
206 |
|
207 | this.error = result.error
|
208 | this.stdout = (result.stdout || '').toString()
|
209 | this.stderr = (result.stderr || '').toString()
|
210 |
|
211 | this.success = Boolean(result.error) === false
|
212 |
|
213 | await this.update(this.success ? 'passed' : 'failed')
|
214 | return this
|
215 | }
|
216 |
|
217 | |
218 |
|
219 |
|
220 |
|
221 | get row(): Row {
|
222 | const indicator =
|
223 | this.success === null
|
224 | ? ansi.dim(figures.circle)
|
225 | : this.success
|
226 | ? ansi.green(figures.tick)
|
227 | : ansi.red(figures.cross)
|
228 |
|
229 | const result =
|
230 | this.success === null
|
231 | ? ansi.dim(this.status)
|
232 | : this.success
|
233 | ? ansi.green(this.status)
|
234 | : ansi.red(this.status)
|
235 |
|
236 |
|
237 | const ms = this.started ? (this.finished || getTime()) - this.started : 0
|
238 | const duration = this.started
|
239 | ? ansi.dim(ms > 1000 ? `${Math.round(ms / 1000)}s` : `${ms}ms`)
|
240 | : ''
|
241 |
|
242 | const aliases = this.aliases.length
|
243 | ? ansi.dim(` [${this.aliases.join('|')}]`)
|
244 | : ''
|
245 |
|
246 | const row: Row = [
|
247 | ' ' + indicator,
|
248 | this.version + aliases,
|
249 | result,
|
250 | duration,
|
251 | ]
|
252 | return row
|
253 | }
|
254 |
|
255 | |
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | get message() {
|
262 |
|
263 | if (this.messageCache && this.messageCache[0] === this.status) {
|
264 | return this.messageCache[1]
|
265 | }
|
266 |
|
267 |
|
268 | const parts: Array<string> = []
|
269 |
|
270 |
|
271 | const heading = `Node version ${ansi.underline(this.version)} ${
|
272 | this.status
|
273 | }`
|
274 | if (this.status === 'missing') {
|
275 | parts.push(ansi.bold(ansi.red(heading)))
|
276 | } else if (this.success === true) {
|
277 | parts.push(ansi.bold(ansi.green(heading)))
|
278 | } else if (this.success === false) {
|
279 | parts.push(ansi.bold(ansi.red(heading)))
|
280 | } else {
|
281 |
|
282 | parts.push(ansi.bold(ansi.dim(heading)))
|
283 | }
|
284 |
|
285 |
|
286 | if (this.error) {
|
287 | parts.push(ansi.red(this.error.message.split('\n')[0]))
|
288 | }
|
289 |
|
290 |
|
291 | if (this.status === 'missing') {
|
292 | parts.push(ansi.red(`You need to run: nvm install ${this.version}`))
|
293 | } else {
|
294 | const stdout = trim(this.stdout || '')
|
295 | const stderr = trim(this.stderr || '')
|
296 | if (!stdout && !stderr) {
|
297 | parts.push(ansi.dim('no output'))
|
298 | } else {
|
299 | if (stdout) {
|
300 | parts.push(stdout)
|
301 | }
|
302 | if (stderr) {
|
303 | parts.push(ansi.red(stderr))
|
304 | }
|
305 | }
|
306 | }
|
307 |
|
308 |
|
309 | const message = parts.join('\n')
|
310 |
|
311 |
|
312 | this.messageCache = [this.status, message]
|
313 | return message
|
314 | }
|
315 | }
|
316 | export default Version
|