UNPKG

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