qunitx-cli 0.17.7 → 0.17.8

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 +104 -89
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -301,16 +301,16 @@ var init_perf_logger = __esm({
301
301
  }
302
302
  });
303
303
 
304
- // lib/utils/early-chrome.ts
305
- async function shutdownEarlyBrowser() {
304
+ // lib/utils/chrome-prelaunch.ts
305
+ async function shutdownPrelaunch() {
306
306
  if (!earlyChrome) return;
307
307
  const { shutdown } = earlyChrome;
308
308
  earlyChrome = null;
309
309
  await shutdown();
310
310
  }
311
- var NON_RUN_COMMANDS, isRunCommand, browserFromArgv, openFromArgv, watchFromArgv, openWatchMode, earlyChrome, earlyBrowserPromise;
312
- var init_early_chrome = __esm({
313
- "lib/utils/early-chrome.ts"() {
311
+ var NON_RUN_COMMANDS, isRunCommand, browserFromArgv, openFromArgv, watchFromArgv, openWatchMode, earlyChrome, prelaunchPromise;
312
+ var init_chrome_prelaunch = __esm({
313
+ "lib/utils/chrome-prelaunch.ts"() {
314
314
  init_find_chrome();
315
315
  init_pre_launch_chrome();
316
316
  init_kill_process_group();
@@ -335,12 +335,12 @@ var init_early_chrome = __esm({
335
335
  killProcessGroup(earlyChrome.proc.pid);
336
336
  });
337
337
  }
338
- perfLog("early-chrome.js: module evaluated");
339
- earlyBrowserPromise = isRunCommand && browserFromArgv === "chromium" ? findChrome().then((chromePath) => {
340
- perfLog("early-chrome.js: findChrome resolved", chromePath);
338
+ perfLog("chrome-prelaunch.ts: module evaluated");
339
+ prelaunchPromise = isRunCommand && browserFromArgv === "chromium" ? findChrome().then((chromePath) => {
340
+ perfLog("chrome-prelaunch.ts: findChrome resolved", chromePath);
341
341
  return preLaunchChrome(chromePath, CHROMIUM_ARGS, !openWatchMode);
342
342
  }).then((info) => {
343
- perfLog("early-chrome.js: Chrome CDP ready", info?.cdpEndpoint ?? null);
343
+ perfLog("chrome-prelaunch.ts: Chrome CDP ready", info?.cdpEndpoint ?? null);
344
344
  if (info) earlyChrome = info;
345
345
  return info;
346
346
  }) : Promise.resolve(null);
@@ -400,11 +400,11 @@ var init_path_exists = __esm({
400
400
  }
401
401
  });
402
402
 
403
- // lib/utils/read-boilerplate.ts
403
+ // lib/utils/read-template.ts
404
404
  import fs3 from "node:fs/promises";
405
405
  import { dirname, join as join2 } from "node:path";
406
406
  import { fileURLToPath } from "node:url";
407
- async function readBoilerplate(relativePath) {
407
+ async function readTemplate(relativePath) {
408
408
  const sea = await import("node:sea").catch(() => null);
409
409
  if (sea?.isSea()) return sea.getAsset(relativePath, "utf8");
410
410
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -418,8 +418,8 @@ async function readBoilerplate(relativePath) {
418
418
  `qunitx-cli: template "${relativePath}" not found \u2014 try reinstalling the package.`
419
419
  );
420
420
  }
421
- var init_read_boilerplate = __esm({
422
- "lib/utils/read-boilerplate.ts"() {
421
+ var init_read_template = __esm({
422
+ "lib/utils/read-template.ts"() {
423
423
  }
424
424
  });
425
425
 
@@ -438,19 +438,19 @@ var init_find_internal_assets_from_html = __esm({
438
438
  }
439
439
  });
440
440
 
441
- // lib/utils/html-content-marker.ts
442
- function findHTMLContentMarker(html) {
443
- return html.includes(HTML_CONTENT_MARKER) ? HTML_CONTENT_MARKER : void 0;
441
+ // lib/utils/html.ts
442
+ function findScriptPlaceholder(html) {
443
+ return html.includes(SCRIPT_PLACEHOLDER) ? SCRIPT_PLACEHOLDER : void 0;
444
444
  }
445
- function htmlHasDynamicContentMarker(html) {
446
- return !!findHTMLContentMarker(html) || HANDLEBARS_TOKEN_REGEX.test(html);
445
+ function isCustomTemplate(html) {
446
+ return !!findScriptPlaceholder(html) || HANDLEBARS_TOKEN_REGEX.test(html);
447
447
  }
448
- function replaceHTMLContentMarker(html, content) {
449
- const marker = findHTMLContentMarker(html);
450
- if (marker) {
451
- return html.replace(marker, content);
448
+ function injectScript(html, content) {
449
+ const placeholder = findScriptPlaceholder(html);
450
+ if (placeholder) {
451
+ return html.replace(placeholder, content);
452
452
  }
453
- if (htmlHasDynamicContentMarker(html)) {
453
+ if (isCustomTemplate(html)) {
454
454
  if (html.includes("</body>")) {
455
455
  return html.replace("</body>", `${content}</body>`);
456
456
  }
@@ -461,10 +461,10 @@ function replaceHTMLContentMarker(html, content) {
461
461
  }
462
462
  return html;
463
463
  }
464
- var HTML_CONTENT_MARKER, HANDLEBARS_TOKEN_REGEX;
465
- var init_html_content_marker = __esm({
466
- "lib/utils/html-content-marker.ts"() {
467
- HTML_CONTENT_MARKER = "{{qunitxScript}}";
464
+ var SCRIPT_PLACEHOLDER, HANDLEBARS_TOKEN_REGEX;
465
+ var init_html = __esm({
466
+ "lib/utils/html.ts"() {
467
+ SCRIPT_PLACEHOLDER = "{{qunitxScript}}";
468
468
  HANDLEBARS_TOKEN_REGEX = /{{\s*[^}]+\s*}}/;
469
469
  }
470
470
  });
@@ -627,8 +627,8 @@ async function bindServerToPort(server, config) {
627
627
  var EXPLICIT_PORT_RETRIES, EXPLICIT_PORT_RETRY_DELAY_MS;
628
628
  var init_bind_server_to_port = __esm({
629
629
  "lib/setup/bind-server-to-port.ts"() {
630
- EXPLICIT_PORT_RETRIES = 5;
631
- EXPLICIT_PORT_RETRY_DELAY_MS = 20;
630
+ EXPLICIT_PORT_RETRIES = 20;
631
+ EXPLICIT_PORT_RETRY_DELAY_MS = 50;
632
632
  }
633
633
  });
634
634
 
@@ -1189,17 +1189,14 @@ function testRuntimeToInject(port, config) {
1189
1189
  </script>`;
1190
1190
  }
1191
1191
  function escapeAndInjectTestsToHTML(html, testRuntimeCode, testBundleUrl) {
1192
- return replaceHTMLContentMarker(
1193
- html,
1194
- `${testRuntimeCode}
1195
- <script src="${testBundleUrl}" async></script>`
1196
- );
1192
+ return injectScript(html, `${testRuntimeCode}
1193
+ <script src="${testBundleUrl}" async></script>`);
1197
1194
  }
1198
1195
  var fsPromise;
1199
1196
  var init_web_server = __esm({
1200
1197
  "lib/setup/web-server.ts"() {
1201
1198
  init_find_internal_assets_from_html();
1202
- init_html_content_marker();
1199
+ init_html();
1203
1200
  init_display_test_result();
1204
1201
  init_color();
1205
1202
  init_path_exists();
@@ -1213,18 +1210,18 @@ async function launchBrowser(config) {
1213
1210
  const browserName = config.browser || "chromium";
1214
1211
  if (browserName === "chromium") {
1215
1212
  const waitStart = Date.now();
1216
- const [playwrightCore2, earlyChrome2] = await Promise.all([
1213
+ const [playwrightCore2, prelaunch] = await Promise.all([
1217
1214
  playwrightCorePromise,
1218
- earlyBrowserPromise
1215
+ prelaunchPromise
1219
1216
  ]);
1220
1217
  perfLog(
1221
- `browser.js: playwright-core + earlyChrome resolved in ${Date.now() - waitStart}ms, earlyChrome:`,
1222
- earlyChrome2?.cdpEndpoint ?? null
1218
+ `browser.js: playwright-core + prelaunch resolved in ${Date.now() - waitStart}ms, prelaunch:`,
1219
+ prelaunch?.cdpEndpoint ?? null
1223
1220
  );
1224
- if (earlyChrome2) {
1221
+ if (prelaunch) {
1225
1222
  const connectStart = Date.now();
1226
1223
  const browser = await playwrightCore2.chromium.connectOverCDP({
1227
- endpointURL: earlyChrome2.cdpEndpoint
1224
+ endpointURL: prelaunch.cdpEndpoint
1228
1225
  });
1229
1226
  perfLog(`browser.js: connectOverCDP took ${Date.now() - connectStart}ms`);
1230
1227
  return browser;
@@ -1279,8 +1276,8 @@ async function setupBrowser(config, cachedContent, existingBrowser = null) {
1279
1276
  }
1280
1277
  });
1281
1278
  page.on("pageerror", (error) => {
1282
- console.log(error.toString());
1283
1279
  console.error(error.toString());
1280
+ config.COUNTER.failCount++;
1284
1281
  });
1285
1282
  return { server, browser, page };
1286
1283
  }
@@ -1291,7 +1288,7 @@ var init_browser = __esm({
1291
1288
  init_bind_server_to_port();
1292
1289
  init_find_chrome();
1293
1290
  init_chromium_args();
1294
- init_early_chrome();
1291
+ init_chrome_prelaunch();
1295
1292
  init_perf_logger();
1296
1293
  playwrightCorePromise = import("playwright-core");
1297
1294
  perfLog("browser.js: playwright-core import started");
@@ -1397,28 +1394,26 @@ async function buildTestBundle(config, cachedContent) {
1397
1394
  await fs9.mkdir(`${projectRoot}/${output}`, { recursive: true });
1398
1395
  const sourcemap = config.debug ? "inline" : config.watch ? "linked" : false;
1399
1396
  const needsDisk = true;
1397
+ const buildOptions = {
1398
+ stdin: {
1399
+ contents: allTestFilePaths.map((filePath) => `import "${filePath}";`).join(""),
1400
+ resolveDir: process.cwd()
1401
+ },
1402
+ bundle: true,
1403
+ logLevel: "error",
1404
+ outfile,
1405
+ keepNames: true,
1406
+ legalComments: "none",
1407
+ target: esbuildTarget(config.browser),
1408
+ sourcemap,
1409
+ // Signal the runtime that all test modules are registered. The runtime's maybeStart()
1410
+ // waits for both this event and the WebSocket 'open' event before calling QUnit.start().
1411
+ // Dispatching from the bundle (rather than from a script onload attr) is reliable across
1412
+ // all browsers and does not require changes to user test code.
1413
+ footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
1414
+ };
1400
1415
  const [allTestCode] = await Promise.all([
1401
- buildWithOverlayfsRetry(
1402
- {
1403
- stdin: {
1404
- contents: allTestFilePaths.map((filePath) => `import "${filePath}";`).join(""),
1405
- resolveDir: process.cwd()
1406
- },
1407
- bundle: true,
1408
- logLevel: "error",
1409
- outfile,
1410
- keepNames: true,
1411
- legalComments: "none",
1412
- target: esbuildTarget(config.browser),
1413
- sourcemap,
1414
- // Signal the runtime that all test modules are registered. The runtime's maybeStart()
1415
- // waits for both this event and the WebSocket 'open' event before calling QUnit.start().
1416
- // Dispatching from the bundle (rather than from a script onload attr) is reliable across
1417
- // all browsers and does not require changes to user test code.
1418
- footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
1419
- },
1420
- needsDisk
1421
- ),
1416
+ config.watch ? buildIncrementally(buildOptions, allTestFilePaths.join("\0"), cachedContent, needsDisk) : buildWithOverlayfsRetry(buildOptions, needsDisk),
1422
1417
  Promise.all(
1423
1418
  cachedContent.htmlPathsToRunTests.map(async (htmlPath) => {
1424
1419
  const targetPath = `${config.projectRoot}/${config.output}${htmlPath}`;
@@ -1440,8 +1435,10 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
1440
1435
  }
1441
1436
  config.lastRanTestFiles = targetTestFilesToFilter || allTestFilePaths;
1442
1437
  try {
1438
+ const preBuildPromise = cachedContent._preBuildPromise;
1439
+ cachedContent._preBuildPromise = null;
1443
1440
  if (!cachedContent.allTestCode) {
1444
- await buildTestBundle(config, cachedContent);
1441
+ await (preBuildPromise ?? buildTestBundle(config, cachedContent));
1445
1442
  }
1446
1443
  if (!cachedContent.allTestCode) {
1447
1444
  return connections;
@@ -1475,7 +1472,7 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
1475
1472
  connections.server && connections.server.close(),
1476
1473
  connections.browser && connections.browser.close()
1477
1474
  ]);
1478
- await shutdownEarlyBrowser();
1475
+ await shutdownPrelaunch();
1479
1476
  return process.exit(config.COUNTER.failCount > 0 ? 1 : 0);
1480
1477
  }
1481
1478
  }
@@ -1511,16 +1508,10 @@ function buildFilteredTests(filteredTests, outputPath, config) {
1511
1508
  needsDisk
1512
1509
  );
1513
1510
  }
1514
- async function buildWithOverlayfsRetry(options, needsDisk) {
1511
+ async function runWithOverlayfsRetry(getContents, needsDisk) {
1515
1512
  const RETRY_DELAY_MS = 100;
1516
1513
  const MAX_RETRIES = 3;
1517
1514
  const EMPTY_BUNDLE_THRESHOLD = 500;
1518
- const buildOpts = { ...options, write: false };
1519
- const getContents = async () => {
1520
- const result2 = await esbuild.build(buildOpts);
1521
- const jsFile = result2.outputFiles.find((outputFile) => !outputFile.path.endsWith(".map"));
1522
- return { result: result2, js: Buffer.from(jsFile.contents) };
1523
- };
1524
1515
  let { result, js } = await getContents();
1525
1516
  for (let retry = 1; retry <= MAX_RETRIES; retry++) {
1526
1517
  if (js.length >= EMPTY_BUNDLE_THRESHOLD) break;
@@ -1542,6 +1533,29 @@ async function buildWithOverlayfsRetry(options, needsDisk) {
1542
1533
  }
1543
1534
  return js;
1544
1535
  }
1536
+ function buildWithOverlayfsRetry(options, needsDisk) {
1537
+ const buildOpts = { ...options, write: false };
1538
+ return runWithOverlayfsRetry(async () => {
1539
+ const result = await esbuild.build(buildOpts);
1540
+ const jsFile = result.outputFiles.find((f) => !f.path.endsWith(".map"));
1541
+ return { result, js: Buffer.from(jsFile.contents) };
1542
+ }, needsDisk);
1543
+ }
1544
+ async function buildIncrementally(options, fileKey, cachedContent, needsDisk) {
1545
+ const buildOpts = { ...options, write: false };
1546
+ if (!cachedContent._esbuildContext || cachedContent._esbuildContextKey !== fileKey) {
1547
+ cachedContent._esbuildContext?.dispose().catch(() => {
1548
+ });
1549
+ cachedContent._esbuildContext = await esbuild.context(buildOpts);
1550
+ cachedContent._esbuildContextKey = fileKey;
1551
+ }
1552
+ const ctx = cachedContent._esbuildContext;
1553
+ return runWithOverlayfsRetry(async () => {
1554
+ const result = await ctx.rebuild();
1555
+ const jsFile = result.outputFiles.find((f) => !f.path.endsWith(".map"));
1556
+ return { result, js: Buffer.from(jsFile.contents) };
1557
+ }, needsDisk);
1558
+ }
1545
1559
  function esbuildTarget(browser) {
1546
1560
  if (browser === "firefox") return ["firefox115"];
1547
1561
  if (browser === "webkit") return ["safari16"];
@@ -1626,7 +1640,7 @@ async function failOnNonWatchMode(watchMode = false, connections = {}, groupMode
1626
1640
  connections.server && connections.server.close(),
1627
1641
  connections.browser && connections.browser.close()
1628
1642
  ]);
1629
- await shutdownEarlyBrowser();
1643
+ await shutdownPrelaunch();
1630
1644
  process.exit(1);
1631
1645
  }
1632
1646
  }
@@ -1634,7 +1648,7 @@ var BundleError;
1634
1648
  var init_tests_in_browser = __esm({
1635
1649
  "lib/commands/run/tests-in-browser.ts"() {
1636
1650
  init_color();
1637
- init_early_chrome();
1651
+ init_chrome_prelaunch();
1638
1652
  init_time_counter();
1639
1653
  init_run_user_module();
1640
1654
  init_display_final_result();
@@ -1947,6 +1961,7 @@ import { availableParallelism } from "node:os";
1947
1961
  async function run(config) {
1948
1962
  const cachedContent = await buildCachedContent(config, config.htmlPaths);
1949
1963
  if (config.watch) {
1964
+ cachedContent._preBuildPromise = buildTestBundle(config, cachedContent);
1950
1965
  const [connections] = await Promise.all([
1951
1966
  setupBrowser(config, cachedContent),
1952
1967
  writeOutputStaticFiles(config, cachedContent)
@@ -2099,7 +2114,7 @@ async function run(config) {
2099
2114
  clearInterval(keepAlive);
2100
2115
  await browser.close().catch(() => {
2101
2116
  });
2102
- await shutdownEarlyBrowser();
2117
+ await shutdownPrelaunch();
2103
2118
  process.exit(exitCode);
2104
2119
  });
2105
2120
  }
@@ -2114,7 +2129,7 @@ async function buildCachedContent(config, htmlPaths) {
2114
2129
  if (buffer === null) return result;
2115
2130
  const filePath = config.htmlPaths[index];
2116
2131
  const html = buffer.toString();
2117
- if (htmlHasDynamicContentMarker(html)) {
2132
+ if (isCustomTemplate(html)) {
2118
2133
  result.dynamicContentHTMLs[filePath] = html;
2119
2134
  result.htmlPathsToRunTests.push(filePath.replace(config.projectRoot, ""));
2120
2135
  } else {
@@ -2153,7 +2168,7 @@ async function addCachedContentMainHTML(projectRoot, cachedContent) {
2153
2168
  html: cachedContent.dynamicContentHTMLs[mainHTMLPath]
2154
2169
  };
2155
2170
  } else {
2156
- const html = await readBoilerplate("setup/tests.hbs");
2171
+ const html = await readTemplate("setup/tests.hbs");
2157
2172
  cachedContent.mainHTML = { filePath: `${projectRoot}/test/tests.html`, html };
2158
2173
  cachedContent.assets.add(`${projectRoot}/node_modules/qunitx/vendor/qunit.css`);
2159
2174
  }
@@ -2184,7 +2199,7 @@ function normalizeInternalAssetPathFromHTML(projectRoot, assetPath, htmlPath) {
2184
2199
  var init_run = __esm({
2185
2200
  "lib/commands/run.ts"() {
2186
2201
  init_browser();
2187
- init_early_chrome();
2202
+ init_chrome_prelaunch();
2188
2203
  init_open_output_in_browser();
2189
2204
  init_color();
2190
2205
  init_tests_in_browser();
@@ -2195,13 +2210,13 @@ var init_run = __esm({
2195
2210
  init_write_output_static_files();
2196
2211
  init_time_counter();
2197
2212
  init_display_final_result();
2198
- init_read_boilerplate();
2199
- init_html_content_marker();
2213
+ init_read_template();
2214
+ init_html();
2200
2215
  }
2201
2216
  });
2202
2217
 
2203
2218
  // cli.ts
2204
- init_early_chrome();
2219
+ init_chrome_prelaunch();
2205
2220
  import process4 from "node:process";
2206
2221
 
2207
2222
  // lib/commands/help.ts
@@ -2211,7 +2226,7 @@ init_color();
2211
2226
  var package_default = {
2212
2227
  name: "qunitx-cli",
2213
2228
  type: "module",
2214
- version: "0.17.7",
2229
+ version: "0.17.8",
2215
2230
  description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
2216
2231
  author: "Izel Nakri",
2217
2232
  license: "MIT",
@@ -2270,7 +2285,7 @@ var package_default = {
2270
2285
  express: "^5.2.1",
2271
2286
  "js-yaml": "^4.1.1",
2272
2287
  prettier: "^3.8.2",
2273
- qunitx: "^1.2.1",
2288
+ qunitx: "^1.2.7",
2274
2289
  typescript: "^6.0.2"
2275
2290
  },
2276
2291
  volta: {
@@ -2370,7 +2385,7 @@ var defaultProjectConfigValues = {
2370
2385
  };
2371
2386
 
2372
2387
  // lib/commands/init.ts
2373
- init_read_boilerplate();
2388
+ init_read_template();
2374
2389
  async function initializeProject() {
2375
2390
  const projectRoot = await findProjectRoot();
2376
2391
  const oldPackageJSON = JSON.parse(await fs4.readFile(`${projectRoot}/package.json`));
@@ -2386,7 +2401,7 @@ async function initializeProject() {
2386
2401
  ]);
2387
2402
  }
2388
2403
  async function writeTestsHTML(projectRoot, config, oldPackageJSON) {
2389
- const testHTMLTemplateBuffer = await readBoilerplate("setup/tests.hbs");
2404
+ const testHTMLTemplateBuffer = await readTemplate("setup/tests.hbs");
2390
2405
  return await Promise.all(
2391
2406
  config.htmlPaths.map(async (htmlPath) => {
2392
2407
  const targetPath = `${projectRoot}/${htmlPath}`;
@@ -2416,7 +2431,7 @@ async function rewritePackageJSON(projectRoot, config, oldPackageJSON) {
2416
2431
  async function writeTSConfigIfNeeded(projectRoot) {
2417
2432
  const targetPath = `${projectRoot}/tsconfig.json`;
2418
2433
  if (!await pathExists(targetPath)) {
2419
- const tsConfigTemplate = await readBoilerplate("setup/tsconfig.json");
2434
+ const tsConfigTemplate = await readTemplate("setup/tsconfig.json");
2420
2435
  await fs4.writeFile(targetPath, tsConfigTemplate);
2421
2436
  console.log(`${targetPath} written`);
2422
2437
  }
@@ -2426,7 +2441,7 @@ async function writeTSConfigIfNeeded(projectRoot) {
2426
2441
  init_color();
2427
2442
  import fs5 from "node:fs/promises";
2428
2443
  init_path_exists();
2429
- init_read_boilerplate();
2444
+ init_read_template();
2430
2445
 
2431
2446
  // lib/utils/convert-to-pascal-case.ts
2432
2447
  function convertToPascalCase(str) {
@@ -2448,7 +2463,7 @@ async function generateTestFiles() {
2448
2463
  console.log(`${path6} already exists!`);
2449
2464
  return;
2450
2465
  }
2451
- const testJSContent = await readBoilerplate("test.js");
2466
+ const testJSContent = await readTemplate("test.js");
2452
2467
  const targetFolderPaths = path6.split("/");
2453
2468
  targetFolderPaths.pop();
2454
2469
  await fs5.mkdir(targetFolderPaths.join("/"), { recursive: true });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qunitx-cli",
3
3
  "type": "module",
4
- "version": "0.17.7",
4
+ "version": "0.17.8",
5
5
  "description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
6
6
  "author": "Izel Nakri",
7
7
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "express": "^5.2.1",
61
61
  "js-yaml": "^4.1.1",
62
62
  "prettier": "^3.8.2",
63
- "qunitx": "^1.2.1",
63
+ "qunitx": "^1.2.7",
64
64
  "typescript": "^6.0.2"
65
65
  },
66
66
  "volta": {