qunitx-cli 0.19.2 → 0.19.3
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/dist/cli.js +420 -188
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -55,68 +55,67 @@ var init_kill_process_group = __esm({
|
|
|
55
55
|
|
|
56
56
|
// lib/utils/cleanup-browser-dir.ts
|
|
57
57
|
import fs from "node:fs/promises";
|
|
58
|
-
async function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
async function processReferencesDir(entry, dirPath, dirName) {
|
|
59
|
+
try {
|
|
60
|
+
const [cwd, cmdline] = await Promise.all([
|
|
61
|
+
fs.readlink(`/proc/${entry}/cwd`).catch(() => ""),
|
|
62
|
+
fs.readFile(`/proc/${entry}/cmdline`, "utf8").catch(() => "")
|
|
63
|
+
]);
|
|
64
|
+
if (cwd.startsWith(dirPath) || cmdline.includes(dirName)) return true;
|
|
65
|
+
const fds = await fs.readdir(`/proc/${entry}/fd`).catch(() => []);
|
|
66
|
+
const fdTargets = await Promise.all(
|
|
67
|
+
fds.map((fd) => fs.readlink(`/proc/${entry}/fd/${fd}`).catch(() => ""))
|
|
68
|
+
);
|
|
69
|
+
return fdTargets.some((target) => target.startsWith(dirPath));
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
63
72
|
}
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
}
|
|
74
|
+
async function killAllReferencingProcesses(dirPath, dirName) {
|
|
66
75
|
const procEntries = await fs.readdir("/proc").catch(() => []);
|
|
67
76
|
await Promise.all(
|
|
68
77
|
procEntries.map(async (entry) => {
|
|
69
78
|
if (!/^\d+$/.test(entry)) return;
|
|
70
|
-
|
|
79
|
+
if (!await processReferencesDir(entry, dirPath, dirName)) return;
|
|
71
80
|
try {
|
|
72
|
-
|
|
73
|
-
fs.readlink(`/proc/${entry}/cwd`).catch(() => ""),
|
|
74
|
-
fs.readFile(`/proc/${entry}/cmdline`, "utf8").catch(() => "")
|
|
75
|
-
]);
|
|
76
|
-
if (!cwd.startsWith(dirPath) && !cmdline.includes(dirName)) return;
|
|
77
|
-
try {
|
|
78
|
-
process.kill(pid, "SIGKILL");
|
|
79
|
-
killedPids.add(pid);
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
81
|
+
process.kill(parseInt(entry), "SIGKILL");
|
|
82
82
|
} catch {
|
|
83
83
|
}
|
|
84
84
|
})
|
|
85
85
|
);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
killedPids.delete(pid);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
86
|
+
}
|
|
87
|
+
async function cleanupBrowserDir(dirPath) {
|
|
88
|
+
if (process.platform !== "linux") {
|
|
89
|
+
await fs.rm(dirPath, { recursive: true, force: true }).catch(() => {
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
95
92
|
}
|
|
93
|
+
const dirName = dirPath.split("/").pop();
|
|
94
|
+
await killAllReferencingProcesses(dirPath, dirName);
|
|
96
95
|
const deadline = Date.now() + 5e3;
|
|
97
96
|
while (Date.now() < deadline) {
|
|
98
97
|
const removed = await fs.rm(dirPath, { recursive: true, force: true }).then(() => true).catch(() => false);
|
|
99
|
-
if (removed)
|
|
100
|
-
await
|
|
98
|
+
if (removed) return;
|
|
99
|
+
await killAllReferencingProcesses(dirPath, dirName);
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
101
101
|
}
|
|
102
|
-
if (await fs.access(dirPath).then(() => true).catch(() => false))
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
if (!await fs.access(dirPath).then(() => true).catch(() => false))
|
|
103
|
+
return;
|
|
104
|
+
const diagEntries = await fs.readdir("/proc").catch(() => []);
|
|
105
|
+
await Promise.all(
|
|
106
|
+
diagEntries.map(async (entry) => {
|
|
107
|
+
if (!/^\d+$/.test(entry)) return;
|
|
108
|
+
try {
|
|
109
|
+
if (!await processReferencesDir(entry, dirPath, dirName)) return;
|
|
110
|
+
const cmdline = await fs.readFile(`/proc/${entry}/cmdline`, "utf8").catch(() => "");
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
`# [qunitx] cleanup failed: pid ${entry} still references ${dirPath} (cmdline: ${cmdline.replace(/\0/g, " ").slice(0, 120)})
|
|
113
113
|
`
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
114
|
+
);
|
|
115
|
+
} catch {
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
);
|
|
120
119
|
}
|
|
121
120
|
var init_cleanup_browser_dir = __esm({
|
|
122
121
|
"lib/utils/cleanup-browser-dir.ts"() {
|
|
@@ -168,36 +167,11 @@ async function preLaunchChrome(chromePath, args, headless = true) {
|
|
|
168
167
|
});
|
|
169
168
|
async function shutdown() {
|
|
170
169
|
proc.ref();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
proc.once("close", resolve);
|
|
177
|
-
});
|
|
178
|
-
if (proc.exitCode === null) killProcessGroup(proc.pid);
|
|
179
|
-
await closed;
|
|
180
|
-
await rm(userDataDir, { recursive: true, force: true }).catch(async () => {
|
|
181
|
-
const pgid = proc.pid;
|
|
182
|
-
const warnTimer = setTimeout(
|
|
183
|
-
() => process.stderr.write(
|
|
184
|
-
`# [qunitx] warning: Chrome process group ${pgid} still alive 500ms after SIGKILL, waiting...
|
|
185
|
-
`
|
|
186
|
-
),
|
|
187
|
-
500
|
|
188
|
-
);
|
|
189
|
-
warnTimer.unref();
|
|
190
|
-
while (true) {
|
|
191
|
-
try {
|
|
192
|
-
process.kill(-pgid, 0);
|
|
193
|
-
} catch {
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
197
|
-
}
|
|
198
|
-
clearTimeout(warnTimer);
|
|
199
|
-
await cleanupBrowserDir(userDataDir);
|
|
200
|
-
});
|
|
170
|
+
if (proc.exitCode === null) {
|
|
171
|
+
killProcessGroup(proc.pid);
|
|
172
|
+
await new Promise((resolve) => proc.once("close", resolve));
|
|
173
|
+
}
|
|
174
|
+
await cleanupBrowserDir(userDataDir);
|
|
201
175
|
}
|
|
202
176
|
}
|
|
203
177
|
var CDP_URL_REGEX;
|
|
@@ -780,11 +754,13 @@ var init_http = __esm({
|
|
|
780
754
|
if (!this.routes[method]) {
|
|
781
755
|
this.routes[method] = {};
|
|
782
756
|
}
|
|
757
|
+
const paramNames = this.#extractParamNames(path7);
|
|
783
758
|
this.routes[method][path7] = {
|
|
784
759
|
path: path7,
|
|
785
760
|
handler,
|
|
786
|
-
paramNames
|
|
787
|
-
isWildcard: path7 === "/*"
|
|
761
|
+
paramNames,
|
|
762
|
+
isWildcard: path7 === "/*",
|
|
763
|
+
compiledRegex: paramNames.length > 0 ? new RegExp(`^${this.#buildRegexPattern(path7, paramNames)}$`) : null
|
|
788
764
|
};
|
|
789
765
|
}
|
|
790
766
|
#handleRequest(req, res) {
|
|
@@ -827,10 +803,8 @@ var init_http = __esm({
|
|
|
827
803
|
return false;
|
|
828
804
|
}
|
|
829
805
|
if (isWildcard || this.#matchPathSegments(path7, url)) {
|
|
830
|
-
if (route.
|
|
831
|
-
const
|
|
832
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
833
|
-
const regexMatches = regex.exec(url);
|
|
806
|
+
if (route.compiledRegex) {
|
|
807
|
+
const regexMatches = route.compiledRegex.exec(url);
|
|
834
808
|
if (regexMatches) {
|
|
835
809
|
route.paramValues = regexMatches.slice(1);
|
|
836
810
|
}
|
|
@@ -892,6 +866,20 @@ function setupWebServer(config, cachedContent) {
|
|
|
892
866
|
config.projectRoot
|
|
893
867
|
);
|
|
894
868
|
const runtimeScript = testRuntimeToInject(config);
|
|
869
|
+
const mainIndexHTML = escapeAndInjectTestsToHTML(
|
|
870
|
+
mainHTMLWithReplacedAssets,
|
|
871
|
+
runtimeScript,
|
|
872
|
+
"./tests.js"
|
|
873
|
+
);
|
|
874
|
+
const mainQunitxHTML = escapeAndInjectTestsToHTML(
|
|
875
|
+
mainHTMLWithReplacedAssets,
|
|
876
|
+
runtimeScript,
|
|
877
|
+
"./filtered-tests.js"
|
|
878
|
+
);
|
|
879
|
+
const saveHTML = (filePath, html) => fsPromise.writeFile(filePath, html).catch(
|
|
880
|
+
(err) => config.debug && process.stderr.write(`# [qunitx] writeFile ${filePath}: ${err.message}
|
|
881
|
+
`)
|
|
882
|
+
);
|
|
895
883
|
server.wss.on("connection", function connection(socket) {
|
|
896
884
|
socket.on("message", function message(data) {
|
|
897
885
|
const { event, details, qunitResult, abort } = JSON.parse(data);
|
|
@@ -901,17 +889,7 @@ function setupWebServer(config, cachedContent) {
|
|
|
901
889
|
} else if (event === "connection") {
|
|
902
890
|
config._phase = "running";
|
|
903
891
|
if (!config._groupMode) process.stdout.write("TAP version 13\n");
|
|
904
|
-
if (config.debug && config._groupMode)
|
|
905
|
-
const allFiles = Object.keys(config.fsTree);
|
|
906
|
-
const relFiles = allFiles.map(
|
|
907
|
-
(filePath) => filePath.replace(`${config.projectRoot}/`, "")
|
|
908
|
-
);
|
|
909
|
-
const shown = relFiles.slice(0, 2);
|
|
910
|
-
const rest = relFiles.length - shown.length;
|
|
911
|
-
const fileList = rest > 0 ? `${shown.join(" ")} +${rest} more` : shown.join(" ");
|
|
912
|
-
process.stdout.write(`# ${blue(`\u2500\u2500 ${fileList} \u2500\u2500`)}
|
|
913
|
-
`);
|
|
914
|
-
}
|
|
892
|
+
if (config.debug && config._groupMode) debugGroupHeader(config);
|
|
915
893
|
config._resetTestTimeout?.();
|
|
916
894
|
} else if (event === "testEnd" && !abort) {
|
|
917
895
|
if (details.status === "failed") {
|
|
@@ -983,69 +961,41 @@ function setupWebServer(config, cachedContent) {
|
|
|
983
961
|
});
|
|
984
962
|
res.end(cachedContent.filteredTestCode);
|
|
985
963
|
});
|
|
986
|
-
server.get("/",
|
|
964
|
+
server.get("/", (_req, res) => {
|
|
987
965
|
if (cachedContent._buildError) {
|
|
988
|
-
const
|
|
989
|
-
res.writeHead(200,
|
|
990
|
-
res.
|
|
991
|
-
|
|
992
|
-
return
|
|
993
|
-
`${config.projectRoot}/${config.output}/index.html`,
|
|
994
|
-
htmlContent2
|
|
995
|
-
);
|
|
966
|
+
const htmlContent = buildErrorHTML(cachedContent._buildError);
|
|
967
|
+
res.writeHead(200, HTML_HEADERS);
|
|
968
|
+
res.end(htmlContent);
|
|
969
|
+
saveHTML(`${config.projectRoot}/${config.output}/index.html`, htmlContent);
|
|
970
|
+
return;
|
|
996
971
|
}
|
|
997
972
|
if (cachedContent._noTestsWarning) {
|
|
998
|
-
|
|
999
|
-
res.
|
|
1000
|
-
res.write(htmlContent2);
|
|
1001
|
-
res.end();
|
|
973
|
+
res.writeHead(200, HTML_HEADERS);
|
|
974
|
+
res.end(buildNoTestsHTML(cachedContent._noTestsWarning));
|
|
1002
975
|
return;
|
|
1003
976
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
"./tests.js"
|
|
1008
|
-
);
|
|
1009
|
-
res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-store" });
|
|
1010
|
-
res.write(htmlContent);
|
|
1011
|
-
res.end();
|
|
1012
|
-
return await fsPromise.writeFile(
|
|
1013
|
-
`${config.projectRoot}/${config.output}/index.html`,
|
|
1014
|
-
htmlContent
|
|
1015
|
-
);
|
|
977
|
+
res.writeHead(200, HTML_HEADERS);
|
|
978
|
+
res.end(mainIndexHTML);
|
|
979
|
+
saveHTML(`${config.projectRoot}/${config.output}/index.html`, mainIndexHTML);
|
|
1016
980
|
});
|
|
1017
|
-
server.get("/qunitx.html",
|
|
981
|
+
server.get("/qunitx.html", (_req, res) => {
|
|
1018
982
|
if (cachedContent._buildError) {
|
|
1019
|
-
const
|
|
1020
|
-
res.writeHead(200,
|
|
1021
|
-
res.
|
|
1022
|
-
|
|
1023
|
-
return
|
|
1024
|
-
`${config.projectRoot}/${config.output}/qunitx.html`,
|
|
1025
|
-
htmlContent2
|
|
1026
|
-
);
|
|
983
|
+
const htmlContent = buildErrorHTML(cachedContent._buildError);
|
|
984
|
+
res.writeHead(200, HTML_HEADERS);
|
|
985
|
+
res.end(htmlContent);
|
|
986
|
+
saveHTML(`${config.projectRoot}/${config.output}/qunitx.html`, htmlContent);
|
|
987
|
+
return;
|
|
1027
988
|
}
|
|
1028
989
|
if (cachedContent._noTestsWarning) {
|
|
1029
|
-
|
|
1030
|
-
res.
|
|
1031
|
-
res.write(htmlContent2);
|
|
1032
|
-
res.end();
|
|
990
|
+
res.writeHead(200, HTML_HEADERS);
|
|
991
|
+
res.end(buildNoTestsHTML(cachedContent._noTestsWarning));
|
|
1033
992
|
return;
|
|
1034
993
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
"./filtered-tests.js"
|
|
1039
|
-
);
|
|
1040
|
-
res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": "no-store" });
|
|
1041
|
-
res.write(htmlContent);
|
|
1042
|
-
res.end();
|
|
1043
|
-
return await fsPromise.writeFile(
|
|
1044
|
-
`${config.projectRoot}/${config.output}/qunitx.html`,
|
|
1045
|
-
htmlContent
|
|
1046
|
-
);
|
|
994
|
+
res.writeHead(200, HTML_HEADERS);
|
|
995
|
+
res.end(mainQunitxHTML);
|
|
996
|
+
saveHTML(`${config.projectRoot}/${config.output}/qunitx.html`, mainQunitxHTML);
|
|
1047
997
|
});
|
|
1048
|
-
server.get("/*",
|
|
998
|
+
server.get("/*", (req, res) => {
|
|
1049
999
|
const possibleDynamicHTML = cachedContent.dynamicContentHTMLs[`${config.projectRoot}${req.path}`];
|
|
1050
1000
|
if (possibleDynamicHTML) {
|
|
1051
1001
|
const htmlContent = escapeAndInjectTestsToHTML(
|
|
@@ -1053,13 +1003,10 @@ function setupWebServer(config, cachedContent) {
|
|
|
1053
1003
|
runtimeScript,
|
|
1054
1004
|
"/tests.js"
|
|
1055
1005
|
);
|
|
1056
|
-
res.writeHead(200,
|
|
1057
|
-
res.
|
|
1058
|
-
|
|
1059
|
-
return
|
|
1060
|
-
`${config.projectRoot}/${config.output}${req.path}`,
|
|
1061
|
-
htmlContent
|
|
1062
|
-
);
|
|
1006
|
+
res.writeHead(200, HTML_HEADERS);
|
|
1007
|
+
res.end(htmlContent);
|
|
1008
|
+
saveHTML(`${config.projectRoot}/${config.output}${req.path}`, htmlContent);
|
|
1009
|
+
return;
|
|
1063
1010
|
}
|
|
1064
1011
|
const url = req.url;
|
|
1065
1012
|
const requestStartedAt = Date.now();
|
|
@@ -1093,7 +1040,8 @@ function replaceAssetPaths(html, htmlPath, projectRoot) {
|
|
|
1093
1040
|
return result.replace(assetPath, normalizedFullAbsolutePath.replace(projectRoot, "."));
|
|
1094
1041
|
}, html);
|
|
1095
1042
|
}
|
|
1096
|
-
function testRuntimeToInject(config) {
|
|
1043
|
+
function testRuntimeToInject(config, groupId) {
|
|
1044
|
+
const groupIdPart = groupId !== void 0 ? `, groupId: ${groupId}` : "";
|
|
1097
1045
|
return `<script>
|
|
1098
1046
|
window.testTimeout = 0;
|
|
1099
1047
|
setInterval(() => {
|
|
@@ -1142,7 +1090,7 @@ function testRuntimeToInject(config) {
|
|
|
1142
1090
|
// this runtime script is tiny \u2014 tests.js background compilation hasn't finished yet.
|
|
1143
1091
|
// Node.js uses this to distinguish "WS never connected" from "WS connected but bundle slow".
|
|
1144
1092
|
if (navigator.webdriver) {
|
|
1145
|
-
window.socket.send(JSON.stringify({ event: 'wsOpen' }));
|
|
1093
|
+
window.socket.send(JSON.stringify({ event: 'wsOpen'${groupIdPart} }));
|
|
1146
1094
|
}
|
|
1147
1095
|
maybeStart();
|
|
1148
1096
|
});
|
|
@@ -1457,7 +1405,146 @@ function buildErrorHTML(buildError) {
|
|
|
1457
1405
|
</body>
|
|
1458
1406
|
</html>`;
|
|
1459
1407
|
}
|
|
1460
|
-
|
|
1408
|
+
function registerGroupRoutes(server, groupConfig, groupCachedContent, groupId) {
|
|
1409
|
+
const mainHTMLWithReplacedAssets = replaceAssetPaths(
|
|
1410
|
+
groupCachedContent.mainHTML.html,
|
|
1411
|
+
groupCachedContent.mainHTML.filePath,
|
|
1412
|
+
groupConfig.projectRoot
|
|
1413
|
+
);
|
|
1414
|
+
const runtimeScript = testRuntimeToInject(groupConfig, groupId);
|
|
1415
|
+
const mainGroupHTML = escapeAndInjectTestsToHTML(
|
|
1416
|
+
mainHTMLWithReplacedAssets,
|
|
1417
|
+
runtimeScript,
|
|
1418
|
+
"./tests.js"
|
|
1419
|
+
);
|
|
1420
|
+
const saveHTML = (filePath, html) => fsPromise.writeFile(filePath, html).catch(
|
|
1421
|
+
(err) => groupConfig.debug && process.stderr.write(`# [qunitx] writeFile ${filePath}: ${err.message}
|
|
1422
|
+
`)
|
|
1423
|
+
);
|
|
1424
|
+
server.get(`/group-${groupId}/`, (_req, res) => {
|
|
1425
|
+
if (groupCachedContent._buildError) {
|
|
1426
|
+
res.writeHead(200, HTML_HEADERS);
|
|
1427
|
+
res.end(buildErrorHTML(groupCachedContent._buildError));
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
if (groupCachedContent._noTestsWarning) {
|
|
1431
|
+
res.writeHead(200, HTML_HEADERS);
|
|
1432
|
+
res.end(buildNoTestsHTML(groupCachedContent._noTestsWarning));
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
res.writeHead(200, HTML_HEADERS);
|
|
1436
|
+
res.end(mainGroupHTML);
|
|
1437
|
+
saveHTML(`${groupConfig.projectRoot}/${groupConfig.output}/index.html`, mainGroupHTML);
|
|
1438
|
+
});
|
|
1439
|
+
server.get(`/group-${groupId}/tests.js`, (_req, res) => {
|
|
1440
|
+
const bytes = groupCachedContent.allTestCode?.length ?? null;
|
|
1441
|
+
if (bytes === null) {
|
|
1442
|
+
res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
1443
|
+
res.end(
|
|
1444
|
+
'console.error("[qunitx] /tests.js requested before bundle was built \u2014 allTestCode is null");'
|
|
1445
|
+
);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
groupConfig._onTestsJsServed?.();
|
|
1449
|
+
res.writeHead(200, {
|
|
1450
|
+
"Content-Type": "application/javascript",
|
|
1451
|
+
"Cache-Control": "no-store",
|
|
1452
|
+
"Content-Length": bytes
|
|
1453
|
+
});
|
|
1454
|
+
res.end(groupCachedContent.allTestCode);
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
function debugGroupHeader(config) {
|
|
1458
|
+
const files = Object.keys(config.fsTree);
|
|
1459
|
+
const rel = files.map((f) => f.replace(`${config.projectRoot}/`, ""));
|
|
1460
|
+
const shown = rel.slice(0, 2);
|
|
1461
|
+
const rest = rel.length - shown.length;
|
|
1462
|
+
process.stdout.write(
|
|
1463
|
+
`# ${blue(`\u2500\u2500 ${shown.join(" ")}${rest > 0 ? ` +${rest} more` : ""} \u2500\u2500`)}
|
|
1464
|
+
`
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
function setupGroupWSHandler(server, groupConfigs) {
|
|
1468
|
+
const socketToGroupId = /* @__PURE__ */ new WeakMap();
|
|
1469
|
+
server.wss.on("connection", function connection(socket) {
|
|
1470
|
+
socket.on("message", function message(data) {
|
|
1471
|
+
const { event, groupId, details, qunitResult, abort } = JSON.parse(data);
|
|
1472
|
+
let resolvedGroupId = socketToGroupId.get(socket);
|
|
1473
|
+
if (event === "wsOpen" && typeof groupId === "number") {
|
|
1474
|
+
resolvedGroupId = groupId;
|
|
1475
|
+
socketToGroupId.set(socket, groupId);
|
|
1476
|
+
}
|
|
1477
|
+
if (resolvedGroupId === void 0) return;
|
|
1478
|
+
const config = groupConfigs[resolvedGroupId];
|
|
1479
|
+
if (!config) return;
|
|
1480
|
+
if (event === "wsOpen") {
|
|
1481
|
+
config._phase = "loading";
|
|
1482
|
+
config._onWsOpen?.();
|
|
1483
|
+
} else if (event === "connection") {
|
|
1484
|
+
config._phase = "running";
|
|
1485
|
+
if (config.debug) debugGroupHeader(config);
|
|
1486
|
+
config._resetTestTimeout?.();
|
|
1487
|
+
} else if (event === "testEnd" && !abort) {
|
|
1488
|
+
if (details.status === "failed") {
|
|
1489
|
+
config.lastFailedTestFiles = config.lastRanTestFiles;
|
|
1490
|
+
}
|
|
1491
|
+
if (config.debug && details.runtime > config.timeout * 0.8) {
|
|
1492
|
+
process.stdout.write(
|
|
1493
|
+
`# SLOW (${details.runtime.toFixed(0)}ms / ${config.timeout}ms timeout): ${details.fullName.join(" | ")}
|
|
1494
|
+
`
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
config._resetTestTimeout?.();
|
|
1498
|
+
TAPDisplayTestResult(config.COUNTER, details);
|
|
1499
|
+
} else if (event === "done") {
|
|
1500
|
+
config._phase = "done";
|
|
1501
|
+
config._lastQUnitResult = qunitResult ?? null;
|
|
1502
|
+
if (config.debug) {
|
|
1503
|
+
process.stdout.write(
|
|
1504
|
+
`# group done: ${details.passed} passed, ${details.failed} failed (${details.runtime}ms)
|
|
1505
|
+
`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
if (typeof config._testRunDone === "function") {
|
|
1509
|
+
config._testRunDone();
|
|
1510
|
+
config._testRunDone = null;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
function registerSharedStaticHandler(server, groupConfigs) {
|
|
1517
|
+
const groupUrlRegex = /^\/group-(\d+)(\/.*)?$/;
|
|
1518
|
+
server.get("/*", (req, res) => {
|
|
1519
|
+
const match = groupUrlRegex.exec(req.path);
|
|
1520
|
+
if (!match) {
|
|
1521
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1522
|
+
res.end("Not found");
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const groupId = parseInt(match[1], 10);
|
|
1526
|
+
const groupConfig = groupConfigs[groupId];
|
|
1527
|
+
if (!groupConfig) {
|
|
1528
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1529
|
+
res.end("Not found");
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const STATIC_FILES_PATH = path4.join(groupConfig.projectRoot, groupConfig.output);
|
|
1533
|
+
const subPath = match[2] || "/";
|
|
1534
|
+
const filePath = (subPath.endsWith("/") ? [STATIC_FILES_PATH, subPath, "index.html"] : [STATIC_FILES_PATH, subPath]).join("");
|
|
1535
|
+
const contentType = req.headers.accept?.includes("text/html") ? MIME_TYPES.html : MIME_TYPES[path4.extname(filePath).substring(1).toLowerCase()] || MIME_TYPES.html;
|
|
1536
|
+
const stream = fs8.createReadStream(filePath);
|
|
1537
|
+
stream.on("open", () => {
|
|
1538
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
1539
|
+
stream.pipe(res);
|
|
1540
|
+
});
|
|
1541
|
+
stream.on("error", () => {
|
|
1542
|
+
res.writeHead(404, { "Content-Type": contentType });
|
|
1543
|
+
res.end(contentType === MIME_TYPES.html ? NOT_FOUND_HTML : void 0);
|
|
1544
|
+
});
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
var fsPromise, HTML_HEADERS, NOT_FOUND_HTML;
|
|
1461
1548
|
var init_web_server = __esm({
|
|
1462
1549
|
"lib/setup/web-server.ts"() {
|
|
1463
1550
|
init_find_internal_assets_from_html();
|
|
@@ -1466,6 +1553,7 @@ var init_web_server = __esm({
|
|
|
1466
1553
|
init_color();
|
|
1467
1554
|
init_http();
|
|
1468
1555
|
fsPromise = fs8.promises;
|
|
1556
|
+
HTML_HEADERS = { "Content-Type": "text/html", "Cache-Control": "no-store" };
|
|
1469
1557
|
NOT_FOUND_HTML = `<!DOCTYPE html>
|
|
1470
1558
|
<html lang="en">
|
|
1471
1559
|
<head>
|
|
@@ -1541,19 +1629,27 @@ async function launchBrowser(config) {
|
|
|
1541
1629
|
handleSIGHUP: false
|
|
1542
1630
|
});
|
|
1543
1631
|
}
|
|
1544
|
-
async function setupBrowser(config, cachedContent, existingBrowser = null) {
|
|
1632
|
+
async function setupBrowser(config, cachedContent, existingBrowser = null, sharedServer = null) {
|
|
1545
1633
|
const setupStart = Date.now();
|
|
1546
|
-
const [server,
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1634
|
+
const [server, browser, page] = await (async () => {
|
|
1635
|
+
if (sharedServer) {
|
|
1636
|
+
const newPage2 = await existingBrowser.newPage();
|
|
1637
|
+
perfLog(`browser.js: newPage (shared server) took ${Date.now() - setupStart}ms`);
|
|
1638
|
+
return [sharedServer, existingBrowser, newPage2];
|
|
1639
|
+
}
|
|
1640
|
+
const [newServer, resolvedBrowser] = await Promise.all([
|
|
1641
|
+
setupWebServer(config, cachedContent),
|
|
1642
|
+
Promise.resolve(existingBrowser)
|
|
1643
|
+
]);
|
|
1644
|
+
perfLog(`browser.js: setupWebServer took ${Date.now() - setupStart}ms`);
|
|
1645
|
+
const activeBrowser = resolvedBrowser ?? await launchBrowser(config);
|
|
1646
|
+
const pageStart = Date.now();
|
|
1647
|
+
const isHeadedWatchMode = config.open === true && config.watch;
|
|
1648
|
+
const getPage = isHeadedWatchMode ? () => activeBrowser.contexts()[0]?.pages()[0] ?? activeBrowser.newPage() : () => activeBrowser.newPage();
|
|
1649
|
+
const [newPage] = await Promise.all([getPage(), bindServerToPort(newServer, config)]);
|
|
1650
|
+
perfLog(`browser.js: newPage + bindServerToPort took ${Date.now() - pageStart}ms`);
|
|
1651
|
+
return [newServer, activeBrowser, newPage];
|
|
1652
|
+
})();
|
|
1557
1653
|
if (config.browser === "firefox") {
|
|
1558
1654
|
await page.addInitScript(() => {
|
|
1559
1655
|
const preSerialize = (arg) => {
|
|
@@ -1875,8 +1971,10 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
1875
1971
|
fs9.writeFile(
|
|
1876
1972
|
`${projectRoot}/${output}/qunitx.html`,
|
|
1877
1973
|
buildErrorHTML(cachedContent._buildError)
|
|
1878
|
-
).catch(
|
|
1879
|
-
|
|
1974
|
+
).catch(
|
|
1975
|
+
(err) => config.debug && process.stderr.write(`# [qunitx] writeFile qunitx.html: ${err.message}
|
|
1976
|
+
`)
|
|
1977
|
+
);
|
|
1880
1978
|
}
|
|
1881
1979
|
if (config.watch) {
|
|
1882
1980
|
console.log(`# ${exception}`);
|
|
@@ -1908,9 +2006,6 @@ function buildFilteredTests(filteredTests, outputPath, config) {
|
|
|
1908
2006
|
);
|
|
1909
2007
|
}
|
|
1910
2008
|
async function runWithOverlayfsRetry(getContents, needsDisk) {
|
|
1911
|
-
const RETRY_DELAY_MS = 100;
|
|
1912
|
-
const MAX_RETRIES = 3;
|
|
1913
|
-
const EMPTY_BUNDLE_THRESHOLD = 500;
|
|
1914
2009
|
let { result, js } = await getContents();
|
|
1915
2010
|
const initialSize = js.length;
|
|
1916
2011
|
for (let retry = 1; retry <= MAX_RETRIES; retry++) {
|
|
@@ -2060,7 +2155,110 @@ async function flushConsoleHandlers(handlers, deadline = Date.now() + 2e3) {
|
|
|
2060
2155
|
await Promise.allSettled([...handlers]);
|
|
2061
2156
|
return flushConsoleHandlers(handlers, deadline);
|
|
2062
2157
|
}
|
|
2063
|
-
|
|
2158
|
+
async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
2159
|
+
groupCachedContents.forEach((cachedContent) => {
|
|
2160
|
+
cachedContent._buildError = null;
|
|
2161
|
+
cachedContent._noTestsWarning = null;
|
|
2162
|
+
});
|
|
2163
|
+
const { projectRoot, debug, watch, browser } = groupConfigs[0];
|
|
2164
|
+
const activeGroups = groupConfigs.reduce(
|
|
2165
|
+
(acc, groupConfig, groupIndex) => {
|
|
2166
|
+
const files = Object.keys(groupConfig.fsTree);
|
|
2167
|
+
if (files.length > 0)
|
|
2168
|
+
acc.push({
|
|
2169
|
+
groupIndex,
|
|
2170
|
+
config: groupConfig,
|
|
2171
|
+
cachedContent: groupCachedContents[groupIndex],
|
|
2172
|
+
files
|
|
2173
|
+
});
|
|
2174
|
+
return acc;
|
|
2175
|
+
},
|
|
2176
|
+
[]
|
|
2177
|
+
);
|
|
2178
|
+
if (activeGroups.length === 0)
|
|
2179
|
+
return console.log(
|
|
2180
|
+
"# [buildAllGroupBundles] all groups empty \u2014 skipping build (no test files found)"
|
|
2181
|
+
);
|
|
2182
|
+
await Promise.all(
|
|
2183
|
+
activeGroups.map(
|
|
2184
|
+
(group) => fs9.mkdir(`${group.config.projectRoot}/${group.config.output}`, { recursive: true })
|
|
2185
|
+
)
|
|
2186
|
+
);
|
|
2187
|
+
const sourcemap = debug ? "inline" : watch ? "linked" : false;
|
|
2188
|
+
const groupEntryPlugin = {
|
|
2189
|
+
name: "group-entry-loader",
|
|
2190
|
+
setup(build) {
|
|
2191
|
+
build.onResolve({ filter: /^group-entry-\d+$/ }, (args) => ({
|
|
2192
|
+
path: args.path,
|
|
2193
|
+
namespace: "group-entry"
|
|
2194
|
+
}));
|
|
2195
|
+
build.onLoad({ filter: /.*/, namespace: "group-entry" }, (args) => {
|
|
2196
|
+
const slotIndex = parseInt(args.path.replace("group-entry-", ""));
|
|
2197
|
+
return {
|
|
2198
|
+
contents: activeGroups[slotIndex].files.map((filePath) => `import "${filePath}";`).join(""),
|
|
2199
|
+
resolveDir: process.cwd()
|
|
2200
|
+
};
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
const buildOptions = {
|
|
2205
|
+
entryPoints: activeGroups.map((_, slotIndex) => ({
|
|
2206
|
+
in: `group-entry-${slotIndex}`,
|
|
2207
|
+
out: `group-${slotIndex}`
|
|
2208
|
+
})),
|
|
2209
|
+
plugins: [groupEntryPlugin],
|
|
2210
|
+
nodePaths: ANCESTOR_NODE_MODULES,
|
|
2211
|
+
bundle: true,
|
|
2212
|
+
logLevel: "silent",
|
|
2213
|
+
// outdir only labels the paths in outputFiles[].path — nothing is written to disk
|
|
2214
|
+
// (write:false). Use projectRoot/tmp as a stable sentinel; mkdir is not required.
|
|
2215
|
+
outdir: path5.join(projectRoot, "tmp"),
|
|
2216
|
+
keepNames: true,
|
|
2217
|
+
legalComments: "none",
|
|
2218
|
+
target: esbuildTarget(browser),
|
|
2219
|
+
sourcemap,
|
|
2220
|
+
write: false,
|
|
2221
|
+
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
2222
|
+
};
|
|
2223
|
+
const hasSmallOutput = (result) => (result.outputFiles ?? []).some(
|
|
2224
|
+
(outputFile) => GROUP_OUTPUT_REGEX.test(outputFile.path) && !outputFile.path.endsWith(".map") && outputFile.contents.length < EMPTY_BUNDLE_THRESHOLD
|
|
2225
|
+
);
|
|
2226
|
+
const buildWithRetry = async (retriesLeft) => {
|
|
2227
|
+
const result = await esbuild.build(buildOptions);
|
|
2228
|
+
if (!hasSmallOutput(result) || retriesLeft === 0) return result;
|
|
2229
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
2230
|
+
return buildWithRetry(retriesLeft - 1);
|
|
2231
|
+
};
|
|
2232
|
+
try {
|
|
2233
|
+
const result = await buildWithRetry(MAX_RETRIES);
|
|
2234
|
+
await Promise.all(
|
|
2235
|
+
(result.outputFiles ?? []).map((outputFile) => {
|
|
2236
|
+
const match = GROUP_OUTPUT_REGEX.exec(outputFile.path);
|
|
2237
|
+
if (!match) return Promise.resolve();
|
|
2238
|
+
const slotIndex = parseInt(match[1]);
|
|
2239
|
+
const isMap = Boolean(match[2]);
|
|
2240
|
+
const { config, cachedContent } = activeGroups[slotIndex];
|
|
2241
|
+
const destPath = `${config.projectRoot}/${config.output}/tests.js${isMap ? ".map" : ""}`;
|
|
2242
|
+
if (!isMap) cachedContent.allTestCode = Buffer.from(outputFile.contents);
|
|
2243
|
+
return fs9.writeFile(destPath, outputFile.contents);
|
|
2244
|
+
})
|
|
2245
|
+
);
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
const buildError = { type: deriveBuildErrorType(error), formatted: formatBuildErrors(error) };
|
|
2248
|
+
const errorHtml = buildErrorHTML(buildError);
|
|
2249
|
+
await Promise.all(
|
|
2250
|
+
activeGroups.map((group) => {
|
|
2251
|
+
group.cachedContent._buildError = buildError;
|
|
2252
|
+
return fs9.writeFile(`${group.config.projectRoot}/${group.config.output}/index.html`, errorHtml).catch(
|
|
2253
|
+
(err) => debug && process.stderr.write(`# [qunitx] writeFile index.html: ${err.message}
|
|
2254
|
+
`)
|
|
2255
|
+
);
|
|
2256
|
+
})
|
|
2257
|
+
);
|
|
2258
|
+
throw error;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
var BundleError, ancestorNodeModules, ANCESTOR_NODE_MODULES, RETRY_DELAY_MS, MAX_RETRIES, EMPTY_BUNDLE_THRESHOLD, GROUP_OUTPUT_REGEX;
|
|
2064
2262
|
var init_tests_in_browser = __esm({
|
|
2065
2263
|
"lib/commands/run/tests-in-browser.ts"() {
|
|
2066
2264
|
init_color();
|
|
@@ -2080,6 +2278,10 @@ var init_tests_in_browser = __esm({
|
|
|
2080
2278
|
(_, i, parts) => path5.join(parts.slice(0, parts.length - i).join(path5.sep) || path5.sep, "node_modules")
|
|
2081
2279
|
);
|
|
2082
2280
|
ANCESTOR_NODE_MODULES = ancestorNodeModules(process.cwd());
|
|
2281
|
+
RETRY_DELAY_MS = 100;
|
|
2282
|
+
MAX_RETRIES = 3;
|
|
2283
|
+
EMPTY_BUNDLE_THRESHOLD = 500;
|
|
2284
|
+
GROUP_OUTPUT_REGEX = /group-(\d+)\.js(\.map)?$/;
|
|
2083
2285
|
}
|
|
2084
2286
|
});
|
|
2085
2287
|
|
|
@@ -2472,6 +2674,13 @@ async function run(config) {
|
|
|
2472
2674
|
_phase: "bundling"
|
|
2473
2675
|
}));
|
|
2474
2676
|
const groupCachedContents = groups.map(() => ({ ...cachedContent }));
|
|
2677
|
+
const sharedServer = groupCount > 1 && cachedContent.htmlPathsToRunTests[0] === "/" && cachedContent.htmlPathsToRunTests.length === 1 ? (() => {
|
|
2678
|
+
const s = new HTTPServer();
|
|
2679
|
+
setupGroupWSHandler(s, groupConfigs);
|
|
2680
|
+
groupConfigs.forEach((gc, i) => registerGroupRoutes(s, gc, groupCachedContents[i], i));
|
|
2681
|
+
registerSharedStaticHandler(s, groupConfigs);
|
|
2682
|
+
return s;
|
|
2683
|
+
})() : null;
|
|
2475
2684
|
process.stdout.write("TAP version 13\n");
|
|
2476
2685
|
process.stdout.write(
|
|
2477
2686
|
`# Running ${allFiles.length} test file${allFiles.length === 1 ? "" : "s"} across ${groupCount} group${groupCount === 1 ? "" : "s"}
|
|
@@ -2479,14 +2688,18 @@ async function run(config) {
|
|
|
2479
2688
|
);
|
|
2480
2689
|
const [browser] = await Promise.all([
|
|
2481
2690
|
launchBrowser(config),
|
|
2482
|
-
|
|
2483
|
-
groupConfigs.
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2691
|
+
sharedServer ? bindServerToPort(sharedServer, config).then(
|
|
2692
|
+
() => groupConfigs.forEach((gc, i) => {
|
|
2693
|
+
gc.port = config.port;
|
|
2694
|
+
groupCachedContents[i].htmlPathsToRunTests = [`/group-${i}/`];
|
|
2695
|
+
})
|
|
2696
|
+
) : Promise.resolve(),
|
|
2697
|
+
Promise.all([
|
|
2698
|
+
groupCount > 1 ? buildAllGroupBundles(groupConfigs, groupCachedContents) : buildTestBundle(groupConfigs[0], groupCachedContents[0]),
|
|
2699
|
+
Promise.all(
|
|
2700
|
+
groupConfigs.map((gc, i) => writeOutputStaticFiles(gc, groupCachedContents[i]))
|
|
2488
2701
|
)
|
|
2489
|
-
)
|
|
2702
|
+
])
|
|
2490
2703
|
]);
|
|
2491
2704
|
if (config.open) {
|
|
2492
2705
|
void openOutputInBrowser(config);
|
|
@@ -2495,7 +2708,7 @@ async function run(config) {
|
|
|
2495
2708
|
const wallTimes = /* @__PURE__ */ new Map();
|
|
2496
2709
|
const GROUP_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
2497
2710
|
const keepAlive = setInterval(() => {
|
|
2498
|
-
},
|
|
2711
|
+
}, 1e4);
|
|
2499
2712
|
const groupResults = await Promise.allSettled(
|
|
2500
2713
|
groupConfigs.map((groupConfig, i) => {
|
|
2501
2714
|
const groupTimeout = new Promise((_, reject) => {
|
|
@@ -2515,7 +2728,12 @@ async function run(config) {
|
|
|
2515
2728
|
const startMs = Date.now();
|
|
2516
2729
|
const work = (async () => {
|
|
2517
2730
|
groupConfig._phase = "connecting";
|
|
2518
|
-
const connections = await setupBrowser(
|
|
2731
|
+
const connections = await setupBrowser(
|
|
2732
|
+
groupConfig,
|
|
2733
|
+
groupCachedContents[i],
|
|
2734
|
+
browser,
|
|
2735
|
+
sharedServer
|
|
2736
|
+
);
|
|
2519
2737
|
groupConfig.expressApp = connections.server;
|
|
2520
2738
|
if (config.before) {
|
|
2521
2739
|
await runUserModule(`${process.cwd()}/${config.before}`, groupConfig, "before");
|
|
@@ -2525,7 +2743,7 @@ async function run(config) {
|
|
|
2525
2743
|
} finally {
|
|
2526
2744
|
await flushConsoleHandlers(groupConfig._pendingConsoleHandlers);
|
|
2527
2745
|
await Promise.all([
|
|
2528
|
-
|
|
2746
|
+
!sharedServer && connections.server?.close(),
|
|
2529
2747
|
connections.page && // Unref'd: the keepAlive interval above holds the event loop open, so this
|
|
2530
2748
|
// timer still fires if page.close() hangs, without preventing process exit later.
|
|
2531
2749
|
Promise.race([
|
|
@@ -2561,8 +2779,10 @@ async function run(config) {
|
|
|
2561
2779
|
}
|
|
2562
2780
|
TAPDisplayFinalResult(config.COUNTER, TIME_COUNTER.stop());
|
|
2563
2781
|
const fileTimes = computeFileTimes(groups, weights, wallTimes);
|
|
2564
|
-
persistTimings(fileTimes, config.projectRoot).catch(
|
|
2565
|
-
|
|
2782
|
+
persistTimings(fileTimes, config.projectRoot).catch(
|
|
2783
|
+
(err) => config.debug && process.stderr.write(`# [qunitx] persistTimings: ${err.message}
|
|
2784
|
+
`)
|
|
2785
|
+
);
|
|
2566
2786
|
printFileTimings(fileTimes, config.projectRoot);
|
|
2567
2787
|
if (config.after) {
|
|
2568
2788
|
await runUserModule(`${process.cwd()}/${config.after}`, config.COUNTER, "after");
|
|
@@ -2572,8 +2792,16 @@ async function run(config) {
|
|
|
2572
2792
|
process.stdout.write("\n", async () => {
|
|
2573
2793
|
clearTimeout(exitTimer);
|
|
2574
2794
|
clearInterval(keepAlive);
|
|
2575
|
-
await
|
|
2576
|
-
|
|
2795
|
+
await Promise.all([
|
|
2796
|
+
sharedServer?.close().catch(
|
|
2797
|
+
(err) => config.debug && process.stderr.write(`# [qunitx] server.close: ${err.message}
|
|
2798
|
+
`)
|
|
2799
|
+
),
|
|
2800
|
+
browser.close().catch(
|
|
2801
|
+
(err) => config.debug && process.stderr.write(`# [qunitx] browser.close: ${err.message}
|
|
2802
|
+
`)
|
|
2803
|
+
)
|
|
2804
|
+
]);
|
|
2577
2805
|
await shutdownPrelaunch();
|
|
2578
2806
|
process.exit(exitCode);
|
|
2579
2807
|
});
|
|
@@ -2670,7 +2898,7 @@ ${lines.join("\n")}
|
|
|
2670
2898
|
async function splitIntoGroups(files, groupCount, timings) {
|
|
2671
2899
|
const sizes = await Promise.all(
|
|
2672
2900
|
files.map(
|
|
2673
|
-
(f) => fs12.stat(f).then((s) => s.size).catch(() => 0)
|
|
2901
|
+
(f) => timings[f] > 0 ? Promise.resolve(0) : fs12.stat(f).then((s) => s.size).catch(() => 0)
|
|
2674
2902
|
)
|
|
2675
2903
|
);
|
|
2676
2904
|
const knownRates = files.map((f, i) => ({ ms: timings[f], size: sizes[i] })).filter(({ ms, size }) => ms > 0 && size > 0);
|
|
@@ -2707,6 +2935,9 @@ var init_run = __esm({
|
|
|
2707
2935
|
"lib/commands/run.ts"() {
|
|
2708
2936
|
init_browser();
|
|
2709
2937
|
init_chrome_prelaunch();
|
|
2938
|
+
init_http();
|
|
2939
|
+
init_bind_server_to_port();
|
|
2940
|
+
init_web_server();
|
|
2710
2941
|
init_open_output_in_browser();
|
|
2711
2942
|
init_color();
|
|
2712
2943
|
init_tests_in_browser();
|
|
@@ -2733,7 +2964,7 @@ init_color();
|
|
|
2733
2964
|
var package_default = {
|
|
2734
2965
|
name: "qunitx-cli",
|
|
2735
2966
|
type: "module",
|
|
2736
|
-
version: "0.19.
|
|
2967
|
+
version: "0.19.3",
|
|
2737
2968
|
description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
2738
2969
|
author: "Izel Nakri",
|
|
2739
2970
|
license: "MIT",
|
|
@@ -3238,6 +3469,7 @@ process4.title = "qunitx";
|
|
|
3238
3469
|
} catch (error) {
|
|
3239
3470
|
console.error(error);
|
|
3240
3471
|
process4.exitCode = 1;
|
|
3472
|
+
await shutdownPrelaunch();
|
|
3241
3473
|
process4.stdout.write("\n", () => process4.exit(1));
|
|
3242
3474
|
}
|
|
3243
3475
|
})();
|