1 | #!/usr/bin/env node
|
2 |
|
3 |
|
4 |
|
5 | import { Version, Versions } from './index.js'
|
6 | import { parseExitCode } from './util.js'
|
7 |
|
8 |
|
9 | import versionRange from 'version-range'
|
10 | import minimist from 'minimist'
|
11 | import textTable from 'text-table'
|
12 | import stringWidth from 'string-width'
|
13 | import Logger from 'logger-clearable'
|
14 | import Spinner from 'spinner-title'
|
15 | import { isReadable } from '@bevry/fs-readable'
|
16 | import wait from '@bevry/wait'
|
17 | import { readJSON } from '@bevry/json'
|
18 | import {
|
19 | preloadNodeVersions,
|
20 | filterSignificantNodeVersions,
|
21 | } from '@bevry/nodejs-versions'
|
22 | import filedirname from 'filedirname'
|
23 | const [file, dir] = filedirname()
|
24 |
|
25 |
|
26 | import process from 'node:process'
|
27 | import { join } from 'node:path'
|
28 |
|
29 |
|
30 | const spinner: any = null
|
31 | async function parse(): Promise<null | any> {
|
32 |
|
33 | const cli: any = minimist(process.argv.slice(2), {
|
34 | '--': true,
|
35 | alias: {
|
36 | h: 'help',
|
37 | j: 'json',
|
38 | n: 'node',
|
39 | s: 'serial',
|
40 | },
|
41 | string: ['node', 'spinner'],
|
42 |
|
43 | })
|
44 | if (cli.help) {
|
45 |
|
46 | process.stdout.write(
|
47 | [
|
48 | 'Usage:',
|
49 | '',
|
50 | ' -n/--node [version]: Add a Node.js version to test',
|
51 | ' -s/--serial: Run tests serially, one after the other',
|
52 | ' -j/--json: Output the test results as JSON',
|
53 | ' --spinner [spinner] Which spinner to use in the title bar',
|
54 | ' --verbose: Report details about all statuses, not just failures',
|
55 | ' --version: Output the version of Testen',
|
56 | ' --help: Output this help',
|
57 | ' -- [command]: The test command you expect',
|
58 | '',
|
59 | ].join('\n'),
|
60 | )
|
61 | return null
|
62 | }
|
63 | if (cli.version) {
|
64 |
|
65 | const testenPackage: any = await readJSON(join(dir, 'package.json'))
|
66 | process.stdout.write(testenPackage.version + '\n')
|
67 | return null
|
68 | }
|
69 |
|
70 |
|
71 | const cliTestenConfig: any = {}
|
72 | if (cli.node)
|
73 | cliTestenConfig.node =
|
74 | Array.isArray(cli.node) && cli.node.length === 1 ? cli.node[0] : cli.node
|
75 | if (cli['--'] && cli['--'].join(''))
|
76 | cliTestenConfig.command = cli['--'].join(' ')
|
77 | if (cli.spinner != null) cliTestenConfig.spinner = cli.spinner
|
78 | if (cli.serial != null) cliTestenConfig.serial = cli.serial
|
79 | if (cli.json != null) cliTestenConfig.json = cli.json
|
80 | if (cli.verbose != null) cliTestenConfig.verbose = cli.verbose
|
81 | return cliTestenConfig
|
82 | }
|
83 | async function testen(customTestenConfig = {}) {
|
84 |
|
85 | const cwd = process.cwd()
|
86 | const userPackagePath = `${cwd}/package.json`
|
87 | const userPackage: any = (await isReadable(userPackagePath))
|
88 | ? await readJSON(userPackagePath)
|
89 | : {}
|
90 |
|
91 |
|
92 | const testenConfig = Object.assign(
|
93 | {
|
94 | node: (userPackage.engines && userPackage.engines.node) || '',
|
95 | command: 'npm test',
|
96 | spinner: 'monkey',
|
97 | serial: false,
|
98 | json: false,
|
99 | verbose: false,
|
100 | },
|
101 | userPackage.testen || {},
|
102 | customTestenConfig,
|
103 | )
|
104 |
|
105 |
|
106 | const nodeVersions = []
|
107 | if (Array.isArray(testenConfig.node) && testenConfig.node.length) {
|
108 |
|
109 | nodeVersions.push(...testenConfig.node)
|
110 | } else if (testenConfig.node && /^[\d\w.-]+$/.test(testenConfig.node)) {
|
111 |
|
112 | nodeVersions.push(testenConfig.node)
|
113 | } else if (testenConfig.node) {
|
114 |
|
115 | await preloadNodeVersions()
|
116 | nodeVersions.push(
|
117 | ...filterSignificantNodeVersions({
|
118 | maintainedOrLTS: true,
|
119 | released: true,
|
120 | }).filter((version: string) => versionRange(version, testenConfig.node)),
|
121 | )
|
122 | } else {
|
123 | nodeVersions.push('current', 'stable', 'system')
|
124 | }
|
125 | if (!nodeVersions || !nodeVersions.length) {
|
126 | throw new Error('No node versions specified')
|
127 | }
|
128 |
|
129 |
|
130 | const listeners = []
|
131 | let interval: any = null
|
132 | let spinner: any = null
|
133 | if (!testenConfig.json) {
|
134 |
|
135 | spinner = new Spinner({
|
136 | style: testenConfig.spinner,
|
137 | interval: 1000,
|
138 | } as any)
|
139 | spinner.start()
|
140 |
|
141 | const logger = new Logger()
|
142 | function table(result: any) {
|
143 | return textTable(result, { stringLength: stringWidth })
|
144 | }
|
145 | function refresh(versions: Versions) {
|
146 | const messages: any = []
|
147 | versions.array.forEach(function (V: Version) {
|
148 | if (V.success === false || testenConfig.verbose) {
|
149 | messages.push(V.message)
|
150 | }
|
151 | })
|
152 | if (messages.length) {
|
153 | return (
|
154 | '\n' + messages.join('\n\n') + '\n\n' + table(versions.table) + '\n\n'
|
155 | )
|
156 | } else {
|
157 | return '\n' + table(versions.table) + '\n\n'
|
158 | }
|
159 | }
|
160 |
|
161 | function refresher() {
|
162 |
|
163 | logger.queue(() => refresh(versions))
|
164 | }
|
165 | interval = setInterval(refresher, 1000)
|
166 | listeners.push(refresher)
|
167 | }
|
168 | const versions = new Versions(nodeVersions, listeners as any)
|
169 |
|
170 |
|
171 | await versions.load()
|
172 | await versions.install()
|
173 | await versions.test(testenConfig.command, testenConfig.serial)
|
174 |
|
175 |
|
176 | if (testenConfig.json) {
|
177 |
|
178 | process.stdout.write(JSON.stringify(versions.json, null, ' '))
|
179 | } else {
|
180 |
|
181 |
|
182 | if (interval) {
|
183 | clearInterval(interval)
|
184 | interval = null
|
185 | }
|
186 | spinner.stop()
|
187 | await wait(0)
|
188 | }
|
189 |
|
190 |
|
191 | return versions
|
192 | }
|
193 |
|
194 |
|
195 | Promise.resolve()
|
196 | .then(parse)
|
197 | .then(async function (cliTestenConfig: any = null) {
|
198 | if (cliTestenConfig) {
|
199 | const versions = await testen(cliTestenConfig)
|
200 | process.exitCode = versions.success ? 0 : 1
|
201 | }
|
202 | })
|
203 | .catch(function (err: any) {
|
204 | if (spinner) spinner.stop()
|
205 | process.stderr.write((err.stack || err.message || err).toString())
|
206 | process.exitCode = parseExitCode(err.code) || 2
|
207 | })
|