qunitx-cli 0.17.5 → 0.17.7

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.
Files changed (2) hide show
  1. package/dist/cli.js +131 -41
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -53,6 +53,76 @@ var init_kill_process_group = __esm({
53
53
  }
54
54
  });
55
55
 
56
+ // lib/utils/cleanup-browser-dir.ts
57
+ import fs from "node:fs/promises";
58
+ async function cleanupBrowserDir(dirPath) {
59
+ if (process.platform !== "linux") {
60
+ await fs.rm(dirPath, { recursive: true, force: true }).catch(() => {
61
+ });
62
+ return;
63
+ }
64
+ const dirName = dirPath.split("/").pop();
65
+ const killedPids = /* @__PURE__ */ new Set();
66
+ const procEntries = await fs.readdir("/proc").catch(() => []);
67
+ await Promise.all(
68
+ procEntries.map(async (entry) => {
69
+ if (!/^\d+$/.test(entry)) return;
70
+ const pid = parseInt(entry);
71
+ try {
72
+ const [cwd, cmdline] = await Promise.all([
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
+ }
82
+ } catch {
83
+ }
84
+ })
85
+ );
86
+ while (killedPids.size > 0) {
87
+ await new Promise((resolve) => setTimeout(resolve, 20));
88
+ for (const pid of killedPids) {
89
+ try {
90
+ process.kill(pid, 0);
91
+ } catch {
92
+ killedPids.delete(pid);
93
+ }
94
+ }
95
+ }
96
+ const deadline = Date.now() + 1e3;
97
+ while (Date.now() < deadline) {
98
+ const removed = await fs.rm(dirPath, { recursive: true, force: true }).then(() => true).catch(() => false);
99
+ if (removed) break;
100
+ await new Promise((resolve) => setTimeout(resolve, 20));
101
+ }
102
+ if (await fs.access(dirPath).then(() => true).catch(() => false)) {
103
+ const diagEntries = await fs.readdir("/proc").catch(() => []);
104
+ await Promise.all(
105
+ diagEntries.map(async (entry) => {
106
+ if (!/^\d+$/.test(entry)) return;
107
+ try {
108
+ const cwd = await fs.readlink(`/proc/${entry}/cwd`).catch(() => "");
109
+ if (!cwd.startsWith(dirPath)) return;
110
+ const cmdline = await fs.readFile(`/proc/${entry}/cmdline`, "utf8").catch(() => "");
111
+ process.stderr.write(
112
+ `# [qunitx] cleanup failed: pid ${entry} still holds ${dirPath} as cwd (cmdline: ${cmdline.replace(/\0/g, " ").slice(0, 120)})
113
+ `
114
+ );
115
+ } catch {
116
+ }
117
+ })
118
+ );
119
+ }
120
+ }
121
+ var init_cleanup_browser_dir = __esm({
122
+ "lib/utils/cleanup-browser-dir.ts"() {
123
+ }
124
+ });
125
+
56
126
  // lib/utils/pre-launch-chrome.ts
57
127
  import { spawn } from "node:child_process";
58
128
  import { mkdtemp, rm } from "node:fs/promises";
@@ -107,7 +177,26 @@ async function preLaunchChrome(chromePath, args, headless = true) {
107
177
  });
108
178
  if (proc.exitCode === null) killProcessGroup(proc.pid);
109
179
  await closed;
110
- await rm(userDataDir, { recursive: true, force: true }).catch(() => {
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);
111
200
  });
112
201
  }
113
202
  }
@@ -115,6 +204,7 @@ var CDP_URL_REGEX;
115
204
  var init_pre_launch_chrome = __esm({
116
205
  "lib/utils/pre-launch-chrome.ts"() {
117
206
  init_kill_process_group();
207
+ init_cleanup_browser_dir();
118
208
  CDP_URL_REGEX = /DevTools listening on (ws:\/\/[^\s]+)/;
119
209
  }
120
210
  });
@@ -296,10 +386,10 @@ var init_color = __esm({
296
386
  });
297
387
 
298
388
  // lib/utils/path-exists.ts
