qunitx-cli 0.23.6 → 0.24.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/dist/cli.js +353 -149
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -374,7 +374,7 @@ var init_package = __esm({
|
|
|
374
374
|
package_default = {
|
|
375
375
|
name: "qunitx-cli",
|
|
376
376
|
type: "module",
|
|
377
|
-
version: "0.
|
|
377
|
+
version: "0.24.0",
|
|
378
378
|
description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
379
379
|
author: "Izel Nakri",
|
|
380
380
|
license: "MIT",
|
|
@@ -519,6 +519,8 @@ ${color("--browser")} : browser engine to run tests in: chromium, firefox, webki
|
|
|
519
519
|
${color("--before")} : run a script before the tests(i.e start a new web server before tests)
|
|
520
520
|
${color("--after")} : run a script after the tests(i.e save test results to a file)
|
|
521
521
|
${color("--no-daemon")} : don't use the daemon for this run \u2014 skips a running daemon and prevents ${color("QUNITX_DAEMON")} auto-spawn
|
|
522
|
+
${color("--changed")} : run only test files affected by changes since ${color("HEAD")} (requires git; falls back to running all on first use)
|
|
523
|
+
${color("--since")} : run only test files affected by changes since the given git ref (e.g. ${color("--since=main")}); ${color("--changed")} = ${color("--since=HEAD")}
|
|
522
524
|
${color("--trace-perf")} : write timestamped startup-perf trace lines to stderr (Chrome pre-launch, module load, browser bind)
|
|
523
525
|
|
|
524
526
|
${highlight("Example:")} $ ${color("qunitx test/foo.ts app/e2e --debug --watch --before=scripts/start-new-webserver.js --after=scripts/write-test-results.js")}
|
|
@@ -547,9 +549,9 @@ var init_help = __esm({
|
|
|
547
549
|
|
|
548
550
|
// lib/utils/path-exists.ts
|
|
549
551
|
import fs2 from "node:fs/promises";
|
|
550
|
-
async function pathExists(
|
|
552
|
+
async function pathExists(path17) {
|
|
551
553
|
try {
|
|
552
|
-
await fs2.access(
|
|
554
|
+
await fs2.access(path17);
|
|
553
555
|
return true;
|
|
554
556
|
} catch {
|
|
555
557
|
return false;
|
|
@@ -641,17 +643,17 @@ import fs4 from "node:fs/promises";
|
|
|
641
643
|
async function generateTestFiles() {
|
|
642
644
|
const projectRoot = await findProjectRoot();
|
|
643
645
|
const moduleName = pathToModuleName(process.argv[3]);
|
|
644
|
-
const
|
|
645
|
-
if (await pathExists(
|
|
646
|
-
console.log(`${
|
|
646
|
+
const path17 = process.argv[3].endsWith(".js") || process.argv[3].endsWith(".ts") ? `${projectRoot}/${process.argv[3]}` : `${projectRoot}/${process.argv[3]}.js`;
|
|
647
|
+
if (await pathExists(path17)) {
|
|
648
|
+
console.log(`${path17} already exists!`);
|
|
647
649
|
return;
|
|
648
650
|
}
|
|
649
651
|
const testJSContent = await readTemplate("test.js");
|
|
650
|
-
const targetFolderPaths =
|
|
652
|
+
const targetFolderPaths = path17.split("/");
|
|
651
653
|
targetFolderPaths.pop();
|
|
652
654
|
await fs4.mkdir(targetFolderPaths.join("/"), { recursive: true });
|
|
653
|
-
await fs4.writeFile(
|
|
654
|
-
console.log(green(`${
|
|
655
|
+
await fs4.writeFile(path17, testJSContent.replace("{{moduleName}}", moduleName));
|
|
656
|
+
console.log(green(`${path17} written`));
|
|
655
657
|
}
|
|
656
658
|
function pathToModuleName(filePath) {
|
|
657
659
|
const withoutExt = filePath.replace(/\.(js|ts)$/, "");
|
|
@@ -892,9 +894,9 @@ async function pingDaemon() {
|
|
|
892
894
|
socket.end();
|
|
893
895
|
return pong;
|
|
894
896
|
}
|
|
895
|
-
async function shutdownDaemon() {
|
|
896
|
-
const pid = await readDaemonPid();
|
|
897
|
-
const socket = await tryConnect();
|
|
897
|
+
async function shutdownDaemon(cwd = process.cwd()) {
|
|
898
|
+
const pid = await readDaemonPid(cwd);
|
|
899
|
+
const socket = await tryConnect(cwd);
|
|
898
900
|
if (!socket) return false;
|
|
899
901
|
attachLineParser(socket, () => {
|
|
900
902
|
});
|
|
@@ -903,9 +905,9 @@ async function shutdownDaemon() {
|
|
|
903
905
|
if (pid !== null) await waitForPidExit(pid, SHUTDOWN_PID_WAIT_MS);
|
|
904
906
|
return true;
|
|
905
907
|
}
|
|
906
|
-
async function readDaemonPid() {
|
|
908
|
+
async function readDaemonPid(cwd = process.cwd()) {
|
|
907
909
|
try {
|
|
908
|
-
const info = JSON.parse(await fs6.readFile(daemonInfoPath(), "utf8"));
|
|
910
|
+
const info = JSON.parse(await fs6.readFile(daemonInfoPath(cwd), "utf8"));
|
|
909
911
|
return typeof info.pid === "number" ? info.pid : null;
|
|
910
912
|
} catch {
|
|
911
913
|
return null;
|
|
@@ -1068,11 +1070,11 @@ function setupTestFilePaths(inputs2) {
|
|
|
1068
1070
|
}, []);
|
|
1069
1071
|
return dedupedFolders.concat(dedupedGlobFiles, dedupedPlainFiles).map((meta) => meta.input);
|
|
1070
1072
|
}
|
|
1071
|
-
function pathIsFile(
|
|
1072
|
-
return
|
|
1073
|
+
function pathIsFile(path17) {
|
|
1074
|
+
return path17.includes(".", path17.lastIndexOf("/") + 1);
|
|
1073
1075
|
}
|
|
1074
1076
|
function isIncludedIn(paths, target) {
|
|
1075
|
-
return paths.some((
|
|
1077
|
+
return paths.some((path17) => path17 !== target && matchesGlob(target.input, path17.globFormat));
|
|
1076
1078
|
}
|
|
1077
1079
|
function isGlob2(str) {
|
|
1078
1080
|
return GLOB_CHARS.test(str);
|
|
@@ -1084,8 +1086,162 @@ var init_test_file_paths = __esm({
|
|
|
1084
1086
|
}
|
|
1085
1087
|
});
|
|
1086
1088
|
|
|
1087
|
-
// lib/utils/
|
|
1089
|
+
// lib/utils/get-changed-files.ts
|
|
1088
1090
|
import path5 from "node:path";
|
|
1091
|
+
function getChangedFiles(metafile, esbuildCwd, changedAbsPaths, testFiles) {
|
|
1092
|
+
const deps = new Map(
|
|
1093
|
+
Object.entries(metafile.inputs).map(([relPath, info]) => [
|
|
1094
|
+
path5.resolve(esbuildCwd, relPath),
|
|
1095
|
+
(info.imports ?? []).map((i) => path5.resolve(esbuildCwd, i.path))
|
|
1096
|
+
])
|
|
1097
|
+
);
|
|
1098
|
+
const memo = /* @__PURE__ */ new Map();
|
|
1099
|
+
return new Set(
|
|
1100
|
+
testFiles.filter((test) => reachesChange(test, deps, changedAbsPaths, memo, /* @__PURE__ */ new Set()))
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
function reachesChange(node, deps, changedAbsPaths, memo, stack) {
|
|
1104
|
+
const cached = memo.get(node);
|
|
1105
|
+
if (cached !== void 0) return cached;
|
|
1106
|
+
if (stack.has(node)) return false;
|
|
1107
|
+
stack.add(node);
|
|
1108
|
+
const result2 = changedAbsPaths.has(node) || (deps.get(node) ?? []).some((dep) => reachesChange(dep, deps, changedAbsPaths, memo, stack));
|
|
1109
|
+
stack.delete(node);
|
|
1110
|
+
memo.set(node, result2);
|
|
1111
|
+
return result2;
|
|
1112
|
+
}
|
|
1113
|
+
var init_get_changed_files = __esm({
|
|
1114
|
+
"lib/utils/get-changed-files.ts"() {
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// lib/utils/get-changed-file-paths-in-git-since.ts
|
|
1119
|
+
import { execFile } from "node:child_process";
|
|
1120
|
+
import { promisify } from "node:util";
|
|
1121
|
+
import path6 from "node:path";
|
|
1122
|
+
async function getChangedFilePathsInGitSince(projectRoot, ref) {
|
|
1123
|
+
const [diffOut, statusOut] = await Promise.all([
|
|
1124
|
+
execFileAsync("git", ["diff", "--name-only", "--no-renames", ref, "--", projectRoot], {
|
|
1125
|
+
cwd: projectRoot,
|
|
1126
|
+
maxBuffer: 16 * 1024 * 1024
|
|
1127
|
+
}).then((r) => r.stdout),
|
|
1128
|
+
execFileAsync("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
1129
|
+
cwd: projectRoot,
|
|
1130
|
+
maxBuffer: 16 * 1024 * 1024
|
|
1131
|
+
}).then((r) => r.stdout)
|
|
1132
|
+
]);
|
|
1133
|
+
const fromStatus = (line) => {
|
|
1134
|
+
const rest = line.slice(3);
|
|
1135
|
+
const arrow = rest.indexOf(" -> ");
|
|
1136
|
+
return arrow === -1 ? rest : rest.slice(arrow + 4);
|
|
1137
|
+
};
|
|
1138
|
+
const relPaths = /* @__PURE__ */ new Set([
|
|
1139
|
+
...diffOut.split("\n").filter(Boolean),
|
|
1140
|
+
...statusOut.split("\n").filter((l) => l.length >= 4).map(fromStatus)
|
|
1141
|
+
]);
|
|
1142
|
+
const isBlastRadius = (rel) => {
|
|
1143
|
+
const base = path6.basename(rel);
|
|
1144
|
+
return BLAST_RADIUS_FILES.has(base) || BLAST_RADIUS_PATTERNS.some((re) => re.test(base));
|
|
1145
|
+
};
|
|
1146
|
+
if (Array.from(relPaths).some(isBlastRadius)) return null;
|
|
1147
|
+
return new Set(Array.from(relPaths, (rel) => path6.resolve(projectRoot, rel)));
|
|
1148
|
+
}
|
|
1149
|
+
var execFileAsync, BLAST_RADIUS_FILES, BLAST_RADIUS_PATTERNS;
|
|
1150
|
+
var init_get_changed_file_paths_in_git_since = __esm({
|
|
1151
|
+
"lib/utils/get-changed-file-paths-in-git-since.ts"() {
|
|
1152
|
+
execFileAsync = promisify(execFile);
|
|
1153
|
+
BLAST_RADIUS_FILES = /* @__PURE__ */ new Set(["package.json", "package-lock.json", "deno.json", "deno.lock"]);
|
|
1154
|
+
BLAST_RADIUS_PATTERNS = [/^tsconfig.*\.json$/];
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
// lib/utils/metafile-cache.ts
|
|
1159
|
+
import fs8 from "node:fs/promises";
|
|
1160
|
+
import path7 from "node:path";
|
|
1161
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1162
|
+
function metafileCachePath(projectRoot) {
|
|
1163
|
+
const tag = createHash2("sha1").update(projectRoot).digest("hex").slice(0, 12);
|
|
1164
|
+
return path7.join(projectRoot, "node_modules", ".cache", "qunitx", tag, CACHE_FILE);
|
|
1165
|
+
}
|
|
1166
|
+
async function writeMetafileCache(projectRoot, esbuildCwd, metafile) {
|
|
1167
|
+
const file = metafileCachePath(projectRoot);
|
|
1168
|
+
try {
|
|
1169
|
+
await fs8.mkdir(path7.dirname(file), { recursive: true });
|
|
1170
|
+
await fs8.writeFile(
|
|
1171
|
+
file,
|
|
1172
|
+
JSON.stringify({ esbuildCwd, metafile })
|
|
1173
|
+
);
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
async function readMetafileCache(projectRoot) {
|
|
1178
|
+
try {
|
|
1179
|
+
const raw = await fs8.readFile(metafileCachePath(projectRoot), "utf8");
|
|
1180
|
+
const parsed = JSON.parse(raw);
|
|
1181
|
+
if (typeof parsed?.esbuildCwd !== "string" || !parsed.metafile?.inputs) return null;
|
|
1182
|
+
return parsed;
|
|
1183
|
+
} catch {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
var CACHE_FILE;
|
|
1188
|
+
var init_metafile_cache = __esm({
|
|
1189
|
+
"lib/utils/metafile-cache.ts"() {
|
|
1190
|
+
CACHE_FILE = "metafile.json";
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
// lib/setup/get-changed-fs-tree.ts
|
|
1195
|
+
async function getChangedFsTree(fsTree, projectRoot, changedSince) {
|
|
1196
|
+
const testFiles = Object.keys(fsTree);
|
|
1197
|
+
if (testFiles.length === 0) return fsTree;
|
|
1198
|
+
const cache = await readMetafileCache(projectRoot);
|
|
1199
|
+
if (!cache) {
|
|
1200
|
+
process.stdout.write(
|
|
1201
|
+
`# --changed: no metafile cache yet \u2014 running all ${testFiles.length} test files (cache populates on this run)
|
|
1202
|
+
`
|
|
1203
|
+
);
|
|
1204
|
+
return fsTree;
|
|
1205
|
+
}
|
|
1206
|
+
const changed = await getChangedFilePathsInGitSince(projectRoot, changedSince).catch(
|
|
1207
|
+
(err) => err
|
|
1208
|
+
);
|
|
1209
|
+
if (changed instanceof Error) {
|
|
1210
|
+
process.stdout.write(
|
|
1211
|
+
`# --changed: git lookup failed (${changed.message.split("\n")[0]}) \u2014 running all ${testFiles.length} test files
|
|
1212
|
+
`
|
|
1213
|
+
);
|
|
1214
|
+
return fsTree;
|
|
1215
|
+
} else if (changed === null) {
|
|
1216
|
+
process.stdout.write(
|
|
1217
|
+
`# --changed: blast-radius file changed (package.json / tsconfig.json / lockfile) \u2014 running all ${testFiles.length} test files
|
|
1218
|
+
`
|
|
1219
|
+
);
|
|
1220
|
+
return fsTree;
|
|
1221
|
+
} else if (changed.size === 0) {
|
|
1222
|
+
process.stdout.write(
|
|
1223
|
+
`# --changed: 0 files changed since ${changedSince} \u2014 running 0 test files
|
|
1224
|
+
`
|
|
1225
|
+
);
|
|
1226
|
+
return {};
|
|
1227
|
+
}
|
|
1228
|
+
const affected = getChangedFiles(cache.metafile, cache.esbuildCwd, changed, testFiles);
|
|
1229
|
+
process.stdout.write(
|
|
1230
|
+
`# --changed: ${affected.size} of ${testFiles.length} test files affected by changes since ${changedSince}
|
|
1231
|
+
`
|
|
1232
|
+
);
|
|
1233
|
+
return Object.fromEntries(testFiles.filter((f) => affected.has(f)).map((f) => [f, null]));
|
|
1234
|
+
}
|
|
1235
|
+
var init_get_changed_fs_tree = __esm({
|
|
1236
|
+
"lib/setup/get-changed-fs-tree.ts"() {
|
|
1237
|
+
init_get_changed_files();
|
|
1238
|
+
init_get_changed_file_paths_in_git_since();
|
|
1239
|
+
init_metafile_cache();
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
// lib/utils/parse-cli-flags.ts
|
|
1244
|
+
import path8 from "node:path";
|
|
1089
1245
|
function parseCliFlags(projectRoot) {
|
|
1090
1246
|
const providedFlags = process.argv.slice(2).reduce(
|
|
1091
1247
|
(result2, arg) => {
|
|
@@ -1129,6 +1285,15 @@ function parseCliFlags(projectRoot) {
|
|
|
1129
1285
|
return Object.assign(result2, { before: parseModule(arg.split("=")[1]) });
|
|
1130
1286
|
} else if (arg.startsWith("--after")) {
|
|
1131
1287
|
return Object.assign(result2, { after: parseModule(arg.split("=")[1]) });
|
|
1288
|
+
} else if (arg === "--changed") {
|
|
1289
|
+
return Object.assign(result2, { changedSince: "HEAD" });
|
|
1290
|
+
} else if (arg.startsWith("--since")) {
|
|
1291
|
+
const ref = arg.split("=")[1];
|
|
1292
|
+
if (!ref) {
|
|
1293
|
+
console.error(`Invalid --since value: empty. Expected --since=<git-ref>.`);
|
|
1294
|
+
process.exit(1);
|
|
1295
|
+
}
|
|
1296
|
+
return Object.assign(result2, { changedSince: ref });
|
|
1132
1297
|
} else if (arg === "--trace-perf") {
|
|
1133
1298
|
return result2;
|
|
1134
1299
|
}
|
|
@@ -1137,7 +1302,7 @@ function parseCliFlags(projectRoot) {
|
|
|
1137
1302
|
return result2;
|
|
1138
1303
|
}
|
|
1139
1304
|
result2.inputs.add(
|
|
1140
|
-
arg.startsWith(projectRoot) ||
|
|
1305
|
+
arg.startsWith(projectRoot) || path8.isAbsolute(arg) ? arg : path8.join(process.cwd(), arg)
|
|
1141
1306
|
);
|
|
1142
1307
|
return result2;
|
|
1143
1308
|
},
|
|
@@ -1185,7 +1350,7 @@ __export(config_exports, {
|
|
|
1185
1350
|
default: () => setupConfig,
|
|
1186
1351
|
setupConfig: () => setupConfig
|
|
1187
1352
|
});
|
|
1188
|
-
import
|
|
1353
|
+
import fs9 from "node:fs/promises";
|
|
1189
1354
|
import { createRequire } from "node:module";
|
|
1190
1355
|
import { pathToFileURL } from "node:url";
|
|
1191
1356
|
async function setupConfig() {
|
|
@@ -1223,10 +1388,13 @@ async function setupConfig() {
|
|
|
1223
1388
|
buildFSTree(config.testFileLookupPaths, config),
|
|
1224
1389
|
pluginsPromise
|
|
1225
1390
|
]);
|
|
1391
|
+
if (config.changedSince && !config.watch) {
|
|
1392
|
+
config.fsTree = await getChangedFsTree(config.fsTree, config.projectRoot, config.changedSince);
|
|
1393
|
+
}
|
|
1226
1394
|
return config;
|
|
1227
1395
|
}
|
|
1228
1396
|
async function readConfigFromPackageJSON(projectRoot) {
|
|
1229
|
-
const packageJSON = await
|
|
1397
|
+
const packageJSON = await fs9.readFile(`${projectRoot}/package.json`);
|
|
1230
1398
|
return JSON.parse(packageJSON.toString());
|
|
1231
1399
|
}
|
|
1232
1400
|
function normalizeHTMLPaths(projectRoot, htmlPaths) {
|
|
@@ -1258,6 +1426,7 @@ var init_config = __esm({
|
|
|
1258
1426
|
init_find_project_root();
|
|
1259
1427
|
init_fs_tree();
|
|
1260
1428
|
init_test_file_paths();
|
|
1429
|
+
init_get_changed_fs_tree();
|
|
1261
1430
|
init_parse_cli_flags();
|
|
1262
1431
|
}
|
|
1263
1432
|
});
|
|
@@ -1529,13 +1698,13 @@ function extractSourceLine(content, lineIndex) {
|
|
|
1529
1698
|
const line = content.split("\n", lineIndex + 1)[lineIndex];
|
|
1530
1699
|
return line?.trim() || null;
|
|
1531
1700
|
}
|
|
1532
|
-
function normalizePosix(
|
|
1533
|
-
const parts =
|
|
1701
|
+
function normalizePosix(path17) {
|
|
1702
|
+
const parts = path17.split("/").reduce((acc, part) => {
|
|
1534
1703
|
if (part === "..") acc.pop();
|
|
1535
1704
|
else if (part && part !== ".") acc.push(part);
|
|
1536
1705
|
return acc;
|
|
1537
1706
|
}, []);
|
|
1538
|
-
return (
|
|
1707
|
+
return (path17.startsWith("/") ? "/" : "") + parts.join("/");
|
|
1539
1708
|
}
|
|
1540
1709
|
function toAbsolutePath(rawSource, outDir, sourceRoot) {
|
|
1541
1710
|
if (rawSource.startsWith("file://")) return rawSource.slice(7);
|
|
@@ -1543,8 +1712,8 @@ function toAbsolutePath(rawSource, outDir, sourceRoot) {
|
|
|
1543
1712
|
const base = sourceRoot ? normalizePosix(`${outDir}/${sourceRoot}`) : outDir;
|
|
1544
1713
|
return normalizePosix(`${base}/${rawSource}`);
|
|
1545
1714
|
}
|
|
1546
|
-
function isNodeModulesPath(
|
|
1547
|
-
return
|
|
1715
|
+
function isNodeModulesPath(path17) {
|
|
1716
|
+
return path17.includes("/node_modules/") || path17.includes("\\node_modules\\");
|
|
1548
1717
|
}
|
|
1549
1718
|
function makeDisplayPath(absolutePath, projectRoot) {
|
|
1550
1719
|
const prefix = projectRoot + "/";
|
|
@@ -1820,8 +1989,8 @@ var init_web = __esm({
|
|
|
1820
1989
|
});
|
|
1821
1990
|
}
|
|
1822
1991
|
/** Registers a GET route handler. */
|
|
1823
|
-
get(
|
|
1824
|
-
this.#registerRouteHandler("GET",
|
|
1992
|
+
get(path17, handler) {
|
|
1993
|
+
this.#registerRouteHandler("GET", path17, handler);
|
|
1825
1994
|
}
|
|
1826
1995
|
/**
|
|
1827
1996
|
* Starts listening on the given port (0 = OS-assigned).
|
|
@@ -1852,32 +2021,32 @@ var init_web = __esm({
|
|
|
1852
2021
|
});
|
|
1853
2022
|
}
|
|
1854
2023
|
/** Registers a POST route handler. */
|
|
1855
|
-
post(
|
|
1856
|
-
this.#registerRouteHandler("POST",
|
|
2024
|
+
post(path17, handler) {
|
|
2025
|
+
this.#registerRouteHandler("POST", path17, handler);
|
|
1857
2026
|
}
|
|
1858
2027
|
/** Registers a DELETE route handler. */
|
|
1859
|
-
delete(
|
|
1860
|
-
this.#registerRouteHandler("DELETE",
|
|
2028
|
+
delete(path17, handler) {
|
|
2029
|
+
this.#registerRouteHandler("DELETE", path17, handler);
|
|
1861
2030
|
}
|
|
1862
2031
|
/** Registers a PUT route handler. */
|
|
1863
|
-
put(
|
|
1864
|
-
this.#registerRouteHandler("PUT",
|
|
2032
|
+
put(path17, handler) {
|
|
2033
|
+
this.#registerRouteHandler("PUT", path17, handler);
|
|
1865
2034
|
}
|
|
1866
2035
|
/** Adds a middleware function to the chain. */
|
|
1867
2036
|
use(middleware) {
|
|
1868
2037
|
this.middleware.push(middleware);
|
|
1869
2038
|
}
|
|
1870
|
-
#registerRouteHandler(method,
|
|
2039
|
+
#registerRouteHandler(method, path17, handler) {
|
|
1871
2040
|
if (!this.routes[method]) {
|
|
1872
2041
|
this.routes[method] = {};
|
|
1873
2042
|
}
|
|
1874
|
-
const paramNames = this.#extractParamNames(
|
|
1875
|
-
this.routes[method][
|
|
1876
|
-
path:
|
|
2043
|
+
const paramNames = this.#extractParamNames(path17);
|
|
2044
|
+
this.routes[method][path17] = {
|
|
2045
|
+
path: path17,
|
|
1877
2046
|
handler,
|
|
1878
2047
|
paramNames,
|
|
1879
|
-
isWildcard:
|
|
1880
|
-
compiledRegex: paramNames.length > 0 ? new RegExp(`^${this.#buildRegexPattern(
|
|
2048
|
+
isWildcard: path17 === "/*",
|
|
2049
|
+
compiledRegex: paramNames.length > 0 ? new RegExp(`^${this.#buildRegexPattern(path17, paramNames)}$`) : null
|
|
1881
2050
|
};
|
|
1882
2051
|
}
|
|
1883
2052
|
#handleRequest(req, res) {
|
|
@@ -1915,11 +2084,11 @@ var init_web = __esm({
|
|
|
1915
2084
|
return null;
|
|
1916
2085
|
}
|
|
1917
2086
|
return routes[url] || Object.values(routes).find((route) => {
|
|
1918
|
-
const { path:
|
|
1919
|
-
if (!isWildcard && !
|
|
2087
|
+
const { path: path17, isWildcard } = route;
|
|
2088
|
+
if (!isWildcard && !path17.includes(":")) {
|
|
1920
2089
|
return false;
|
|
1921
2090
|
}
|
|
1922
|
-
if (isWildcard || this.#matchPathSegments(
|
|
2091
|
+
if (isWildcard || this.#matchPathSegments(path17, url)) {
|
|
1923
2092
|
if (route.compiledRegex) {
|
|
1924
2093
|
const regexMatches = route.compiledRegex.exec(url);
|
|
1925
2094
|
if (regexMatches) {
|
|
@@ -1931,8 +2100,8 @@ var init_web = __esm({
|
|
|
1931
2100
|
return false;
|
|
1932
2101
|
}) || null;
|
|
1933
2102
|
}
|
|
1934
|
-
#matchPathSegments(
|
|
1935
|
-
const pathSegments =
|
|
2103
|
+
#matchPathSegments(path17, url) {
|
|
2104
|
+
const pathSegments = path17.split("/");
|
|
1936
2105
|
const urlSegments = url.split("/");
|
|
1937
2106
|
if (pathSegments.length !== urlSegments.length) {
|
|
1938
2107
|
return false;
|
|
@@ -1949,14 +2118,14 @@ var init_web = __esm({
|
|
|
1949
2118
|
}
|
|
1950
2119
|
return true;
|
|
1951
2120
|
}
|
|
1952
|
-
#buildRegexPattern(
|
|
1953
|
-
let regexPattern =
|
|
2121
|
+
#buildRegexPattern(path17, _paramNames) {
|
|
2122
|
+
let regexPattern = path17.replace(/:[^/]+/g, "([^/]+)");
|
|
1954
2123
|
regexPattern = regexPattern.replace(/\//g, "\\/");
|
|
1955
2124
|
return regexPattern;
|
|
1956
2125
|
}
|
|
1957
|
-
#extractParamNames(
|
|
2126
|
+
#extractParamNames(path17) {
|
|
1958
2127
|
const paramRegex = /:(\w+)/g;
|
|
1959
|
-
const paramMatches =
|
|
2128
|
+
const paramMatches = path17.match(paramRegex);
|
|
1960
2129
|
return paramMatches ? paramMatches.map((match) => match.slice(1)) : [];
|
|
1961
2130
|
}
|
|
1962
2131
|
#extractParams(route, _url) {
|
|
@@ -1972,10 +2141,10 @@ var init_web = __esm({
|
|
|
1972
2141
|
});
|
|
1973
2142
|
|
|
1974
2143
|
// lib/setup/web-server.ts
|
|
1975
|
-
import
|
|
1976
|
-
import
|
|
2144
|
+
import fs10 from "node:fs";
|
|
2145
|
+
import path9 from "node:path";
|
|
1977
2146
|
function setupWebServer(config, cachedContent) {
|
|
1978
|
-
const STATIC_FILES_PATH =
|
|
2147
|
+
const STATIC_FILES_PATH = path9.resolve(config.projectRoot, config.output);
|
|
1979
2148
|
const server = new HTTPServer();
|
|
1980
2149
|
const mainHTMLWithReplacedAssets = replaceAssetPaths(
|
|
1981
2150
|
cachedContent.mainHTML.html,
|
|
@@ -2113,7 +2282,7 @@ function setupWebServer(config, cachedContent) {
|
|
|
2113
2282
|
config._testRunDone = null;
|
|
2114
2283
|
}
|
|
2115
2284
|
return saveHTML(
|
|
2116
|
-
|
|
2285
|
+
path9.join(path9.resolve(config.projectRoot, config.output), "index.html"),
|
|
2117
2286
|
htmlContent
|
|
2118
2287
|
);
|
|
2119
2288
|
}
|
|
@@ -2124,7 +2293,7 @@ function setupWebServer(config, cachedContent) {
|
|
|
2124
2293
|
res.writeHead(200, HTML_HEADERS);
|
|
2125
2294
|
res.end(mainIndexHTML);
|
|
2126
2295
|
saveHTML(
|
|
2127
|
-
|
|
2296
|
+
path9.join(path9.resolve(config.projectRoot, config.output), "index.html"),
|
|
2128
2297
|
mainIndexHTML
|
|
2129
2298
|
);
|
|
2130
2299
|
});
|
|
@@ -2134,7 +2303,7 @@ function setupWebServer(config, cachedContent) {
|
|
|
2134
2303
|
res.writeHead(200, HTML_HEADERS);
|
|
2135
2304
|
res.end(htmlContent);
|
|
2136
2305
|
return saveHTML(
|
|
2137
|
-
|
|
2306
|
+
path9.join(path9.resolve(config.projectRoot, config.output), "qunitx.html"),
|
|
2138
2307
|
htmlContent
|
|
2139
2308
|
);
|
|
2140
2309
|
}
|
|
@@ -2145,7 +2314,7 @@ function setupWebServer(config, cachedContent) {
|
|
|
2145
2314
|
res.writeHead(200, HTML_HEADERS);
|
|
2146
2315
|
res.end(mainQunitxHTML);
|
|
2147
2316
|
saveHTML(
|
|
2148
|
-
|
|
2317
|
+
path9.join(path9.resolve(config.projectRoot, config.output), "qunitx.html"),
|
|
2149
2318
|
mainQunitxHTML
|
|
2150
2319
|
);
|
|
2151
2320
|
});
|
|
@@ -2159,14 +2328,14 @@ function setupWebServer(config, cachedContent) {
|
|
|
2159
2328
|
);
|
|
2160
2329
|
res.writeHead(200, HTML_HEADERS);
|
|
2161
2330
|
res.end(htmlContent);
|
|
2162
|
-
saveHTML(
|
|
2331
|
+
saveHTML(path9.join(path9.resolve(config.projectRoot, config.output), req.path), htmlContent);
|
|
2163
2332
|
return;
|
|
2164
2333
|
}
|
|
2165
2334
|
const url = req.url;
|
|
2166
2335
|
const requestStartedAt = Date.now();
|
|
2167
2336
|
const filePath = (url.endsWith("/") ? [STATIC_FILES_PATH, url, "index.html"] : [STATIC_FILES_PATH, url]).join("");
|
|
2168
|
-
const contentType = req.headers.accept?.includes("text/html") ? MIME_TYPES.html : MIME_TYPES[
|
|
2169
|
-
const stream =
|
|
2337
|
+
const contentType = req.headers.accept?.includes("text/html") ? MIME_TYPES.html : MIME_TYPES[path9.extname(filePath).substring(1).toLowerCase()] || MIME_TYPES.html;
|
|
2338
|
+
const stream = fs10.createReadStream(filePath);
|
|
2170
2339
|
stream.on("open", () => {
|
|
2171
2340
|
res.writeHead(200, { "Content-Type": contentType });
|
|
2172
2341
|
stream.pipe(res);
|
|
@@ -2419,7 +2588,7 @@ function registerGroupRoutes(server, groupConfig, groupCachedContent, groupId) {
|
|
|
2419
2588
|
res.writeHead(200, HTML_HEADERS);
|
|
2420
2589
|
res.end(mainGroupHTML);
|
|
2421
2590
|
saveHTML(
|
|
2422
|
-
|
|
2591
|
+
path9.join(path9.resolve(groupConfig.projectRoot, groupConfig.output), "index.html"),
|
|
2423
2592
|
mainGroupHTML
|
|
2424
2593
|
);
|
|
2425
2594
|
});
|
|
@@ -2505,11 +2674,11 @@ function registerSharedStaticHandler(server, groupConfigs) {
|
|
|
2505
2674
|
res.end("Not found");
|
|
2506
2675
|
return;
|
|
2507
2676
|
}
|
|
2508
|
-
const STATIC_FILES_PATH =
|
|
2677
|
+
const STATIC_FILES_PATH = path9.resolve(groupConfig.projectRoot, groupConfig.output);
|
|
2509
2678
|
const subPath = match[2] || "/";
|
|
2510
2679
|
const filePath = (subPath.endsWith("/") ? [STATIC_FILES_PATH, subPath, "index.html"] : [STATIC_FILES_PATH, subPath]).join("");
|
|
2511
|
-
const contentType = req.headers.accept?.includes("text/html") ? MIME_TYPES.html : MIME_TYPES[
|
|
2512
|
-
const stream =
|
|
2680
|
+
const contentType = req.headers.accept?.includes("text/html") ? MIME_TYPES.html : MIME_TYPES[path9.extname(filePath).substring(1).toLowerCase()] || MIME_TYPES.html;
|
|
2681
|
+
const stream = fs10.createReadStream(filePath);
|
|
2513
2682
|
stream.on("open", () => {
|
|
2514
2683
|
res.writeHead(200, { "Content-Type": contentType });
|
|
2515
2684
|
stream.pipe(res);
|
|
@@ -2524,7 +2693,7 @@ function replaceAssetPaths(html, htmlPath, projectRoot) {
|
|
|
2524
2693
|
const assetPaths = findInternalAssetsFromHTML(html);
|
|
2525
2694
|
const htmlDirectory = htmlPath.split("/").slice(0, -1).join("/");
|
|
2526
2695
|
return assetPaths.reduce((result2, assetPath) => {
|
|
2527
|
-
const normalizedFullAbsolutePath =
|
|
2696
|
+
const normalizedFullAbsolutePath = path9.normalize(`${htmlDirectory}/${assetPath}`);
|
|
2528
2697
|
return result2.replace(assetPath, normalizedFullAbsolutePath.replace(projectRoot, "."));
|
|
2529
2698
|
}, html);
|
|
2530
2699
|
}
|
|
@@ -2686,7 +2855,7 @@ var init_web_server = __esm({
|
|
|
2686
2855
|
init_display_test_result();
|
|
2687
2856
|
init_color();
|
|
2688
2857
|
init_web();
|
|
2689
|
-
fsPromise =
|
|
2858
|
+
fsPromise = fs10.promises;
|
|
2690
2859
|
HTML_HEADERS = { "Content-Type": "text/html", "Cache-Control": "no-store" };
|
|
2691
2860
|
WATCH_WS_RECONNECT_INTERVAL_MS = 1e3;
|
|
2692
2861
|
WATCH_WS_RECONNECT_MAX_RETRIES = 120;
|
|
@@ -2905,8 +3074,8 @@ var init_display_final_result = __esm({
|
|
|
2905
3074
|
});
|
|
2906
3075
|
|
|
2907
3076
|
// lib/commands/run/tests-in-browser.ts
|
|
2908
|
-
import
|
|
2909
|
-
import
|
|
3077
|
+
import fs11 from "node:fs/promises";
|
|
3078
|
+
import path10 from "node:path";
|
|
2910
3079
|
import esbuild from "esbuild";
|
|
2911
3080
|
function deriveBuildErrorType(error) {
|
|
2912
3081
|
const msgs = error?.errors ?? [];
|
|
@@ -2946,9 +3115,9 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
2946
3115
|
console.log("# [buildTestBundle] fsTree is empty \u2014 skipping build (no test files found)");
|
|
2947
3116
|
return;
|
|
2948
3117
|
}
|
|
2949
|
-
const outDir =
|
|
2950
|
-
const outfile =
|
|
2951
|
-
await
|
|
3118
|
+
const outDir = path10.resolve(projectRoot, output);
|
|
3119
|
+
const outfile = path10.join(outDir, "tests.js");
|
|
3120
|
+
await fs11.mkdir(outDir, { recursive: true });
|
|
2952
3121
|
const sourcemap = "inline";
|
|
2953
3122
|
const needsDisk = true;
|
|
2954
3123
|
const buildOptions = {
|
|
@@ -2972,6 +3141,10 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
2972
3141
|
// tsconfig's `jsxImportSource` or a `@jsxImportSource <pkg>` pragma cover Vue/Preact/Solid.
|
|
2973
3142
|
jsx: "automatic",
|
|
2974
3143
|
plugins: config.plugins,
|
|
3144
|
+
// Required for --changed/--since dep-graph filter on subsequent runs (cache
|
|
3145
|
+
// populates here, reads in setupConfig). Inputs map carries the full reverse-dep
|
|
3146
|
+
// graph; output cost is negligible.
|
|
3147
|
+
metafile: true,
|
|
2975
3148
|
// Signal the runtime that all test modules are registered. The runtime's maybeStart()
|
|
2976
3149
|
// waits for both this event and the WebSocket 'open' event before calling QUnit.start().
|
|
2977
3150
|
// Dispatching from the bundle (rather than from a script onload attr) is reliable across
|
|
@@ -2983,32 +3156,33 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
2983
3156
|
const cacheHolder = config._daemonEsbuildCache ?? cachedContent;
|
|
2984
3157
|
const fileKey = bundleCacheKey(buildOptions, allTestFilePaths);
|
|
2985
3158
|
try {
|
|
2986
|
-
const [allTestCode] = await Promise.all([
|
|
3159
|
+
const [{ js: allTestCode, metafile }] = await Promise.all([
|
|
2987
3160
|
config.watch || config._daemonMode ? buildIncrementally(buildOptions, fileKey, cacheHolder, needsDisk) : buildWithOverlayfsRetry(buildOptions, needsDisk),
|
|
2988
3161
|
Promise.all(
|
|
2989
3162
|
cachedContent.htmlPathsToRunTests.map(async (htmlPath) => {
|
|
2990
|
-
const targetPath =
|
|
3163
|
+
const targetPath = path10.join(outDir, htmlPath);
|
|
2991
3164
|
if (htmlPath !== "/") {
|
|
2992
|
-
await
|
|
2993
|
-
await
|
|
3165
|
+
await fs11.rm(targetPath, { force: true, recursive: true });
|
|
3166
|
+
await fs11.mkdir(path10.dirname(targetPath), { recursive: true });
|
|
2994
3167
|
}
|
|
2995
3168
|
})
|
|
2996
3169
|
)
|
|
2997
3170
|
]);
|
|
2998
3171
|
cachedContent.allTestCode = allTestCode;
|
|
2999
3172
|
config._sourceMapDecoder = extractInlineSourceMap(allTestCode, outDir);
|
|
3173
|
+
if (metafile) void writeMetafileCache(projectRoot, process.cwd(), metafile);
|
|
3000
3174
|
} catch (error) {
|
|
3001
3175
|
cachedContent._buildError = {
|
|
3002
3176
|
type: deriveBuildErrorType(error),
|
|
3003
3177
|
formatted: formatBuildErrors(error)
|
|
3004
3178
|
};
|
|
3005
|
-
await
|
|
3179
|
+
await fs11.writeFile(path10.join(outDir, "index.html"), buildErrorHTML(cachedContent._buildError));
|
|
3006
3180
|
throw error;
|
|
3007
3181
|
}
|
|
3008
3182
|
}
|
|
3009
3183
|
async function runTestsInBrowser(config, cachedContent = {}, connections, targetTestFilesToFilter) {
|
|
3010
3184
|
const { projectRoot, output } = config;
|
|
3011
|
-
const outDir =
|
|
3185
|
+
const outDir = path10.resolve(projectRoot, output);
|
|
3012
3186
|
const allTestFilePaths = Object.keys(config.fsTree);
|
|
3013
3187
|
const runHasFilter = !!targetTestFilesToFilter;
|
|
3014
3188
|
if (!config._groupMode) {
|
|
@@ -3036,7 +3210,7 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
3036
3210
|
return connections;
|
|
3037
3211
|
}
|
|
3038
3212
|
if (runHasFilter) {
|
|
3039
|
-
const outputPath =
|
|
3213
|
+
const outputPath = path10.join(outDir, "filtered-tests.js");
|
|
3040
3214
|
cachedContent.filteredTestCode = await buildFilteredTests(
|
|
3041
3215
|
targetTestFilesToFilter,
|
|
3042
3216
|
outputPath,
|
|
@@ -3076,7 +3250,7 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
3076
3250
|
console.log(
|
|
3077
3251
|
`# Warning: 0 tests registered \u2014 no QUnit test cases found in ${allTestFilePaths.length} ${fileWord}`
|
|
3078
3252
|
);
|
|
3079
|
-
|
|
3253
|
+
fs11.writeFile(path10.join(outDir, "index.html"), buildNoTestsHTML(displayFiles)).catch(
|
|
3080
3254
|
() => {
|
|
3081
3255
|
}
|
|
3082
3256
|
);
|
|
@@ -3108,8 +3282,8 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
3108
3282
|
type: deriveBuildErrorType(error),
|
|
3109
3283
|
formatted: formatBuildErrors(error)
|
|
3110
3284
|
};
|
|
3111
|
-
|
|
3112
|
-
|
|
3285
|
+
fs11.writeFile(
|
|
3286
|
+
path10.join(outDir, "qunitx.html"),
|
|
3113
3287
|
buildErrorHTML(cachedContent._buildError)
|
|
3114
3288
|
).catch(
|
|
3115
3289
|
(err) => config.debug && process.stderr.write(`# [qunitx] writeFile qunitx.html: ${err.message}
|
|
@@ -3159,7 +3333,7 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
3159
3333
|
);
|
|
3160
3334
|
await Promise.all(
|
|
3161
3335
|
activeGroups.map(
|
|
3162
|
-
(group) =>
|
|
3336
|
+
(group) => fs11.mkdir(path10.resolve(group.config.projectRoot, group.config.output), { recursive: true })
|
|
3163
3337
|
)
|
|
3164
3338
|
);
|
|
3165
3339
|
const sourcemap = "inline";
|
|
@@ -3179,7 +3353,7 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
3179
3353
|
});
|
|
3180
3354
|
}
|
|
3181
3355
|
};
|
|
3182
|
-
const esbuildOutdir =
|
|
3356
|
+
const esbuildOutdir = path10.join(projectRoot, "tmp");
|
|
3183
3357
|
const buildOptions = {
|
|
3184
3358
|
entryPoints: activeGroups.map((_, slotIndex) => ({
|
|
3185
3359
|
in: `group-entry-${slotIndex}`,
|
|
@@ -3201,6 +3375,9 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
3201
3375
|
sourcemap,
|
|
3202
3376
|
write: false,
|
|
3203
3377
|
jsx: "automatic",
|
|
3378
|
+
// Required for --changed/--since dep-graph filter on subsequent runs. Same
|
|
3379
|
+
// contract as the single-group path in `buildTestBundle`.
|
|
3380
|
+
metafile: true,
|
|
3204
3381
|
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
3205
3382
|
};
|
|
3206
3383
|
const hasSmallOutput = (result2) => (result2.outputFiles ?? []).some(
|
|
@@ -3221,8 +3398,8 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
3221
3398
|
const slotIndex = parseInt(match[1]);
|
|
3222
3399
|
const isMap = Boolean(match[2]);
|
|
3223
3400
|
const { config, cachedContent } = activeGroups[slotIndex];
|
|
3224
|
-
const destPath =
|
|
3225
|
-
|
|
3401
|
+
const destPath = path10.join(
|
|
3402
|
+
path10.resolve(config.projectRoot, config.output),
|
|
3226
3403
|
"tests.js" + (isMap ? ".map" : "")
|
|
3227
3404
|
);
|
|
3228
3405
|
if (!isMap) {
|
|
@@ -3232,17 +3409,20 @@ async function buildAllGroupBundles(groupConfigs, groupCachedContents) {
|
|
|
3232
3409
|
esbuildOutdir
|
|
3233
3410
|
);
|
|
3234
3411
|
}
|
|
3235
|
-
return
|
|
3412
|
+
return fs11.writeFile(destPath, outputFile.contents);
|
|
3236
3413
|
})
|
|
3237
3414
|
);
|
|
3415
|
+
if (result2.metafile) {
|
|
3416
|
+
void writeMetafileCache(projectRoot, process.cwd(), result2.metafile);
|
|
3417
|
+
}
|
|
3238
3418
|
} catch (error) {
|
|
3239
3419
|
const buildError = { type: deriveBuildErrorType(error), formatted: formatBuildErrors(error) };
|
|
3240
3420
|
const errorHtml = buildErrorHTML(buildError);
|
|
3241
3421
|
await Promise.all(
|
|
3242
3422
|
activeGroups.map((group) => {
|
|
3243
3423
|
group.cachedContent._buildError = buildError;
|
|
3244
|
-
return
|
|
3245
|
-
|
|
3424
|
+
return fs11.writeFile(
|
|
3425
|
+
path10.join(path10.resolve(group.config.projectRoot, group.config.output), "index.html"),
|
|
3246
3426
|
errorHtml
|
|
3247
3427
|
).catch(
|
|
3248
3428
|
(err) => debug && process.stderr.write(`# [qunitx] writeFile index.html: ${err.message}
|
|
@@ -3274,7 +3454,7 @@ function buildFilteredTests(filteredTests, outputPath, config) {
|
|
|
3274
3454
|
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
3275
3455
|
},
|
|
3276
3456
|
needsDisk
|
|
3277
|
-
);
|
|
3457
|
+
).then((r) => r.js);
|
|
3278
3458
|
}
|
|
3279
3459
|
async function runWithOverlayfsRetry(getContents, needsDisk) {
|
|
3280
3460
|
let { result: result2, js } = await getContents();
|
|
@@ -3291,10 +3471,10 @@ async function runWithOverlayfsRetry(getContents, needsDisk) {
|
|
|
3291
3471
|
}
|
|
3292
3472
|
if (needsDisk) {
|
|
3293
3473
|
await Promise.all(
|
|
3294
|
-
result2.outputFiles.map((outputFile) =>
|
|
3474
|
+
result2.outputFiles.map((outputFile) => fs11.writeFile(outputFile.path, outputFile.contents))
|
|
3295
3475
|
);
|
|
3296
3476
|
}
|
|
3297
|
-
return js;
|
|
3477
|
+
return { js, metafile: result2.metafile };
|
|
3298
3478
|
}
|
|
3299
3479
|
function buildWithOverlayfsRetry(options, needsDisk) {
|
|
3300
3480
|
const buildOpts = { ...options, write: false };
|
|
@@ -3428,9 +3608,9 @@ async function failOnNonWatchMode(watchMode = false, connections = {}, groupMode
|
|
|
3428
3608
|
process.exit(1);
|
|
3429
3609
|
}
|
|
3430
3610
|
function toEsbuildImportPath(filePath) {
|
|
3431
|
-
const rel =
|
|
3611
|
+
const rel = path10.relative(process.cwd(), filePath);
|
|
3432
3612
|
const normalized = rel.replace(/\\/g, "/");
|
|
3433
|
-
if (
|
|
3613
|
+
if (path10.isAbsolute(rel)) return filePath.replace(/\\/g, "/");
|
|
3434
3614
|
return normalized.startsWith(".") ? normalized : "./" + normalized;
|
|
3435
3615
|
}
|
|
3436
3616
|
var ancestorNodeModules, ANCESTOR_NODE_MODULES, RETRY_DELAY_MS, MAX_RETRIES, EMPTY_BUNDLE_THRESHOLD, NAV_GRACE_MS, MAX_NAV_SLOWDOWN_FACTOR, MIN_NAV_MS, STARTUP_TIMEOUT_FACTOR, TESTS_JS_TIMEOUT_FACTOR, CONSOLE_FLUSH_TIMEOUT_MS, TEST_STALL_BUFFER_MS, GROUP_OUTPUT_REGEX, BundleError, DaemonRunError;
|
|
@@ -3444,8 +3624,9 @@ var init_tests_in_browser = __esm({
|
|
|
3444
3624
|
init_display_final_result();
|
|
3445
3625
|
init_web_server();
|
|
3446
3626
|
init_source_map_decoder();
|
|
3447
|
-
|
|
3448
|
-
|
|
3627
|
+
init_metafile_cache();
|
|
3628
|
+
ancestorNodeModules = (dir) => dir.split(path10.sep).map(
|
|
3629
|
+
(_, i, parts) => path10.join(parts.slice(0, parts.length - i).join(path10.sep) || path10.sep, "node_modules")
|
|
3449
3630
|
);
|
|
3450
3631
|
ANCESTOR_NODE_MODULES = ancestorNodeModules(process.cwd());
|
|
3451
3632
|
RETRY_DELAY_MS = 100;
|
|
@@ -3481,11 +3662,11 @@ var init_tests_in_browser = __esm({
|
|
|
3481
3662
|
|
|
3482
3663
|
// lib/utils/open-output-in-browser.ts
|
|
3483
3664
|
import { spawn as spawn2 } from "node:child_process";
|
|
3484
|
-
import
|
|
3665
|
+
import path11 from "node:path";
|
|
3485
3666
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
3486
3667
|
async function openOutputInBrowser(config) {
|
|
3487
3668
|
try {
|
|
3488
|
-
const outputFile = config.watch ? `http://localhost:${config.port}` : pathToFileURL3(
|
|
3669
|
+
const outputFile = config.watch ? `http://localhost:${config.port}` : pathToFileURL3(path11.join(path11.resolve(config.projectRoot, config.output), "index.html")).href;
|
|
3489
3670
|
if (typeof config.open === "string") {
|
|
3490
3671
|
spawnDetached(config.open, [outputFile]);
|
|
3491
3672
|
return;
|
|
@@ -3518,9 +3699,9 @@ var init_open_output_in_browser = __esm({
|
|
|
3518
3699
|
});
|
|
3519
3700
|
|
|
3520
3701
|
// lib/setup/file-watcher.ts
|
|
3521
|
-
import
|
|
3702
|
+
import fs12 from "node:fs";
|
|
3522
3703
|
import { readdir, stat, lstat } from "node:fs/promises";
|
|
3523
|
-
import
|
|
3704
|
+
import path12 from "node:path";
|
|
3524
3705
|
function recordJustAdded(config, filePath) {
|
|
3525
3706
|
let map = justAddedAt.get(config);
|
|
3526
3707
|
if (!map) justAddedAt.set(config, map = /* @__PURE__ */ new Map());
|
|
@@ -3539,19 +3720,27 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
|
|
|
3539
3720
|
if (symlinkPollers.has(filePath)) return;
|
|
3540
3721
|
const handler = (curr, prev) => {
|
|
3541
3722
|
if (curr.nlink === 0) {
|
|
3542
|
-
|
|
3723
|
+
fs12.unwatchFile(filePath, handler);
|
|
3543
3724
|
symlinkPollers.delete(filePath);
|
|
3544
3725
|
if (filePath in config.fsTree) {
|
|
3545
3726
|
handleWatchEvent(config, extensions, "unlink", filePath, onEventFunc, onFinishFunc);
|
|
3546
3727
|
}
|
|
3547
3728
|
} else if ((process.platform === "win32" || process.platform === "darwin") && curr.mtimeMs !== prev.mtimeMs) {
|
|
3548
3729
|
if (filePath in config.fsTree) {
|
|
3549
|
-
|
|
3730
|
+
const existing = pendingChangeTimers.get(filePath);
|
|
3731
|
+
if (existing) clearTimeout(existing);
|
|
3732
|
+
pendingChangeTimers.set(
|
|
3733
|
+
filePath,
|
|
3734
|
+
setTimeout(() => {
|
|
3735
|
+
pendingChangeTimers.delete(filePath);
|
|
3736
|
+
handleWatchEvent(config, extensions, "change", filePath, onEventFunc, onFinishFunc);
|
|
3737
|
+
}, CHANGE_COALESCE_MS)
|
|
3738
|
+
);
|
|
3550
3739
|
}
|
|
3551
3740
|
}
|
|
3552
3741
|
};
|
|
3553
|
-
|
|
3554
|
-
symlinkPollers.set(filePath, () =>
|
|
3742
|
+
fs12.watchFile(filePath, { interval: SYMLINK_POLL_INTERVAL_MS, persistent: false }, handler);
|
|
3743
|
+
symlinkPollers.set(filePath, () => fs12.unwatchFile(filePath, handler));
|
|
3555
3744
|
}
|
|
3556
3745
|
function untrackSymlink(filePath) {
|
|
3557
3746
|
symlinkPollers.get(filePath)?.();
|
|
@@ -3562,7 +3751,7 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
|
|
|
3562
3751
|
let rescanInProgress = false;
|
|
3563
3752
|
const lastEventMs = {};
|
|
3564
3753
|
const seenMtimeMs = {};
|
|
3565
|
-
const childWatcher =
|
|
3754
|
+
const childWatcher = fs12.watch(watchPath, { recursive: true }, async (eventType, filename) => {
|
|
3566
3755
|
if (!ready) return;
|
|
3567
3756
|
if (!filename) {
|
|
3568
3757
|
if (process.platform === "darwin" && !rescanInProgress) {
|
|
@@ -3580,7 +3769,7 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
|
|
|
3580
3769
|
}
|
|
3581
3770
|
return;
|
|
3582
3771
|
}
|
|
3583
|
-
const fullPath = filename ===
|
|
3772
|
+
const fullPath = filename === path12.basename(watchPath) ? watchPath : path12.join(watchPath, filename);
|
|
3584
3773
|
if (eventType === "change") {
|
|
3585
3774
|
const now = Date.now();
|
|
3586
3775
|
const last = lastEventMs[fullPath] ?? 0;
|
|
@@ -3618,8 +3807,8 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
|
|
|
3618
3807
|
}
|
|
3619
3808
|
handleWatchEvent(config, extensions, event, fullPath, onEventFunc, onFinishFunc);
|
|
3620
3809
|
});
|
|
3621
|
-
const parentDir =
|
|
3622
|
-
const watchedBasename =
|
|
3810
|
+
const parentDir = path12.dirname(watchPath);
|
|
3811
|
+
const watchedBasename = path12.basename(watchPath);
|
|
3623
3812
|
let parentUnlinkFired = false;
|
|
3624
3813
|
let rescanTimer = null;
|
|
3625
3814
|
const tryFireParentUnlink = async () => {
|
|
@@ -3638,7 +3827,7 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
|
|
|
3638
3827
|
return true;
|
|
3639
3828
|
}
|
|
3640
3829
|
};
|
|
3641
|
-
const parentWatcher =
|
|
3830
|
+
const parentWatcher = fs12.watch(parentDir, async (eventType, filename) => {
|
|
3642
3831
|
if (!ready || filename !== watchedBasename || eventType !== "rename") return;
|
|
3643
3832
|
await tryFireParentUnlink();
|
|
3644
3833
|
});
|
|
@@ -3752,11 +3941,11 @@ async function rescanDirectoryForDelta(watchPath, config, extensions, onEventFun
|
|
|
3752
3941
|
const trackedToRecheck = [];
|
|
3753
3942
|
for (const entry of entries) {
|
|
3754
3943
|
if (entry.isDirectory()) {
|
|
3755
|
-
presentDirs.add(
|
|
3944
|
+
presentDirs.add(path12.join(entry.parentPath, entry.name));
|
|
3756
3945
|
continue;
|
|
3757
3946
|
}
|
|
3758
3947
|
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
3759
|
-
const entryPath =
|
|
3948
|
+
const entryPath = path12.join(entry.parentPath, entry.name);
|
|
3760
3949
|
presentDirs.add(entry.parentPath);
|
|
3761
3950
|
if (!extensions.some((ext) => entryPath.endsWith(`.${ext}`))) continue;
|
|
3762
3951
|
presentPaths.add(entryPath);
|
|
@@ -3779,13 +3968,13 @@ async function rescanDirectoryForDelta(watchPath, config, extensions, onEventFun
|
|
|
3779
3968
|
}
|
|
3780
3969
|
})
|
|
3781
3970
|
);
|
|
3782
|
-
const watchPrefix = watchPath +
|
|
3971
|
+
const watchPrefix = watchPath + path12.sep;
|
|
3783
3972
|
const firedDirPrefixes = [];
|
|
3784
3973
|
for (const trackedPath of Object.keys(config.fsTree)) {
|
|
3785
3974
|
if (!trackedPath.startsWith(watchPrefix) || presentPaths.has(trackedPath)) continue;
|
|
3786
|
-
if (firedDirPrefixes.some((p) => trackedPath.startsWith(p +
|
|
3787
|
-
const parts = trackedPath.slice(watchPrefix.length).split(
|
|
3788
|
-
const goneDirPath = parts.slice(0, -1).map((_, i) => watchPrefix + parts.slice(0, i + 1).join(
|
|
3975
|
+
if (firedDirPrefixes.some((p) => trackedPath.startsWith(p + path12.sep))) continue;
|
|
3976
|
+
const parts = trackedPath.slice(watchPrefix.length).split(path12.sep);
|
|
3977
|
+
const goneDirPath = parts.slice(0, -1).map((_, i) => watchPrefix + parts.slice(0, i + 1).join(path12.sep)).find((p) => !presentDirs.has(p)) ?? null;
|
|
3789
3978
|
if (goneDirPath !== null) {
|
|
3790
3979
|
firedDirPrefixes.push(goneDirPath);
|
|
3791
3980
|
handleWatchEvent(config, extensions, "unlinkDir", goneDirPath, onEventFunc, onFinishFunc);
|
|
@@ -3838,7 +4027,7 @@ var init_file_watcher = __esm({
|
|
|
3838
4027
|
SYMLINK_POLL_INTERVAL_MS = 500;
|
|
3839
4028
|
OVERLAYFS_RENAME_RETRY_MS = 50;
|
|
3840
4029
|
RESCAN_INTERVAL_MS = 1e3;
|
|
3841
|
-
CHANGE_COALESCE_MS =
|
|
4030
|
+
CHANGE_COALESCE_MS = 75;
|
|
3842
4031
|
ADD_SUPPRESS_WINDOW_MS = 1e3;
|
|
3843
4032
|
justAddedAt = /* @__PURE__ */ new WeakMap();
|
|
3844
4033
|
}
|
|
@@ -3920,28 +4109,28 @@ var init_keyboard_events = __esm({
|
|
|
3920
4109
|
});
|
|
3921
4110
|
|
|
3922
4111
|
// lib/setup/write-output-static-files.ts
|
|
3923
|
-
import
|
|
3924
|
-
import
|
|
4112
|
+
import fs13 from "node:fs/promises";
|
|
4113
|
+
import path13 from "node:path";
|
|
3925
4114
|
async function writeOutputStaticFiles({ projectRoot, output }, cachedContent) {
|
|
3926
4115
|
const staticHTMLPromises = Object.keys(cachedContent.staticHTMLs).map(async (staticHTMLKey) => {
|
|
3927
|
-
const htmlRelativePath =
|
|
3928
|
-
const outDir =
|
|
3929
|
-
await ensureFolderExists(
|
|
3930
|
-
await
|
|
3931
|
-
|
|
4116
|
+
const htmlRelativePath = path13.relative(projectRoot, staticHTMLKey);
|
|
4117
|
+
const outDir = path13.resolve(projectRoot, output);
|
|
4118
|
+
await ensureFolderExists(path13.join(outDir, htmlRelativePath));
|
|
4119
|
+
await fs13.writeFile(
|
|
4120
|
+
path13.join(outDir, htmlRelativePath),
|
|
3932
4121
|
cachedContent.staticHTMLs[staticHTMLKey]
|
|
3933
4122
|
);
|
|
3934
4123
|
});
|
|
3935
4124
|
const assetPromises = Array.from(cachedContent.assets).map(async (assetAbsolutePath) => {
|
|
3936
|
-
const assetRelativePath =
|
|
3937
|
-
const outDir =
|
|
3938
|
-
await ensureFolderExists(
|
|
3939
|
-
await
|
|
4125
|
+
const assetRelativePath = path13.relative(projectRoot, assetAbsolutePath).replace(/^(?:\.\.[\\/])+/, "");
|
|
4126
|
+
const outDir = path13.resolve(projectRoot, output);
|
|
4127
|
+
await ensureFolderExists(path13.join(outDir, assetRelativePath));
|
|
4128
|
+
await fs13.copyFile(assetAbsolutePath, path13.join(outDir, assetRelativePath));
|
|
3940
4129
|
});
|
|
3941
4130
|
await Promise.all(staticHTMLPromises.concat(assetPromises));
|
|
3942
4131
|
}
|
|
3943
4132
|
async function ensureFolderExists(assetPath) {
|
|
3944
|
-
await
|
|
4133
|
+
await fs13.mkdir(path13.dirname(assetPath), { recursive: true });
|
|
3945
4134
|
}
|
|
3946
4135
|
var init_write_output_static_files = __esm({
|
|
3947
4136
|
"lib/setup/write-output-static-files.ts"() {
|
|
@@ -3949,9 +4138,9 @@ var init_write_output_static_files = __esm({
|
|
|
3949
4138
|
});
|
|
3950
4139
|
|
|
3951
4140
|
// lib/utils/daemon-hint.ts
|
|
3952
|
-
import
|
|
4141
|
+
import fs14 from "node:fs/promises";
|
|
3953
4142
|
import os3 from "node:os";
|
|
3954
|
-
import
|
|
4143
|
+
import path14 from "node:path";
|
|
3955
4144
|
function shouldShowDaemonHint(ctx) {
|
|
3956
4145
|
const env = ctx.env ?? process.env;
|
|
3957
4146
|
if (ctx.watch) return false;
|
|
@@ -3969,14 +4158,14 @@ async function maybePrintDaemonHint(ctx, opts = {}) {
|
|
|
3969
4158
|
if (!shouldShowDaemonHint(ctx)) return;
|
|
3970
4159
|
const sentinel = opts.sentinelPath ?? DEFAULT_SENTINEL;
|
|
3971
4160
|
try {
|
|
3972
|
-
await
|
|
4161
|
+
await fs14.access(sentinel);
|
|
3973
4162
|
return;
|
|
3974
4163
|
} catch {
|
|
3975
4164
|
}
|
|
3976
4165
|
(opts.write ?? ((t) => process.stderr.write(t)))(HINT_TEXT);
|
|
3977
4166
|
try {
|
|
3978
|
-
await
|
|
3979
|
-
await
|
|
4167
|
+
await fs14.mkdir(path14.dirname(sentinel), { recursive: true });
|
|
4168
|
+
await fs14.writeFile(sentinel, (/* @__PURE__ */ new Date()).toISOString());
|
|
3980
4169
|
} catch {
|
|
3981
4170
|
}
|
|
3982
4171
|
}
|
|
@@ -3985,7 +4174,7 @@ var init_daemon_hint = __esm({
|
|
|
3985
4174
|
"lib/utils/daemon-hint.ts"() {
|
|
3986
4175
|
FAST_RUN_THRESHOLD_MS = 500;
|
|
3987
4176
|
HINT_TEXT = "\n\x1B[34m\u2139\x1B[39m Tip: export QUNITX_DAEMON=1 for ~2\xD7 faster repeated runs (qunitx daemon --help)\n";
|
|
3988
|
-
DEFAULT_SENTINEL =
|
|
4177
|
+
DEFAULT_SENTINEL = path14.join(os3.homedir(), ".cache", "qunitx", "hint-shown");
|
|
3989
4178
|
}
|
|
3990
4179
|
});
|
|
3991
4180
|
|
|
@@ -3998,7 +4187,7 @@ __export(run_exports, {
|
|
|
3998
4187
|
readTimingCache: () => readTimingCache,
|
|
3999
4188
|
run: () => run
|
|
4000
4189
|
});
|
|
4001
|
-
import
|
|
4190
|
+
import fs15 from "node:fs/promises";
|
|
4002
4191
|
import { join as join3, normalize } from "node:path";
|
|
4003
4192
|
import { createRequire as createRequire2 } from "node:module";
|
|
4004
4193
|
import { availableParallelism } from "node:os";
|
|
@@ -4085,6 +4274,21 @@ async function run(config) {
|
|
|
4085
4274
|
logWatcherAndKeyboardShortcutInfo(config, connections.server);
|
|
4086
4275
|
} else {
|
|
4087
4276
|
const allFiles = Object.keys(config.fsTree);
|
|
4277
|
+
if (allFiles.length === 0) {
|
|
4278
|
+
process.stdout.write("TAP version 13\n");
|
|
4279
|
+
process.stdout.write(
|
|
4280
|
+
`# Running 0 test files${config._daemonMode ? " (daemon)" : ""}
|
|
4281
|
+
1..0
|
|
4282
|
+
`
|
|
4283
|
+
);
|
|
4284
|
+
if (config._daemonMode) throw new DaemonRunError(0);
|
|
4285
|
+
if (!config.watch) {
|
|
4286
|
+
const browser2 = config._daemonBrowser ? null : await browserPromise;
|
|
4287
|
+
await closeWithGrace([browser2?.close(), shutdownPrelaunch()]);
|
|
4288
|
+
return process.exit(0);
|
|
4289
|
+
}
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4088
4292
|
const groupCount = Math.min(allFiles.length, availableParallelism());
|
|
4089
4293
|
const { groups, weights } = await splitIntoGroups(allFiles, groupCount, timings ?? {});
|
|
4090
4294
|
config.COUNTER = {
|
|
@@ -4242,7 +4446,7 @@ async function run(config) {
|
|
|
4242
4446
|
}
|
|
4243
4447
|
async function buildCachedContent(config, htmlPaths) {
|
|
4244
4448
|
const htmlBuffers = await Promise.all(
|
|
4245
|
-
config.htmlPaths.map((htmlPath) =>
|
|
4449
|
+
config.htmlPaths.map((htmlPath) => fs15.readFile(htmlPath).catch(() => null))
|
|
4246
4450
|
);
|
|
4247
4451
|
const cachedContent = htmlPaths.reduce(
|
|
4248
4452
|
(result2, _htmlPath, index) => {
|
|
@@ -4297,7 +4501,7 @@ async function addCachedContentMainHTML(projectRoot, cachedContent) {
|
|
|
4297
4501
|
}
|
|
4298
4502
|
async function readTimingCache(projectRoot) {
|
|
4299
4503
|
try {
|
|
4300
|
-
const parsed = JSON.parse(await
|
|
4504
|
+
const parsed = JSON.parse(await fs15.readFile(`${projectRoot}/tmp/test-timings.json`, "utf8"));
|
|
4301
4505
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
4302
4506
|
} catch {
|
|
4303
4507
|
return {};
|
|
@@ -4316,7 +4520,7 @@ function computeFileTimes(groups, weights, wallTimes) {
|
|
|
4316
4520
|
return result2;
|
|
4317
4521
|
}
|
|
4318
4522
|
async function persistTimings(fileTimes, projectRoot) {
|
|
4319
|
-
await
|
|
4523
|
+
await fs15.writeFile(
|
|
4320
4524
|
`${projectRoot}/tmp/test-timings.json`,
|
|
4321
4525
|
JSON.stringify(Object.fromEntries(fileTimes), null, 2)
|
|
4322
4526
|
);
|
|
@@ -4331,7 +4535,7 @@ ${lines.join("\n")}
|
|
|
4331
4535
|
async function splitIntoGroups(files, groupCount, timings) {
|
|
4332
4536
|
const sizes = await Promise.all(
|
|
4333
4537
|
files.map(
|
|
4334
|
-
(f) => timings[f] > 0 ? Promise.resolve(0) :
|
|
4538
|
+
(f) => timings[f] > 0 ? Promise.resolve(0) : fs15.stat(f).then((s) => s.size).catch(() => 0)
|
|
4335
4539
|
)
|
|
4336
4540
|
);
|
|
4337
4541
|
const knownRates = files.map((f, i) => ({ ms: timings[f], size: sizes[i] })).filter(({ ms, size }) => ms > 0 && size > 0);
|
|
@@ -4393,7 +4597,7 @@ var init_run = __esm({
|
|
|
4393
4597
|
init_close_with_grace();
|
|
4394
4598
|
init_daemon_hint();
|
|
4395
4599
|
WATCH_NAV_TIMEOUT_MS = 5e3;
|
|
4396
|
-
STDOUT_FLUSH_GRACE_MS =
|
|
4600
|
+
STDOUT_FLUSH_GRACE_MS = 3e4;
|
|
4397
4601
|
KEEP_ALIVE_INTERVAL_MS = 1e4;
|
|
4398
4602
|
EXIT_CODE_SIGTERM = 128 + 15;
|
|
4399
4603
|
}
|
|
@@ -4405,19 +4609,19 @@ __export(server_exports, {
|
|
|
4405
4609
|
runDaemonServer: () => runDaemonServer
|
|
4406
4610
|
});
|
|
4407
4611
|
import net2 from "node:net";
|
|
4408
|
-
import
|
|
4612
|
+
import fs16 from "node:fs";
|
|
4409
4613
|
import { writeFile, unlink, stat as stat2, chmod } from "node:fs/promises";
|
|
4410
|
-
import
|
|
4614
|
+
import path15 from "node:path";
|
|
4411
4615
|
async function runDaemonServer() {
|
|
4412
4616
|
const cwd = process.cwd();
|
|
4413
4617
|
const socketPath = daemonSocketPath(cwd);
|
|
4414
4618
|
const infoPath = daemonInfoPath(cwd);
|
|
4415
|
-
if (
|
|
4619
|
+
if (fs16.existsSync(infoPath) && await isLiveSocket(socketPath)) process.exit(0);
|
|
4416
4620
|
await unlink(socketPath).catch(() => {
|
|
4417
4621
|
});
|
|
4418
4622
|
const logPath = process.env.QUNITX_DAEMON_LOG;
|
|
4419
4623
|
if (logPath) {
|
|
4420
|
-
const log =
|
|
4624
|
+
const log = fs16.createWriteStream(logPath, { flags: "a" });
|
|
4421
4625
|
log.on("error", () => {
|
|
4422
4626
|
});
|
|
4423
4627
|
const forward = log.write.bind(log);
|
|
@@ -4543,7 +4747,7 @@ async function dispatch(req, socket, state) {
|
|
|
4543
4747
|
socket.end();
|
|
4544
4748
|
} else if (req.type === "shutdown") {
|
|
4545
4749
|
try {
|
|
4546
|
-
|
|
4750
|
+
fs16.unlinkSync(state.infoPath);
|
|
4547
4751
|
} catch {
|
|
4548
4752
|
}
|
|
4549
4753
|
writeChunk(socket, { type: "done", exitCode: 0 });
|
|
@@ -4685,7 +4889,7 @@ async function isLiveSocket(socketPath) {
|
|
|
4685
4889
|
}
|
|
4686
4890
|
async function readPkgMtime(cwd) {
|
|
4687
4891
|
try {
|
|
4688
|
-
return (await stat2(
|
|
4892
|
+
return (await stat2(path15.join(cwd, "package.json"))).mtimeMs;
|
|
4689
4893
|
} catch {
|
|
4690
4894
|
return 0;
|
|
4691
4895
|
}
|
|
@@ -4713,8 +4917,8 @@ __export(daemon_exports, {
|
|
|
4713
4917
|
runDaemonCommand: () => runDaemonCommand
|
|
4714
4918
|
});
|
|
4715
4919
|
import { spawn as spawn3 } from "node:child_process";
|
|
4716
|
-
import
|
|
4717
|
-
import
|
|
4920
|
+
import fs17, { existsSync as existsSync3 } from "node:fs";
|
|
4921
|
+
import path16 from "node:path";
|
|
4718
4922
|
async function buildDaemonSpawn() {
|
|
4719
4923
|
const sea = await import("node:sea").catch(() => null);
|
|
4720
4924
|
if (sea?.isSea()) return { bin: process.execPath, args: ["daemon", "_serve"] };
|
|
@@ -4739,15 +4943,15 @@ async function runServeMode() {
|
|
|
4739
4943
|
function waitForFile(filePath, timeoutMs) {
|
|
4740
4944
|
if (existsSync3(filePath)) return Promise.resolve(true);
|
|
4741
4945
|
return new Promise((resolve) => {
|
|
4742
|
-
const dir =
|
|
4743
|
-
const fileName =
|
|
4946
|
+
const dir = path16.dirname(filePath);
|
|
4947
|
+
const fileName = path16.basename(filePath);
|
|
4744
4948
|
const settle = (ok) => {
|
|
4745
4949
|
clearTimeout(timer);
|
|
4746
4950
|
watcher.close();
|
|
4747
4951
|
resolve(ok);
|
|
4748
4952
|
};
|
|
4749
4953
|
const timer = setTimeout(() => settle(false), timeoutMs);
|
|
4750
|
-
const watcher =
|
|
4954
|
+
const watcher = fs17.watch(dir, (_event, name) => {
|
|
4751
4955
|
if (name === fileName && existsSync3(filePath)) settle(true);
|
|
4752
4956
|
});
|
|
4753
4957
|
watcher.on("error", () => settle(false));
|