1 | #!/usr/bin/env node
|
2 |
|
3 |
|
4 | import { Versions } from './index.js';
|
5 | import { parseExitCode } from './util.js';
|
6 |
|
7 | import versionRange from 'version-range';
|
8 | import minimist from 'minimist';
|
9 | import textTable from 'text-table';
|
10 | import stringWidth from 'string-width';
|
11 | import Logger from 'logger-clearable';
|
12 | import Spinner from 'spinner-title';
|
13 | import { isReadable } from '@bevry/fs-readable';
|
14 | import wait from '@bevry/wait';
|
15 | import { readJSON } from '@bevry/json';
|
16 | import { preloadNodeVersions, filterSignificantNodeVersions, } from '@bevry/nodejs-versions';
|
17 | import filedirname from 'filedirname';
|
18 | const [file, dir] = filedirname();
|
19 |
|
20 | import process from 'node:process';
|
21 | import { join } from 'node:path';
|
22 |
|
23 | const spinner = null;
|
24 | async function parse() {
|
25 |
|
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 |
|
36 | });
|
37 | if (cli.help) {
|
38 |
|
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 |
|
56 | const testenPackage = await readJSON(join(dir, 'package.json'));
|
57 | process.stdout.write(testenPackage.version + '\n');
|
58 | return null;
|
59 | }
|
60 |
|
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 | }
|
77 | async function testen(customTestenConfig = {}) {
|
78 |
|
79 | const cwd = process.cwd();
|
80 | const userPackagePath = `${cwd}/package.json`;
|
81 | const userPackage = (await isReadable(userPackagePath))
|
82 | ? await readJSON(userPackagePath)
|
83 | : {};
|
84 |
|
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 |
|
94 | const nodeVersions = [];
|
95 | if (Array.isArray(testenConfig.node) && testenConfig.node.length) {
|
96 |
|
97 | nodeVersions.push(...testenConfig.node);
|
98 | }
|
99 | else if (testenConfig.node && /^[\d\w.-]+$/.test(testenConfig.node)) {
|
100 |
|
101 | nodeVersions.push(testenConfig.node);
|
102 | }
|
103 | else if (testenConfig.node) {
|
104 |
|
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 |
|
118 | const listeners = [];
|
119 | let interval = null;
|
120 | let spinner = null;
|
121 | if (!testenConfig.json) {
|
122 |
|
123 | spinner = new Spinner({
|
124 | style: testenConfig.spinner,
|
125 | interval: 1000,
|
126 | });
|
127 | spinner.start();
|
128 |
|
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 |
|
148 | function refresher() {
|
149 |
|
150 | logger.queue(() => refresh(versions));
|
151 | }
|
152 | interval = setInterval(refresher, 1000);
|
153 | listeners.push(refresher);
|
154 | }
|
155 | const versions = new Versions(nodeVersions, listeners);
|
156 |
|
157 | await versions.load();
|
158 | await versions.install();
|
159 | await versions.test(testenConfig.command, testenConfig.serial);
|
160 |
|
161 | if (testenConfig.json) {
|
162 |
|
163 | process.stdout.write(JSON.stringify(versions.json, null, ' '));
|
164 | }
|
165 | else {
|
166 |
|
167 |
|
168 | if (interval) {
|
169 | clearInterval(interval);
|
170 | interval = null;
|
171 | }
|
172 | spinner.stop();
|
173 | await wait(0);
|
174 | }
|
175 |
|
176 | return versions;
|
177 | }
|
178 |
|
179 | Promise.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 | });
|