299
- import fs from "node:fs/promises";
389
+ import fs2 from "node:fs/promises";
300
390
  async function pathExists(path6) {
301
391
  try {
302
- await fs.access(path6);
392
+ await fs2.access(path6);
303
393
  return true;
304
394
  } catch {
305
395
  return false;
@@ -311,7 +401,7 @@ var init_path_exists = __esm({
311
401
  });
312
402
 
313
403
  // lib/utils/read-boilerplate.ts
314
- import fs2 from "node:fs/promises";
404
+ import fs3 from "node:fs/promises";
315
405
  import { dirname, join as join2 } from "node:path";
316
406
  import { fileURLToPath } from "node:url";
317
407
  async function readBoilerplate(relativePath) {
@@ -320,7 +410,7 @@ async function readBoilerplate(relativePath) {
320
410
  const __dirname = dirname(fileURLToPath(import.meta.url));
321
411
  for (const base of ["../templates", "../../templates"]) {
322
412
  try {
323
- return (await fs2.readFile(join2(__dirname, base, relativePath))).toString();
413
+ return (await fs3.readFile(join2(__dirname, base, relativePath))).toString();
324
414
  } catch {
325
415
  }
326
416
  }
@@ -790,7 +880,7 @@ var init_http = __esm({
790
880
  });
791
881
 
792
882
  // lib/setup/web-server.ts
793
- import fs7 from "node:fs";
883
+ import fs8 from "node:fs";
794
884
  import path4 from "node:path";
795
885
  function setupWebServer(config, cachedContent) {
796
886
  const STATIC_FILES_PATH = path4.join(config.projectRoot, config.output);
@@ -934,7 +1024,7 @@ function setupWebServer(config, cachedContent) {
934
1024
  if (statusCode === 404) {
935
1025
  res.end();
936
1026
  } else {
937
- fs7.createReadStream(filePath).pipe(res);
1027
+ fs8.createReadStream(filePath).pipe(res);
938
1028
  }
939
1029
  console.log(`# [HTTPServer] GET ${url} ${statusCode} - ${/* @__PURE__ */ new Date() - requestStartedAt}ms`);
940
1030
  });
@@ -1114,7 +1204,7 @@ var init_web_server = __esm({
1114
1204
  init_color();
1115
1205
  init_path_exists();
1116
1206
  init_http();
1117
- fsPromise = fs7.promises;
1207
+ fsPromise = fs8.promises;
1118
1208
  }
1119
1209
  });
1120
1210
 
@@ -1294,7 +1384,7 @@ var init_display_final_result = __esm({
1294
1384
  });
1295
1385
 
1296
1386
  // lib/commands/run/tests-in-browser.ts
1297
- import fs8 from "node:fs/promises";
1387
+ import fs9 from "node:fs/promises";
1298
1388
  import esbuild from "esbuild";
1299
1389
  async function buildTestBundle(config, cachedContent) {
1300
1390
  const { projectRoot, output } = config;
@@ -1304,7 +1394,7 @@ async function buildTestBundle(config, cachedContent) {
1304
1394
  return;
1305
1395
  }
1306
1396
  const outfile = `${projectRoot}/${output}/tests.js`;
1307
- await fs8.mkdir(`${projectRoot}/${output}`, { recursive: true });
1397
+ await fs9.mkdir(`${projectRoot}/${output}`, { recursive: true });
1308
1398
  const sourcemap = config.debug ? "inline" : config.watch ? "linked" : false;
1309
1399
  const needsDisk = true;
1310
1400
  const [allTestCode] = await Promise.all([
@@ -1333,8 +1423,8 @@ async function buildTestBundle(config, cachedContent) {
1333
1423
  cachedContent.htmlPathsToRunTests.map(async (htmlPath) => {
1334
1424
  const targetPath = `${config.projectRoot}/${config.output}${htmlPath}`;
1335
1425
  if (htmlPath !== "/") {
1336
- await fs8.rm(targetPath, { force: true, recursive: true });
1337
- await fs8.mkdir(targetPath.split("/").slice(0, -1).join("/"), { recursive: true });
1426
+ await fs9.rm(targetPath, { force: true, recursive: true });
1427
+ await fs9.mkdir(targetPath.split("/").slice(0, -1).join("/"), { recursive: true });
1338
1428
  }
1339
1429
  })
1340
1430
  )
@@ -1447,7 +1537,7 @@ async function buildWithOverlayfsRetry(options, needsDisk) {
1447
1537
  }
1448
1538
  if (needsDisk) {
1449
1539
  await Promise.all(
1450
- result.outputFiles.map((outputFile) => fs8.writeFile(outputFile.path, outputFile.contents))
1540
+ result.outputFiles.map((outputFile) => fs9.writeFile(outputFile.path, outputFile.contents))
1451
1541
  );
1452
1542
  }
1453
1543
  return js;
@@ -1559,7 +1649,7 @@ var init_tests_in_browser = __esm({
1559
1649
  });
1560
1650
 
1561
1651
  // lib/setup/file-watcher.ts
1562
- import fs9 from "node:fs";
1652
+ import fs10 from "node:fs";
1563
1653
  import { stat, lstat } from "node:fs/promises";
1564
1654
  import path5 from "node:path";
1565
1655
  function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFunc) {
@@ -1572,15 +1662,15 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
1572
1662
  if (symlinkPollers.has(filePath)) return;
1573
1663
  const handler = (curr) => {
1574
1664
  if (curr.nlink === 0) {
1575
- fs9.unwatchFile(filePath, handler);
1665
+ fs10.unwatchFile(filePath, handler);
1576
1666
  symlinkPollers.delete(filePath);
1577
1667
  if (filePath in config.fsTree) {
1578
1668
  handleWatchEvent(config, extensions, "unlink", filePath, onEventFunc, onFinishFunc);
1579
1669
  }
1580
1670
  }
1581
1671
  };
1582
- fs9.watchFile(filePath, { interval: 500, persistent: false }, handler);
1583
- symlinkPollers.set(filePath, () => fs9.unwatchFile(filePath, handler));
1672
+ fs10.watchFile(filePath, { interval: 500, persistent: false }, handler);
1673
+ symlinkPollers.set(filePath, () => fs10.unwatchFile(filePath, handler));
1584
1674
  }
1585
1675
  function untrackSymlink(filePath) {
1586
1676
  symlinkPollers.get(filePath)?.();
@@ -1589,7 +1679,7 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
1589
1679
  for (const watchPath of testFileLookupPaths) {
1590
1680
  let ready = false;
1591
1681
  const lastChangeMs = {};
1592
- const childWatcher = fs9.watch(watchPath, { recursive: true }, async (eventType, filename) => {
1682
+ const childWatcher = fs10.watch(watchPath, { recursive: true }, async (eventType, filename) => {
1593
1683
  if (!ready || !filename) return;
1594
1684
  const fullPath = path5.join(watchPath, filename);
1595
1685
  if (eventType === "change") {
@@ -1619,7 +1709,7 @@ function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFun
1619
1709
  const parentDir = path5.dirname(watchPath);
1620
1710
  const watchedBasename = path5.basename(watchPath);
1621
1711
  let parentUnlinkFired = false;
1622
- const parentWatcher = fs9.watch(parentDir, async (eventType, filename) => {
1712
+ const parentWatcher = fs10.watch(parentDir, async (eventType, filename) => {
1623
1713
  if (!ready || filename !== watchedBasename || eventType !== "rename") return;
1624
1714
  if (parentUnlinkFired) return;
1625
1715
  parentUnlinkFired = true;
@@ -1820,12 +1910,12 @@ var init_keyboard_events = __esm({
1820
1910
  });
1821
1911
 
1822
1912
  // lib/setup/write-output-static-files.ts
1823
- import fs10 from "node:fs/promises";
1913
+ import fs11 from "node:fs/promises";
1824
1914
  async function writeOutputStaticFiles({ projectRoot, output }, cachedContent) {
1825
1915
  const staticHTMLPromises = Object.keys(cachedContent.staticHTMLs).map(async (staticHTMLKey) => {
1826
1916
  const htmlRelativePath = staticHTMLKey.replace(`${projectRoot}/`, "");
1827
1917
  await ensureFolderExists(`${projectRoot}/${output}/${htmlRelativePath}`);
1828
- await fs10.writeFile(
1918
+ await fs11.writeFile(
1829
1919
  `${projectRoot}/${output}/${htmlRelativePath}`,
1830
1920
  cachedContent.staticHTMLs[staticHTMLKey]
1831
1921
  );
@@ -1833,12 +1923,12 @@ async function writeOutputStaticFiles({ projectRoot, output }, cachedContent) {
1833
1923
  const assetPromises = Array.from(cachedContent.assets).map(async (assetAbsolutePath) => {
1834
1924
  const assetRelativePath = assetAbsolutePath.replace(`${projectRoot}/`, "");
1835
1925
  await ensureFolderExists(`${projectRoot}/${output}/${assetRelativePath}`);
1836
- await fs10.copyFile(assetAbsolutePath, `${projectRoot}/${output}/${assetRelativePath}`);
1926
+ await fs11.copyFile(assetAbsolutePath, `${projectRoot}/${output}/${assetRelativePath}`);
1837
1927
  });
1838
1928
  await Promise.all(staticHTMLPromises.concat(assetPromises));
1839
1929
  }
1840
1930
  async function ensureFolderExists(assetPath) {
1841
- await fs10.mkdir(assetPath.split("/").slice(0, -1).join("/"), { recursive: true });
1931
+ await fs11.mkdir(assetPath.split("/").slice(0, -1).join("/"), { recursive: true });
1842
1932
  }
1843
1933
  var init_write_output_static_files = __esm({
1844
1934
  "lib/setup/write-output-static-files.ts"() {
@@ -1851,7 +1941,7 @@ __export(run_exports, {
1851
1941
  default: () => run,
1852
1942
  run: () => run
1853
1943
  });
1854
- import fs11 from "node:fs/promises";
1944
+ import fs12 from "node:fs/promises";
1855
1945
  import { normalize } from "node:path";
1856
1946
  import { availableParallelism } from "node:os";
1857
1947
  async function run(config) {
@@ -2016,7 +2106,7 @@ async function run(config) {
2016
2106
  }
2017
2107
  async function buildCachedContent(config, htmlPaths) {
2018
2108
  const htmlBuffers = await Promise.all(
2019
- config.htmlPaths.map((htmlPath) => fs11.readFile(htmlPath).catch(() => null))
2109
+ config.htmlPaths.map((htmlPath) => fs12.readFile(htmlPath).catch(() => null))
2020
2110
  );
2021
2111
  const cachedContent = htmlPaths.reduce(
2022
2112
  (result, _htmlPath, index) => {
@@ -2121,7 +2211,7 @@ init_color();
2121
2211
  var package_default = {
2122
2212
  name: "qunitx-cli",
2123
2213
  type: "module",
2124
- version: "0.17.5",
2214
+ version: "0.17.7",
2125
2215
  description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
2126
2216
  author: "Izel Nakri",
2127
2217
  license: "MIT",
@@ -2231,7 +2321,7 @@ ${color("$ qunitx new $testFileName")} # Creates a qunitx test file
2231
2321
  }
2232
2322
 
2233
2323
  // lib/commands/init.ts
2234
- import fs3 from "node:fs/promises";
2324
+ import fs4 from "node:fs/promises";
2235
2325
  import path2 from "node:path";
2236
2326
 
2237
2327
  // lib/utils/find-project-root.ts
@@ -2283,7 +2373,7 @@ var defaultProjectConfigValues = {
2283
2373
  init_read_boilerplate();
2284
2374
  async function initializeProject() {
2285
2375
  const projectRoot = await findProjectRoot();
2286
- const oldPackageJSON = JSON.parse(await fs3.readFile(`${projectRoot}/package.json`));
2376
+ const oldPackageJSON = JSON.parse(await fs4.readFile(`${projectRoot}/package.json`));
2287
2377
  const existingQunitx = oldPackageJSON.qunitx || {};
2288
2378
  const cliHtmlPaths = process.argv.slice(2).filter((arg) => arg.endsWith(".html"));
2289
2379
  const config = Object.assign({}, defaultProjectConfigValues, existingQunitx, {
@@ -2312,8 +2402,8 @@ async function writeTestsHTML(projectRoot, config, oldPackageJSON) {
2312
2402
  "{{applicationName}}",
2313
2403
  oldPackageJSON.name
2314
2404
  );
2315
- await fs3.mkdir(targetDirectory, { recursive: true });
2316
- await fs3.writeFile(targetPath, testHTMLTemplate);
2405
+ await fs4.mkdir(targetDirectory, { recursive: true });
2406
+ await fs4.writeFile(targetPath, testHTMLTemplate);
2317
2407
  console.log(`${targetPath} written`);
2318
2408
  }
2319
2409
  })
@@ -2321,20 +2411,20 @@ async function writeTestsHTML(projectRoot, config, oldPackageJSON) {
2321
2411
  }
2322
2412
  async function rewritePackageJSON(projectRoot, config, oldPackageJSON) {
2323
2413
  const newPackageJSON = Object.assign(oldPackageJSON, { qunitx: config });
2324
- await fs3.writeFile(`${projectRoot}/package.json`, JSON.stringify(newPackageJSON, null, 2));
2414
+ await fs4.writeFile(`${projectRoot}/package.json`, JSON.stringify(newPackageJSON, null, 2));
2325
2415
  }
2326
2416
  async function writeTSConfigIfNeeded(projectRoot) {
2327
2417
  const targetPath = `${projectRoot}/tsconfig.json`;
2328
2418
  if (!await pathExists(targetPath)) {
2329
2419
  const tsConfigTemplate = await readBoilerplate("setup/tsconfig.json");
2330
- await fs3.writeFile(targetPath, tsConfigTemplate);
2420
+ await fs4.writeFile(targetPath, tsConfigTemplate);
2331
2421
  console.log(`${targetPath} written`);
2332
2422
  }
2333
2423
  }
2334
2424
 
2335
2425
  // lib/commands/generate.ts
2336
2426
  init_color();
2337
- import fs4 from "node:fs/promises";
2427
+ import fs5 from "node:fs/promises";
2338
2428
  init_path_exists();
2339
2429
  init_read_boilerplate();
2340
2430
 
@@ -2361,22 +2451,22 @@ async function generateTestFiles() {
2361
2451
  const testJSContent = await readBoilerplate("test.js");
2362
2452
  const targetFolderPaths = path6.split("/");
2363
2453
  targetFolderPaths.pop();
2364
- await fs4.mkdir(targetFolderPaths.join("/"), { recursive: true });
2365
- await fs4.writeFile(path6, testJSContent.replace("{{moduleName}}", moduleName));
2454
+ await fs5.mkdir(targetFolderPaths.join("/"), { recursive: true });
2455
+ await fs5.writeFile(path6, testJSContent.replace("{{moduleName}}", moduleName));
2366
2456
  console.log(green(`${path6} written`));
2367
2457
  }
2368
2458
 
2369
2459
  // lib/setup/config.ts
2370
- import fs6 from "node:fs/promises";
2460
+ import fs7 from "node:fs/promises";
2371
2461
 
2372
2462
  // lib/setup/fs-tree.ts
2373
- import fs5, { glob as fsGlob } from "node:fs/promises";
2463
+ import fs6, { glob as fsGlob } from "node:fs/promises";
2374
2464
  import path3 from "node:path";
2375
2465
  function isGlob(str) {
2376
2466
  return /[*?{[]/.test(str);
2377
2467
  }
2378
2468
  async function readDirRecursive(dir, filter) {
2379
- const entries = await fs5.readdir(dir, { recursive: true, withFileTypes: true });
2469
+ const entries = await fs6.readdir(dir, { recursive: true, withFileTypes: true });
2380
2470
  const candidates = entries.filter(
2381
2471
  (dirent) => (dirent.isFile() || dirent.isSymbolicLink()) && filter(dirent.name)
2382
2472
  );
@@ -2385,7 +2475,7 @@ async function readDirRecursive(dir, filter) {
2385
2475
  const fullPath = path3.join(dirent.parentPath, dirent.name);
2386
2476
  if (dirent.isFile()) return fullPath;
2387
2477
  try {
2388
- const statResult = await fs5.stat(fullPath);
2478
+ const statResult = await fs6.stat(fullPath);
2389
2479
  return statResult.isFile() ? fullPath : null;
2390
2480
  } catch {
2391
2481
  return null;
@@ -2407,7 +2497,7 @@ async function buildFSTree(fileAbsolutePaths, config = {}) {
2407
2497
  }
2408
2498
  }
2409
2499
  } else {
2410
- const entry = await fs5.stat(fileAbsolutePath);
2500
+ const entry = await fs6.stat(fileAbsolutePath);
2411
2501
  if (entry.isFile()) {
2412
2502
  fsTree[fileAbsolutePath] = null;
2413
2503
  } else if (entry.isDirectory()) {
@@ -2580,7 +2670,7 @@ async function setupConfig() {
2580
2670
  return config;
2581
2671
  }
2582
2672
  async function readConfigFromPackageJSON(projectRoot) {
2583
- const packageJSON = await fs6.readFile(`${projectRoot}/package.json`);
2673
+ const packageJSON = await fs7.readFile(`${projectRoot}/package.json`);
2584
2674
  return JSON.parse(packageJSON.toString());
2585
2675
  }
2586
2676
  function normalizeHTMLPaths(projectRoot, htmlPaths) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx-cli",
3
3
  "type": "module",
4
- "version": "0.17.5",
4
+ "version": "0.17.7",
5
5
  "description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",