qunitx-cli 0.1.1 → 0.5.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/.claude/settings.local.json +7 -0
- package/.env +1 -0
- package/Makefile +35 -0
- package/README.md +120 -49
- package/cli.js +1 -1
- package/cliff.toml +23 -0
- package/demo/demo.gif +0 -0
- package/demo/demo.tape +59 -0
- package/demo/example-test.js +53 -0
- package/demo/failing-test.js +22 -0
- package/flake.lock +4 -4
- package/flake.nix +33 -4
- package/lib/boilerplates/default-project-config-values.js +2 -2
- package/lib/boilerplates/test.js +5 -4
- package/lib/commands/generate.js +6 -8
- package/lib/commands/help.js +8 -8
- package/lib/commands/init.js +35 -25
- package/lib/commands/run/tests-in-browser.js +97 -67
- package/lib/commands/run.js +165 -55
- package/lib/servers/http.js +53 -42
- package/lib/setup/bind-server-to-port.js +3 -12
- package/lib/setup/browser.js +26 -18
- package/lib/setup/config.js +8 -10
- package/lib/setup/file-watcher.js +23 -6
- package/lib/setup/fs-tree.js +29 -27
- package/lib/setup/keyboard-events.js +7 -4
- package/lib/setup/test-file-paths.js +25 -23
- package/lib/setup/web-server.js +87 -61
- package/lib/setup/write-output-static-files.js +4 -1
- package/lib/tap/display-final-result.js +2 -2
- package/lib/tap/display-test-result.js +32 -14
- package/lib/utils/find-chrome.js +16 -0
- package/lib/utils/find-internal-assets-from-html.js +7 -5
- package/lib/utils/find-project-root.js +1 -2
- package/lib/utils/indent-string.js +6 -6
- package/lib/utils/listen-to-keyboard-key.js +6 -2
- package/lib/utils/parse-cli-flags.js +34 -31
- package/lib/utils/resolve-port-number-for.js +3 -3
- package/lib/utils/run-user-module.js +5 -3
- package/lib/utils/search-in-parent-directories.js +4 -1
- package/lib/utils/time-counter.js +2 -2
- package/package.json +21 -36
- package/vendor/qunit.css +7 -7
- package/vendor/qunit.js +3772 -3324
package/lib/setup/web-server.js
CHANGED
|
@@ -7,9 +7,15 @@ import HTTPServer, { MIME_TYPES } from '../servers/http.js';
|
|
|
7
7
|
|
|
8
8
|
const fsPromise = fs.promises;
|
|
9
9
|
|
|
10
|
-
export default async function setupWebServer(
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
export default async function setupWebServer(
|
|
11
|
+
config = {
|
|
12
|
+
port: 1234,
|
|
13
|
+
debug: false,
|
|
14
|
+
watch: false,
|
|
15
|
+
timeout: 10000,
|
|
16
|
+
},
|
|
17
|
+
cachedContent,
|
|
18
|
+
) {
|
|
13
19
|
let STATIC_FILES_PATH = path.join(config.projectRoot, config.output);
|
|
14
20
|
let server = new HTTPServer();
|
|
15
21
|
|
|
@@ -17,14 +23,21 @@ export default async function setupWebServer(config = {
|
|
|
17
23
|
socket.on('message', function message(data) {
|
|
18
24
|
const { event, details, abort } = JSON.parse(data);
|
|
19
25
|
|
|
20
|
-
if (event ===
|
|
21
|
-
console.log('TAP version 13');
|
|
22
|
-
} else if (
|
|
26
|
+
if (event === 'connection') {
|
|
27
|
+
if (!config._groupMode) console.log('TAP version 13');
|
|
28
|
+
} else if (event === 'testEnd' && !abort) {
|
|
23
29
|
if (details.status === 'failed') {
|
|
24
30
|
config.lastFailedTestFiles = config.lastRanTestFiles;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
TAPDisplayTestResult(config.COUNTER, details)
|
|
33
|
+
TAPDisplayTestResult(config.COUNTER, details);
|
|
34
|
+
} else if (event === 'done') {
|
|
35
|
+
// Signal test completion. TCP ordering guarantees all testEnd messages
|
|
36
|
+
// preceding this on the same connection are already processed by Node.js.
|
|
37
|
+
if (typeof config._testRunDone === 'function') {
|
|
38
|
+
config._testRunDone();
|
|
39
|
+
config._testRunDone = null;
|
|
40
|
+
}
|
|
28
41
|
}
|
|
29
42
|
});
|
|
30
43
|
});
|
|
@@ -32,29 +45,43 @@ export default async function setupWebServer(config = {
|
|
|
32
45
|
server.get('/', async (req, res) => {
|
|
33
46
|
let TEST_RUNTIME_TO_INJECT = testRuntimeToInject(config.port, config);
|
|
34
47
|
let htmlContent = escapeAndInjectTestsToHTML(
|
|
35
|
-
replaceAssetPaths(
|
|
48
|
+
replaceAssetPaths(
|
|
49
|
+
cachedContent.mainHTML.html,
|
|
50
|
+
cachedContent.mainHTML.filePath,
|
|
51
|
+
config.projectRoot,
|
|
52
|
+
),
|
|
36
53
|
TEST_RUNTIME_TO_INJECT,
|
|
37
|
-
cachedContent.allTestCode
|
|
54
|
+
cachedContent.allTestCode,
|
|
38
55
|
);
|
|
39
56
|
|
|
40
57
|
res.write(htmlContent);
|
|
41
58
|
res.end();
|
|
42
59
|
|
|
43
|
-
return await fsPromise.writeFile(
|
|
60
|
+
return await fsPromise.writeFile(
|
|
61
|
+
`${config.projectRoot}/${config.output}/index.html`,
|
|
62
|
+
htmlContent,
|
|
63
|
+
);
|
|
44
64
|
});
|
|
45
65
|
|
|
46
66
|
server.get('/qunitx.html', async (req, res) => {
|
|
47
67
|
let TEST_RUNTIME_TO_INJECT = testRuntimeToInject(config.port, config);
|
|
48
68
|
let htmlContent = escapeAndInjectTestsToHTML(
|
|
49
|
-
replaceAssetPaths(
|
|
69
|
+
replaceAssetPaths(
|
|
70
|
+
cachedContent.mainHTML.html,
|
|
71
|
+
cachedContent.mainHTML.filePath,
|
|
72
|
+
config.projectRoot,
|
|
73
|
+
),
|
|
50
74
|
TEST_RUNTIME_TO_INJECT,
|
|
51
|
-
cachedContent.filteredTestCode
|
|
75
|
+
cachedContent.filteredTestCode,
|
|
52
76
|
);
|
|
53
77
|
|
|
54
78
|
res.write(htmlContent);
|
|
55
79
|
res.end();
|
|
56
80
|
|
|
57
|
-
return await fsPromise.writeFile(
|
|
81
|
+
return await fsPromise.writeFile(
|
|
82
|
+
`${config.projectRoot}/${config.output}/qunitx.html`,
|
|
83
|
+
htmlContent,
|
|
84
|
+
);
|
|
58
85
|
});
|
|
59
86
|
|
|
60
87
|
server.get('/*', async (req, res) => {
|
|
@@ -64,31 +91,35 @@ export default async function setupWebServer(config = {
|
|
|
64
91
|
let htmlContent = escapeAndInjectTestsToHTML(
|
|
65
92
|
possibleDynamicHTML,
|
|
66
93
|
TEST_RUNTIME_TO_INJECT,
|
|
67
|
-
cachedContent.allTestCode
|
|
94
|
+
cachedContent.allTestCode,
|
|
68
95
|
);
|
|
69
96
|
|
|
70
97
|
res.write(htmlContent);
|
|
71
98
|
res.end();
|
|
72
99
|
|
|
73
|
-
return await fsPromise.writeFile(
|
|
100
|
+
return await fsPromise.writeFile(
|
|
101
|
+
`${config.projectRoot}/${config.output}${req.path}`,
|
|
102
|
+
htmlContent,
|
|
103
|
+
);
|
|
74
104
|
}
|
|
75
105
|
|
|
76
106
|
let url = req.url;
|
|
77
107
|
let requestStartedAt = new Date();
|
|
78
|
-
let filePath = (
|
|
79
|
-
|
|
108
|
+
let filePath = (
|
|
109
|
+
url.endsWith('/') ? [STATIC_FILES_PATH, url, 'index.html'] : [STATIC_FILES_PATH, url]
|
|
110
|
+
).join('');
|
|
111
|
+
let statusCode = (await pathExists(filePath)) ? 200 : 404;
|
|
80
112
|
|
|
81
113
|
res.writeHead(statusCode, {
|
|
82
|
-
|
|
114
|
+
'Content-Type': req.headers.accept?.includes('text/html')
|
|
83
115
|
? MIME_TYPES.html
|
|
84
|
-
: MIME_TYPES[path.extname(filePath).substring(1).toLowerCase()] || MIME_TYPES.html
|
|
116
|
+
: MIME_TYPES[path.extname(filePath).substring(1).toLowerCase()] || MIME_TYPES.html,
|
|
85
117
|
});
|
|
86
118
|
|
|
87
119
|
if (statusCode === 404) {
|
|
88
120
|
res.end();
|
|
89
121
|
} else {
|
|
90
|
-
fs.createReadStream(filePath)
|
|
91
|
-
.pipe(res);
|
|
122
|
+
fs.createReadStream(filePath).pipe(res);
|
|
92
123
|
}
|
|
93
124
|
|
|
94
125
|
console.log(`# [HTTPServer] GET ${url} ${statusCode} - ${new Date() - requestStartedAt}ms`);
|
|
@@ -99,7 +130,7 @@ export default async function setupWebServer(config = {
|
|
|
99
130
|
|
|
100
131
|
function replaceAssetPaths(html, htmlPath, projectRoot) {
|
|
101
132
|
let assetPaths = findInternalAssetsFromHTML(html);
|
|
102
|
-
let htmlDirectory = htmlPath.split('/').slice(0, -1).join('/')
|
|
133
|
+
let htmlDirectory = htmlPath.split('/').slice(0, -1).join('/');
|
|
103
134
|
|
|
104
135
|
return assetPaths.reduce((result, assetPath) => {
|
|
105
136
|
let normalizedFullAbsolutePath = path.normalize(`${htmlDirectory}/${assetPath}`);
|
|
@@ -116,29 +147,46 @@ function testRuntimeToInject(port, config) {
|
|
|
116
147
|
}, 1000);
|
|
117
148
|
|
|
118
149
|
(function() {
|
|
119
|
-
|
|
120
|
-
|
|
150
|
+
let wsRetryCount = 0;
|
|
151
|
+
const WS_MAX_RETRIES = 50; // 500ms total before giving up
|
|
152
|
+
|
|
153
|
+
function setupWebSocket() {
|
|
154
|
+
try {
|
|
155
|
+
window.socket = new WebSocket('ws://localhost:${port}');
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.log(error);
|
|
158
|
+
retryOrFail();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
window.socket.addEventListener('open', function() {
|
|
163
|
+
setupQUnit();
|
|
164
|
+
});
|
|
165
|
+
window.socket.addEventListener('error', function() {
|
|
166
|
+
retryOrFail();
|
|
167
|
+
});
|
|
121
168
|
window.socket.addEventListener('message', function(messageEvent) {
|
|
122
169
|
if (!window.IS_PUPPETEER && messageEvent.data === 'refresh') {
|
|
123
170
|
window.location.reload(true);
|
|
124
171
|
} else if (window.IS_PUPPETEER && messageEvent.data === 'abort') {
|
|
125
172
|
window.abortQUnit = true;
|
|
126
173
|
window.QUnit.config.queue.length = 0;
|
|
127
|
-
window.socket.send(JSON.stringify({ event: 'abort' }))
|
|
174
|
+
window.socket.send(JSON.stringify({ event: 'abort' }));
|
|
128
175
|
}
|
|
129
176
|
});
|
|
130
177
|
}
|
|
131
178
|
|
|
132
|
-
function
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
179
|
+
function retryOrFail() {
|
|
180
|
+
wsRetryCount++;
|
|
181
|
+
if (wsRetryCount > WS_MAX_RETRIES) {
|
|
182
|
+
console.log('WebSocket connection failed after ' + WS_MAX_RETRIES + ' retries');
|
|
183
|
+
window.testTimeout = ${config.timeout};
|
|
184
|
+
return;
|
|
138
185
|
}
|
|
186
|
+
window.setTimeout(setupWebSocket, 10);
|
|
139
187
|
}
|
|
140
188
|
|
|
141
|
-
|
|
189
|
+
setupWebSocket();
|
|
142
190
|
})();
|
|
143
191
|
|
|
144
192
|
{{allTestCode}}
|
|
@@ -163,13 +211,8 @@ function testRuntimeToInject(port, config) {
|
|
|
163
211
|
function setupQUnit() {
|
|
164
212
|
window.QUNIT_RESULT = { totalTests: 0, finishedTests: 0, currentTest: '' };
|
|
165
213
|
|
|
166
|
-
if (!window.
|
|
167
|
-
|
|
168
|
-
} else if (!window.QUnit || !window.QUnit.moduleStart || window.QUnit.config.queue === 0) {
|
|
169
|
-
if (socket.readyState == 0) {
|
|
170
|
-
return window.setTimeout(() => setupQUnit(), 10);
|
|
171
|
-
}
|
|
172
|
-
|
|
214
|
+
if (!window.QUnit) {
|
|
215
|
+
console.log('QUnit not found after WebSocket connected');
|
|
173
216
|
window.testTimeout = ${config.timeout};
|
|
174
217
|
return;
|
|
175
218
|
}
|
|
@@ -211,31 +254,14 @@ function testRuntimeToInject(port, config) {
|
|
|
211
254
|
}, 75);
|
|
212
255
|
});
|
|
213
256
|
|
|
214
|
-
|
|
215
|
-
return window.setTimeout(() => window.QUnit.start(), 25);
|
|
216
|
-
} else {
|
|
217
|
-
let connectionTrialCount = 0;
|
|
218
|
-
let connectionInterval = window.setInterval(() => {
|
|
219
|
-
if ([1, 3].includes(window.socket.readyState) || connectionTrialCount > 25) {
|
|
220
|
-
window.clearInterval(connectionInterval);
|
|
221
|
-
|
|
222
|
-
return window.setTimeout(() => window.QUnit.start(), 25);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
connectionTrialCount = connectionTrialCount + 1;
|
|
226
|
-
}, 10);
|
|
227
|
-
}
|
|
257
|
+
window.setTimeout(() => window.QUnit.start(), 25);
|
|
228
258
|
}
|
|
229
|
-
|
|
230
|
-
setupQUnit();
|
|
231
259
|
</script>`;
|
|
232
260
|
}
|
|
233
261
|
|
|
234
262
|
function escapeAndInjectTestsToHTML(html, testRuntimeCode, testContentCode) {
|
|
235
|
-
return html
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
.replace('</script>', '<\/script>') // NOTE: remove this when simple-html-tokenizer PR gets merged
|
|
240
|
-
);
|
|
263
|
+
return html.replace(
|
|
264
|
+
'{{content}}',
|
|
265
|
+
testRuntimeCode.replace('{{allTestCode}}', testContentCode).replace('</script>', '<\/script>'), // NOTE: remove this when simple-html-tokenizer PR gets merged
|
|
266
|
+
);
|
|
241
267
|
}
|
|
@@ -5,7 +5,10 @@ export default async function writeOutputStaticFiles({ projectRoot, output }, ca
|
|
|
5
5
|
let htmlRelativePath = staticHTMLKey.replace(`${projectRoot}/`, '');
|
|
6
6
|
|
|
7
7
|
await ensureFolderExists(`${projectRoot}/${output}/${htmlRelativePath}`);
|
|
8
|
-
await fs.writeFile(
|
|
8
|
+
await fs.writeFile(
|
|
9
|
+
`${projectRoot}/${output}/${htmlRelativePath}`,
|
|
10
|
+
cachedContent.staticHTMLs[staticHTMLKey],
|
|
11
|
+
);
|
|
9
12
|
});
|
|
10
13
|
let assetPromises = Array.from(cachedContent.assets).map(async (assetAbsolutePath) => {
|
|
11
14
|
let assetRelativePath = assetAbsolutePath.replace(`${projectRoot}/`, '');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export default function({ testCount, passCount, skipCount, failCount }, timeTaken) {
|
|
1
|
+
export default function ({ testCount, passCount, skipCount, failCount }, timeTaken) {
|
|
2
2
|
console.log('');
|
|
3
3
|
console.log(`1..${testCount}`);
|
|
4
4
|
console.log(`# tests ${testCount}`);
|
|
@@ -12,4 +12,4 @@ export default function({ testCount, passCount, skipCount, failCount }, timeTake
|
|
|
12
12
|
console.log(`# duration ${timeTaken}`);
|
|
13
13
|
console.log('');
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
// console.log(details.timeTaken); // runtime
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import yaml from 'js-yaml'
|
|
1
|
+
import yaml from 'js-yaml';
|
|
2
2
|
import indentString from '../utils/indent-string.js';
|
|
3
3
|
|
|
4
4
|
// tape TAP output: ['operator', 'stack', 'at', 'expected', 'actual']
|
|
5
5
|
// ava TAP output: ['message', 'name', 'at', 'assertion', 'values'] // Assertion #5, message
|
|
6
|
-
export default function(COUNTER, details) {
|
|
6
|
+
export default function (COUNTER, details) {
|
|
7
|
+
// NOTE: https://github.com/qunitjs/qunit/blob/master/src/html-reporter/diff.js
|
|
7
8
|
COUNTER.testCount++;
|
|
8
9
|
|
|
9
10
|
if (details.status === 'skipped') {
|
|
@@ -13,21 +14,34 @@ export default function(COUNTER, details) { // NOTE: https://github.com/qunitjs/
|
|
|
13
14
|
console.log(`not ok ${COUNTER.testCount}`, details.fullName.join(' | '), '# skip');
|
|
14
15
|
} else if (details.status === 'failed') {
|
|
15
16
|
COUNTER.failCount++;
|
|
16
|
-
console.log(
|
|
17
|
+
console.log(
|
|
18
|
+
`not ok ${COUNTER.testCount}`,
|
|
19
|
+
details.fullName.join(' | '),
|
|
20
|
+
`# (${details.runtime.toFixed(0)} ms)`,
|
|
21
|
+
);
|
|
17
22
|
details.assertions.reduce((errorCount, assertion, index) => {
|
|
18
23
|
if (!assertion.passed && assertion.todo === false) {
|
|
19
24
|
COUNTER.errorCount++;
|
|
20
25
|
let stack = assertion.stack?.match(/\(.+\)/g);
|
|
21
26
|
|
|
22
27
|
console.log(' ---');
|
|
23
|
-
console.log(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
console.log(
|
|
29
|
+
indentString(
|
|
30
|
+
yaml.dump({
|
|
31
|
+
name: `Assertion #${index + 1}`, // TODO: check what happens on runtime errors
|
|
32
|
+
actual: assertion.actual
|
|
33
|
+
? JSON.parse(JSON.stringify(assertion.actual, getCircularReplacer()))
|
|
34
|
+
: assertion.actual,
|
|
35
|
+
expected: assertion.expected
|
|
36
|
+
? JSON.parse(JSON.stringify(assertion.expected, getCircularReplacer()))
|
|
37
|
+
: assertion.expected,
|
|
38
|
+
message: assertion.message || null,
|
|
39
|
+
stack: assertion.stack || null,
|
|
40
|
+
at: stack ? stack[0].replace('(file://', '').replace(')', '') : null,
|
|
41
|
+
}),
|
|
42
|
+
4,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
31
45
|
console.log(' ...');
|
|
32
46
|
}
|
|
33
47
|
|
|
@@ -35,21 +49,25 @@ export default function(COUNTER, details) { // NOTE: https://github.com/qunitjs/
|
|
|
35
49
|
}, 0);
|
|
36
50
|
} else if (details.status === 'passed') {
|
|
37
51
|
COUNTER.passCount++;
|
|
38
|
-
console.log(
|
|
52
|
+
console.log(
|
|
53
|
+
`ok ${COUNTER.testCount}`,
|
|
54
|
+
details.fullName.join(' | '),
|
|
55
|
+
`# (${details.runtime.toFixed(0)} ms)`,
|
|
56
|
+
);
|
|
39
57
|
}
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
function getCircularReplacer() {
|
|
43
61
|
const ancestors = [];
|
|
44
62
|
return function (key, value) {
|
|
45
|
-
if (typeof value !==
|
|
63
|
+
if (typeof value !== 'object' || value === null) {
|
|
46
64
|
return value;
|
|
47
65
|
}
|
|
48
66
|
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
|
|
49
67
|
ancestors.pop();
|
|
50
68
|
}
|
|
51
69
|
if (ancestors.includes(value)) {
|
|
52
|
-
return
|
|
70
|
+
return '[Circular]';
|
|
53
71
|
}
|
|
54
72
|
ancestors.push(value);
|
|
55
73
|
return value;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const CANDIDATES = ['google-chrome-stable', 'google-chrome', 'chromium', 'chromium-browser'];
|
|
4
|
+
|
|
5
|
+
export default async function findChrome() {
|
|
6
|
+
if (process.env.CHROME_BIN) return process.env.CHROME_BIN;
|
|
7
|
+
|
|
8
|
+
return Promise.any(
|
|
9
|
+
CANDIDATES.map(
|
|
10
|
+
(name) =>
|
|
11
|
+
new Promise((resolve, reject) =>
|
|
12
|
+
exec(`which ${name}`, (err, stdout) => (err ? reject() : resolve(stdout.trim()))),
|
|
13
|
+
),
|
|
14
|
+
),
|
|
15
|
+
).catch(() => null);
|
|
16
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { load } from 'cheerio';
|
|
2
2
|
|
|
3
3
|
const ABSOLUTE_URL_REGEX = new RegExp('^(?:[a-z]+:)?//', 'i');
|
|
4
4
|
|
|
5
5
|
export default function findInternalAssetsFromHTML(htmlContent) {
|
|
6
|
-
const $ =
|
|
7
|
-
const internalJSFiles = $('script[src]')
|
|
6
|
+
const $ = load(htmlContent);
|
|
7
|
+
const internalJSFiles = $('script[src]')
|
|
8
|
+
.toArray()
|
|
8
9
|
.map((scriptNode) => $(scriptNode).attr('src'))
|
|
9
10
|
.filter((uri) => !ABSOLUTE_URL_REGEX.test(uri));
|
|
10
|
-
const internalCSSFiles = $('link[href]')
|
|
11
|
+
const internalCSSFiles = $('link[href]')
|
|
12
|
+
.toArray()
|
|
11
13
|
.map((scriptNode) => $(scriptNode).attr('href'))
|
|
12
14
|
.filter((uri) => !ABSOLUTE_URL_REGEX.test(uri));
|
|
13
15
|
|
|
14
16
|
return internalCSSFiles.concat(internalJSFiles);
|
|
15
|
-
|
|
17
|
+
// TODO: maybe needs normalization ? .map((fileReferencePath) => fileReferencePath.replace('/assets', `${projectRoot}/tmp/assets`));
|
|
16
18
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import searchInParentDirectories from './search-in-parent-directories.js';
|
|
3
3
|
|
|
4
|
-
export default async function() {
|
|
4
|
+
export default async function () {
|
|
5
5
|
try {
|
|
6
6
|
let absolutePath = await searchInParentDirectories('.', 'package.json');
|
|
7
7
|
if (!absolutePath.includes('package.json')) {
|
|
@@ -14,4 +14,3 @@ export default async function() {
|
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export default function indentString(string, count = 1, options = {}) {
|
|
2
|
-
|
|
2
|
+
const { indent = ' ', includeEmptyLines = false } = options;
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
if (count <= 0) {
|
|
5
|
+
return string;
|
|
6
|
+
}
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const regex = includeEmptyLines ? /^/gm : /^(?!\s*$)/gm;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
return string.replace(regex, indent.repeat(count));
|
|
11
11
|
}
|
|
@@ -5,12 +5,16 @@ let targetInputs = {};
|
|
|
5
5
|
let inputs = [];
|
|
6
6
|
let listenerAdded = false;
|
|
7
7
|
|
|
8
|
-
export default function listenToKeyboardKey(
|
|
8
|
+
export default function listenToKeyboardKey(
|
|
9
|
+
inputString,
|
|
10
|
+
closure,
|
|
11
|
+
options = { caseSensitive: false },
|
|
12
|
+
) {
|
|
9
13
|
stdin.setRawMode(true);
|
|
10
14
|
stdin.resume();
|
|
11
15
|
stdin.setEncoding('utf8');
|
|
12
16
|
if (!listenerAdded) {
|
|
13
|
-
stdin.on('data', function(key){
|
|
17
|
+
stdin.on('data', function (key) {
|
|
14
18
|
if (key === '\u0003') {
|
|
15
19
|
process.exit(); // so node process doesnt trap Control-C
|
|
16
20
|
}
|
|
@@ -1,44 +1,47 @@
|
|
|
1
1
|
// { inputs: [], debug: true, watch: true, failFast: true, htmlPaths: [], output }
|
|
2
|
-
export default
|
|
3
|
-
const providedFlags = process.argv.slice(2).reduce(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
result.htmlPaths
|
|
17
|
-
|
|
18
|
-
|
|
2
|
+
export default function (projectRoot) {
|
|
3
|
+
const providedFlags = process.argv.slice(2).reduce(
|
|
4
|
+
(result, arg) => {
|
|
5
|
+
if (arg.startsWith('--debug')) {
|
|
6
|
+
return Object.assign(result, { debug: parseBoolean(arg.split('=')[1]) });
|
|
7
|
+
} else if (arg.startsWith('--watch')) {
|
|
8
|
+
return Object.assign(result, { watch: parseBoolean(arg.split('=')[1]) });
|
|
9
|
+
} else if (arg.startsWith('--failfast') || arg.startsWith('--failFast')) {
|
|
10
|
+
return Object.assign(result, { failFast: parseBoolean(arg.split('=')[1]) });
|
|
11
|
+
} else if (arg.startsWith('--timeout')) {
|
|
12
|
+
return Object.assign(result, { timeout: arg.split('=')[1] || 10000 });
|
|
13
|
+
} else if (arg.startsWith('--output')) {
|
|
14
|
+
return Object.assign(result, { output: arg.split('=')[1] });
|
|
15
|
+
} else if (arg.endsWith('.html')) {
|
|
16
|
+
if (result.htmlPaths) {
|
|
17
|
+
result.htmlPaths.push(arg);
|
|
18
|
+
} else {
|
|
19
|
+
result.htmlPaths = [arg];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result;
|
|
23
|
+
} else if (arg.startsWith('--port')) {
|
|
24
|
+
return Object.assign(result, { port: Number(arg.split('=')[1]) });
|
|
25
|
+
} else if (arg.startsWith('--before')) {
|
|
26
|
+
return Object.assign(result, { before: parseModule(arg.split('=')[1]) });
|
|
27
|
+
} else if (arg.startsWith('--after')) {
|
|
28
|
+
return Object.assign(result, { after: parseModule(arg.split('=')[1]) });
|
|
19
29
|
}
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return Object.assign(result, { port: Number(arg.split('=')[1]) });
|
|
24
|
-
} else if (arg.startsWith('--before')) {
|
|
25
|
-
return Object.assign(result, { before: parseModule(arg.split('=')[1]) });
|
|
26
|
-
} else if (arg.startsWith('--after')) {
|
|
27
|
-
return Object.assign(result, { after: parseModule(arg.split('=')[1]) });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// maybe set watch depth via micromatch(so incl metadata)
|
|
31
|
-
result.inputs.add(arg.startsWith(projectRoot) ? arg : `${process.cwd()}/${arg}`);
|
|
31
|
+
// maybe set watch depth via micromatch(so incl metadata)
|
|
32
|
+
result.inputs.add(arg.startsWith(projectRoot) ? arg : `${process.cwd()}/${arg}`);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
{ inputs: new Set([]) },
|
|
37
|
+
);
|
|
35
38
|
|
|
36
39
|
providedFlags.inputs = Array.from(providedFlags.inputs);
|
|
37
40
|
|
|
38
41
|
return providedFlags;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
function parseBoolean(result, defaultValue=true) {
|
|
44
|
+
function parseBoolean(result, defaultValue = true) {
|
|
42
45
|
if (result === 'true') {
|
|
43
46
|
return true;
|
|
44
47
|
} else if (result === 'false') {
|
|
@@ -3,7 +3,7 @@ export default async function resolvePortNumberFor(portNumber) {
|
|
|
3
3
|
return portNumber;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
return
|
|
6
|
+
return await resolvePortNumberFor(portNumber + 1);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function portIsAvailable(portNumber) {
|
|
@@ -11,13 +11,13 @@ function portIsAvailable(portNumber) {
|
|
|
11
11
|
const net = await import('net');
|
|
12
12
|
const server = net.createServer();
|
|
13
13
|
|
|
14
|
-
server.once('error', function(err) {
|
|
14
|
+
server.once('error', function (err) {
|
|
15
15
|
if (err.code === 'EADDRINUSE') {
|
|
16
16
|
resolve(false);
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
server.once('listening', function() {
|
|
20
|
+
server.once('listening', function () {
|
|
21
21
|
server.close();
|
|
22
22
|
resolve(true);
|
|
23
23
|
});
|
|
@@ -4,9 +4,11 @@ export default async function runUserModule(modulePath, params, scriptPosition)
|
|
|
4
4
|
try {
|
|
5
5
|
let func = await import(modulePath);
|
|
6
6
|
if (func) {
|
|
7
|
-
func.default
|
|
8
|
-
await func.default(params)
|
|
9
|
-
typeof func === 'function'
|
|
7
|
+
func.default
|
|
8
|
+
? await func.default(params)
|
|
9
|
+
: typeof func === 'function'
|
|
10
|
+
? await func(params)
|
|
11
|
+
: null;
|
|
10
12
|
}
|
|
11
13
|
} catch (error) {
|
|
12
14
|
console.log('#', kleur.red(`QUnitX ${scriptPosition} script failed:`));
|
|
@@ -9,7 +9,10 @@ async function searchInParentDirectories(directory, targetEntry) {
|
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
return await searchInParentDirectories(
|
|
12
|
+
return await searchInParentDirectories(
|
|
13
|
+
directory.slice(0, directory.lastIndexOf('/')),
|
|
14
|
+
targetEntry,
|
|
15
|
+
);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export default searchInParentDirectories;
|