as-test 1.1.1 → 1.1.2

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/bin/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import chalk from "chalk";
3
- import { build, formatInvocation as formatBuildInvocation, getBuildInvocationPreview, } from "./commands/build.js";
3
+ import { build, BuildFailureError, formatInvocation as formatBuildInvocation, getBuildInvocationPreview, } from "./commands/build.js";
4
4
  import { createRunReporter, run } from "./commands/run.js";
5
5
  import { executeBuildCommand } from "./commands/build.js";
6
6
  import { executeRunCommand } from "./commands/run.js";
@@ -10,7 +10,7 @@ import { executeInitCommand } from "./commands/init.js";
10
10
  import { executeDoctorCommand } from "./commands/doctor.js";
11
11
  import { executeCleanCommand } from "./commands/clean.js";
12
12
  import { fuzz } from "./commands/fuzz-core.js";
13
- import { applyMode, getDefaultModeNames, formatTime, getCliVersion, loadConfig, resolveModeNames, } from "./util.js";
13
+ import { applyMode, formatTime, formatSpecDisplayPath, getDefaultModeNames, getCliVersion, loadConfig, resolveModeNames, } from "./util.js";
14
14
  import * as path from "path";
15
15
  import { spawnSync } from "child_process";
16
16
  import { glob } from "glob";
@@ -374,7 +374,6 @@ function printCommandHelp(command) {
374
374
  process.stdout.write(chalk.bold("Flags:\n"));
375
375
  process.stdout.write(" --config <path> Use a specific config file\n");
376
376
  process.stdout.write(" --mode <name[,name...]> Clean one or multiple named modes\n");
377
- process.stdout.write(" -f, --force Skip the full-clean confirmation prompt\n");
378
377
  process.stdout.write(" --help, -h Show this help\n");
379
378
  return;
380
379
  }
@@ -1070,10 +1069,12 @@ function resolveCommandTokens(rawArgs, command) {
1070
1069
  }
1071
1070
  async function buildFileForMode(args) {
1072
1071
  if (args.buildPool) {
1072
+ const buildInvocation = await getBuildInvocationPreview(args.configPath, args.file, args.modeName, args.buildFeatureToggles);
1073
1073
  await args.buildPool.buildFileMode({
1074
1074
  configPath: args.configPath,
1075
1075
  file: args.file,
1076
1076
  modeName: args.modeName,
1077
+ buildCommand: formatBuildInvocation(buildInvocation),
1077
1078
  featureToggles: args.buildFeatureToggles,
1078
1079
  });
1079
1080
  }
@@ -1104,21 +1105,30 @@ async function runTestSequential(runFlags, configPath, selectors, suiteSelectors
1104
1105
  const duplicateSpecBasenames = resolveDuplicateSpecBasenames(files);
1105
1106
  for (const file of files) {
1106
1107
  const buildStartedAt = Date.now();
1107
- await build(configPath, [file], modeName, buildFeatureToggles);
1108
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1109
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1110
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1111
- const result = await run(runFlags, configPath, [file], false, {
1112
- reporter,
1113
- webSession,
1114
- suiteSelectors,
1115
- emitRunStart: false,
1116
- emitRunComplete: false,
1117
- logFileName: `test.${artifactKey}.log.json`,
1118
- coverageFileName: `coverage.${artifactKey}.log.json`,
1119
- buildCommand: formatBuildInvocation(buildInvocation),
1120
- modeName,
1121
- });
1108
+ let result;
1109
+ try {
1110
+ await build(configPath, [file], modeName, buildFeatureToggles);
1111
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1112
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1113
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1114
+ result = await run(runFlags, configPath, [file], false, {
1115
+ reporter,
1116
+ webSession,
1117
+ suiteSelectors,
1118
+ emitRunStart: false,
1119
+ emitRunComplete: false,
1120
+ logFileName: `test.${artifactKey}.log.json`,
1121
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1122
+ buildCommand: formatBuildInvocation(buildInvocation),
1123
+ modeName,
1124
+ });
1125
+ }
1126
+ catch (error) {
1127
+ const buildFailure = getBuildFailureErrorLike(error);
1128
+ if (!buildFailure)
1129
+ throw error;
1130
+ result = createBuildFailureRunResult(buildFailure);
1131
+ }
1122
1132
  results.push(result);
1123
1133
  if (result?.failed)
1124
1134
  failed = true;
@@ -1305,7 +1315,7 @@ async function runRuntimeMatrix(runFlags, configPath, selectors, suiteSelectors,
1305
1315
  const buildIntervals = [];
1306
1316
  for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
1307
1317
  const file = files[fileIndex];
1308
- const fileName = path.basename(file);
1318
+ const fileName = formatSpecDisplayPath(file);
1309
1319
  const fileResults = [];
1310
1320
  const modeTimes = modes.map(() => "...");
1311
1321
  if (liveMatrix) {
@@ -1481,7 +1491,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1481
1491
  const buildIntervals = [];
1482
1492
  for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
1483
1493
  const file = files[fileIndex];
1484
- const fileName = path.basename(file);
1494
+ const fileName = formatSpecDisplayPath(file);
1485
1495
  const fileResults = [];
1486
1496
  const modeTimes = modes.map(() => "...");
1487
1497
  if (liveMatrix) {
@@ -1489,6 +1499,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1489
1499
  }
1490
1500
  for (let i = 0; i < modes.length; i++) {
1491
1501
  const modeName = modes[i];
1502
+ let result;
1492
1503
  try {
1493
1504
  const buildStartedAt = Date.now();
1494
1505
  await buildFileForMode({
@@ -1500,7 +1511,7 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1500
1511
  buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1501
1512
  const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1502
1513
  const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1503
- const result = await run(runFlags, configPath, [file], false, {
1514
+ result = await run(runFlags, configPath, [file], false, {
1504
1515
  reporter: silentReporter,
1505
1516
  reporterKind: "default",
1506
1517
  emitRunStart: false,
@@ -1510,23 +1521,27 @@ async function runTestMatrix(runFlags, configPath, selectors, suiteSelectors, fu
1510
1521
  buildCommand: formatBuildInvocation(buildInvocation),
1511
1522
  modeName,
1512
1523
  });
1513
- modeTimes[i] = formatMatrixModeTime(result.stats.time);
1514
- if (liveMatrix) {
1515
- renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
1516
- }
1517
- if (result.failed) {
1518
- modeState[i].failed = true;
1519
- }
1520
- else if (result.stats.passedFiles > 0) {
1521
- modeState[i].passed = true;
1522
- }
1523
- fileResults.push(result);
1524
- allResults.push(result);
1525
1524
  }
1526
1525
  catch (error) {
1527
- clearLiveLine();
1528
- throw error;
1526
+ const buildFailure = getBuildFailureErrorLike(error);
1527
+ if (!buildFailure) {
1528
+ clearLiveLine();
1529
+ throw error;
1530
+ }
1531
+ result = createBuildFailureRunResult(buildFailure);
1532
+ }
1533
+ modeTimes[i] = formatMatrixModeTime(result.stats.time);
1534
+ if (liveMatrix) {
1535
+ renderMatrixLiveLine(fileName, modeLabels, modeTimes, showPerModeTimes);
1536
+ }
1537
+ if (result.failed) {
1538
+ modeState[i].failed = true;
1529
1539
  }
1540
+ else if (result.stats.passedFiles > 0) {
1541
+ modeState[i].passed = true;
1542
+ }
1543
+ fileResults.push(result);
1544
+ allResults.push(result);
1530
1545
  }
1531
1546
  if (reporterSession.reporterKind == "default") {
1532
1547
  renderMatrixFileResult(fileName, modeLabels, fileResults, modeTimes, liveMatrix, showPerModeTimes);
@@ -1611,22 +1626,31 @@ async function runRuntimeSingleParallel(runFlags, configPath, selectors, suiteSe
1611
1626
  const poolWidth = Math.max(runFlags.buildJobs, runFlags.runJobs);
1612
1627
  await runOrderedPool(files, poolWidth, async (file, index) => {
1613
1628
  const token = useQueueDisplay
1614
- ? renderQueuedFileStart(queueDisplay, path.basename(file))
1629
+ ? renderQueuedFileStart(queueDisplay, formatSpecDisplayPath(file))
1615
1630
  : null;
1616
1631
  const buffered = useQueueDisplay
1617
1632
  ? await createBufferedReporter(configPath, runFlags.reporterPath, modeName)
1618
1633
  : null;
1619
- const result = await runLimit(() => run({ ...runFlags, clean: true }, configPath, [file], false, {
1620
- reporter: buffered?.reporter,
1621
- reporterKind: buffered?.reporterKind,
1622
- modeName,
1623
- suiteSelectors,
1624
- emitRunComplete: false,
1625
- fileSummaryTotal: 1,
1626
- modeSummaryTotal,
1627
- modeSummaryExecuted: 1,
1628
- buildCommandsByFile: { [file]: buildCommandsByFile[file] ?? "" },
1629
- }));
1634
+ let result;
1635
+ try {
1636
+ result = await runLimit(() => run({ ...runFlags, clean: true }, configPath, [file], false, {
1637
+ reporter: buffered?.reporter,
1638
+ reporterKind: buffered?.reporterKind,
1639
+ modeName,
1640
+ suiteSelectors,
1641
+ emitRunComplete: false,
1642
+ fileSummaryTotal: 1,
1643
+ modeSummaryTotal,
1644
+ modeSummaryExecuted: 1,
1645
+ buildCommandsByFile: { [file]: buildCommandsByFile[file] ?? "" },
1646
+ }));
1647
+ }
1648
+ catch (error) {
1649
+ const buildFailure = getBuildFailureErrorLike(error);
1650
+ if (!buildFailure)
1651
+ throw error;
1652
+ result = createBuildFailureRunResult(buildFailure);
1653
+ }
1630
1654
  buffered?.reporter.flush?.();
1631
1655
  results[index] = result;
1632
1656
  if (buffered && token != null) {
@@ -1677,7 +1701,7 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, suiteSe
1677
1701
  const buildIntervals = [];
1678
1702
  try {
1679
1703
  await runOrderedPool(files, poolWidth, async (file, fileIndex) => {
1680
- const fileName = path.basename(file);
1704
+ const fileName = formatSpecDisplayPath(file);
1681
1705
  const token = useQueueDisplay
1682
1706
  ? renderQueuedFileStart(queueDisplay, fileName)
1683
1707
  : null;
@@ -1685,28 +1709,37 @@ async function runRuntimeMatrixParallel(runFlags, configPath, selectors, suiteSe
1685
1709
  const modeTimes = modes.map(() => "...");
1686
1710
  for (let i = 0; i < modes.length; i++) {
1687
1711
  const modeName = modes[i];
1688
- const buildStartedAt = Date.now();
1689
- await buildFileForMode({
1690
- configPath,
1691
- file,
1692
- modeName,
1693
- buildFeatureToggles: {},
1694
- buildPool,
1695
- });
1696
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1697
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, {});
1698
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1699
- const result = await run(runFlags, configPath, [file], false, {
1700
- reporter: silentReporter,
1701
- reporterKind: "default",
1702
- suiteSelectors,
1703
- emitRunStart: false,
1704
- emitRunComplete: false,
1705
- logFileName: `run.${artifactKey}.log.json`,
1706
- coverageFileName: `coverage.${artifactKey}.log.json`,
1707
- buildCommand: formatBuildInvocation(buildInvocation),
1708
- modeName,
1709
- });
1712
+ let result;
1713
+ try {
1714
+ const buildStartedAt = Date.now();
1715
+ await buildFileForMode({
1716
+ configPath,
1717
+ file,
1718
+ modeName,
1719
+ buildFeatureToggles: {},
1720
+ buildPool,
1721
+ });
1722
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1723
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, {});
1724
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1725
+ result = await run(runFlags, configPath, [file], false, {
1726
+ reporter: silentReporter,
1727
+ reporterKind: "default",
1728
+ suiteSelectors,
1729
+ emitRunStart: false,
1730
+ emitRunComplete: false,
1731
+ logFileName: `run.${artifactKey}.log.json`,
1732
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1733
+ buildCommand: formatBuildInvocation(buildInvocation),
1734
+ modeName,
1735
+ });
1736
+ }
1737
+ catch (error) {
1738
+ const buildFailure = getBuildFailureErrorLike(error);
1739
+ if (!buildFailure)
1740
+ throw error;
1741
+ result = createBuildFailureRunResult(buildFailure);
1742
+ }
1710
1743
  modeTimes[i] = formatMatrixModeTime(result.stats.time);
1711
1744
  fileResults.push(result);
1712
1745
  }
@@ -1781,32 +1814,41 @@ async function runTestSingleParallel(runFlags, configPath, selectors, suiteSelec
1781
1814
  try {
1782
1815
  await runOrderedPool(files, poolWidth, async (file, index) => {
1783
1816
  const token = useQueueDisplay
1784
- ? renderQueuedFileStart(queueDisplay, path.basename(file))
1817
+ ? renderQueuedFileStart(queueDisplay, formatSpecDisplayPath(file))
1785
1818
  : null;
1786
- const buildStartedAt = Date.now();
1787
- await buildFileForMode({
1788
- configPath,
1789
- file,
1790
- modeName,
1791
- buildFeatureToggles,
1792
- buildPool,
1793
- });
1794
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1795
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1796
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1797
1819
  const buffered = useQueueDisplay
1798
1820
  ? await createBufferedReporter(configPath, runFlags.reporterPath, modeName)
1799
1821
  : null;
1800
- const result = await run({ ...runFlags, clean: true }, configPath, [file], false, {
1801
- reporter: buffered?.reporter,
1802
- reporterKind: buffered?.reporterKind,
1803
- suiteSelectors,
1804
- emitRunComplete: false,
1805
- logFileName: `test.${artifactKey}.log.json`,
1806
- coverageFileName: `coverage.${artifactKey}.log.json`,
1807
- buildCommand: formatBuildInvocation(buildInvocation),
1808
- modeName,
1809
- });
1822
+ let result;
1823
+ try {
1824
+ const buildStartedAt = Date.now();
1825
+ await buildFileForMode({
1826
+ configPath,
1827
+ file,
1828
+ modeName,
1829
+ buildFeatureToggles,
1830
+ buildPool,
1831
+ });
1832
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1833
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1834
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1835
+ result = await run({ ...runFlags, clean: true }, configPath, [file], false, {
1836
+ reporter: buffered?.reporter,
1837
+ reporterKind: buffered?.reporterKind,
1838
+ suiteSelectors,
1839
+ emitRunComplete: false,
1840
+ logFileName: `test.${artifactKey}.log.json`,
1841
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1842
+ buildCommand: formatBuildInvocation(buildInvocation),
1843
+ modeName,
1844
+ });
1845
+ }
1846
+ catch (error) {
1847
+ const buildFailure = getBuildFailureErrorLike(error);
1848
+ if (!buildFailure)
1849
+ throw error;
1850
+ result = createBuildFailureRunResult(buildFailure);
1851
+ }
1810
1852
  buffered?.reporter.flush?.();
1811
1853
  results[index] = result;
1812
1854
  if (buffered && token != null) {
@@ -1882,7 +1924,7 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
1882
1924
  const buildIntervals = [];
1883
1925
  try {
1884
1926
  await runOrderedPool(files, poolWidth, async (file, fileIndex) => {
1885
- const fileName = path.basename(file);
1927
+ const fileName = formatSpecDisplayPath(file);
1886
1928
  const token = useQueueDisplay
1887
1929
  ? renderQueuedFileStart(queueDisplay, fileName)
1888
1930
  : null;
@@ -1890,28 +1932,37 @@ async function runTestMatrixParallel(runFlags, configPath, selectors, suiteSelec
1890
1932
  const modeTimes = modes.map(() => "...");
1891
1933
  for (let i = 0; i < modes.length; i++) {
1892
1934
  const modeName = modes[i];
1893
- const buildStartedAt = Date.now();
1894
- await buildFileForMode({
1895
- configPath,
1896
- file,
1897
- modeName,
1898
- buildFeatureToggles,
1899
- buildPool,
1900
- });
1901
- buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1902
- const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1903
- const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1904
- const result = await run(runFlags, configPath, [file], false, {
1905
- reporter: silentReporter,
1906
- reporterKind: "default",
1907
- suiteSelectors,
1908
- emitRunStart: false,
1909
- emitRunComplete: false,
1910
- logFileName: `test.${artifactKey}.log.json`,
1911
- coverageFileName: `coverage.${artifactKey}.log.json`,
1912
- buildCommand: formatBuildInvocation(buildInvocation),
1913
- modeName,
1914
- });
1935
+ let result;
1936
+ try {
1937
+ const buildStartedAt = Date.now();
1938
+ await buildFileForMode({
1939
+ configPath,
1940
+ file,
1941
+ modeName,
1942
+ buildFeatureToggles,
1943
+ buildPool,
1944
+ });
1945
+ buildIntervals.push({ start: buildStartedAt, end: Date.now() });
1946
+ const buildInvocation = await getBuildInvocationPreview(configPath, file, modeName, buildFeatureToggles);
1947
+ const artifactKey = resolvePerFileArtifactKey(file, duplicateSpecBasenames);
1948
+ result = await run(runFlags, configPath, [file], false, {
1949
+ reporter: silentReporter,
1950
+ reporterKind: "default",
1951
+ suiteSelectors,
1952
+ emitRunStart: false,
1953
+ emitRunComplete: false,
1954
+ logFileName: `test.${artifactKey}.log.json`,
1955
+ coverageFileName: `coverage.${artifactKey}.log.json`,
1956
+ buildCommand: formatBuildInvocation(buildInvocation),
1957
+ modeName,
1958
+ });
1959
+ }
1960
+ catch (error) {
1961
+ const buildFailure = getBuildFailureErrorLike(error);
1962
+ if (!buildFailure)
1963
+ throw error;
1964
+ result = createBuildFailureRunResult(buildFailure);
1965
+ }
1915
1966
  modeTimes[i] = formatMatrixModeTime(result.stats.time);
1916
1967
  fileResults.push(result);
1917
1968
  }
@@ -2006,7 +2057,9 @@ async function runFuzzMatrixResultsParallel(configPath, selectors, fuzzerSelecto
2006
2057
  async function runFuzzMatrixResults(configPath, selectors, fuzzerSelectors, modes, overrides, reporter) {
2007
2058
  const results = [];
2008
2059
  for (const modeName of modes) {
2009
- const files = await resolveSelectedFuzzFiles(configPath, selectors, [modeName]);
2060
+ const files = await resolveSelectedFuzzFiles(configPath, selectors, [
2061
+ modeName,
2062
+ ]);
2010
2063
  if (!files.length) {
2011
2064
  continue;
2012
2065
  }
@@ -2212,6 +2265,133 @@ function buildSingleModeSummary(stats, snapshotSummary, totalModes) {
2212
2265
  total,
2213
2266
  };
2214
2267
  }
2268
+ function createBuildFailureRunResult(error) {
2269
+ const message = formatBuildFailureMessage(error);
2270
+ const report = {
2271
+ file: error.file,
2272
+ modeName: error.mode,
2273
+ suites: [
2274
+ {
2275
+ file: error.file,
2276
+ description: formatSpecDisplayPath(error.file),
2277
+ depth: 0,
2278
+ kind: "build-error",
2279
+ verdict: "fail",
2280
+ time: {
2281
+ start: 0,
2282
+ end: 0,
2283
+ },
2284
+ suites: [],
2285
+ logs: [],
2286
+ tests: [
2287
+ {
2288
+ order: 0,
2289
+ type: "build-error",
2290
+ verdict: "fail",
2291
+ left: null,
2292
+ right: null,
2293
+ instr: "build failed before the test could run",
2294
+ message,
2295
+ location: "",
2296
+ },
2297
+ ],
2298
+ modeName: error.mode,
2299
+ buildCommand: formatBuildInvocation(error.invocation),
2300
+ runCommand: "",
2301
+ },
2302
+ ],
2303
+ coverage: {
2304
+ total: 0,
2305
+ covered: 0,
2306
+ uncovered: 0,
2307
+ percent: 100,
2308
+ points: [],
2309
+ },
2310
+ runCommand: "",
2311
+ buildCommand: formatBuildInvocation(error.invocation),
2312
+ snapshotSummary: {
2313
+ matched: 0,
2314
+ created: 0,
2315
+ updated: 0,
2316
+ failed: 0,
2317
+ },
2318
+ };
2319
+ return {
2320
+ failed: true,
2321
+ buildTime: 0,
2322
+ stats: {
2323
+ passedFiles: 0,
2324
+ failedFiles: 1,
2325
+ skippedFiles: 0,
2326
+ passedSuites: 0,
2327
+ failedSuites: 1,
2328
+ skippedSuites: 0,
2329
+ passedTests: 0,
2330
+ failedTests: 1,
2331
+ skippedTests: 0,
2332
+ time: 0,
2333
+ failedEntries: [
2334
+ {
2335
+ ...report.suites[0],
2336
+ file: error.file,
2337
+ modeName: error.mode,
2338
+ buildCommand: report.buildCommand,
2339
+ runCommand: "",
2340
+ },
2341
+ ],
2342
+ },
2343
+ snapshotSummary: report.snapshotSummary,
2344
+ coverageSummary: {
2345
+ enabled: false,
2346
+ showPoints: false,
2347
+ total: 0,
2348
+ covered: 0,
2349
+ uncovered: 0,
2350
+ percent: 100,
2351
+ files: [],
2352
+ },
2353
+ reports: [report],
2354
+ };
2355
+ }
2356
+ function formatBuildFailureMessage(error) {
2357
+ const parts = [];
2358
+ const stderr = error.stderr.trim();
2359
+ const stdout = error.stdout.trim();
2360
+ if (stderr.length) {
2361
+ parts.push(`stderr:\n${stderr}`);
2362
+ }
2363
+ if (stdout.length) {
2364
+ parts.push(`stdout:\n${stdout}`);
2365
+ }
2366
+ if (error.crashLogPath.length) {
2367
+ parts.push(`Crash log:\n${error.crashLogPath}`);
2368
+ }
2369
+ return parts.join("\n\n") || "build failed with no compiler output";
2370
+ }
2371
+ function getBuildFailureErrorLike(error) {
2372
+ if (error instanceof BuildFailureError) {
2373
+ return error;
2374
+ }
2375
+ if (!error || typeof error != "object") {
2376
+ return null;
2377
+ }
2378
+ const candidate = error;
2379
+ if (candidate.name != "BuildFailureError") {
2380
+ return null;
2381
+ }
2382
+ if (typeof candidate.file != "string" ||
2383
+ typeof candidate.mode != "string" ||
2384
+ typeof candidate.stdout != "string" ||
2385
+ typeof candidate.stderr != "string" ||
2386
+ typeof candidate.crashLogPath != "string" ||
2387
+ !candidate.invocation ||
2388
+ typeof candidate.invocation != "object" ||
2389
+ typeof candidate.invocation.command != "string" ||
2390
+ !Array.isArray(candidate.invocation.args)) {
2391
+ return null;
2392
+ }
2393
+ return candidate;
2394
+ }
2215
2395
  function applyConfiguredFileTotalToStats(stats, fileSummaryTotal) {
2216
2396
  const total = Math.max(fileSummaryTotal, 0);
2217
2397
  const executed = stats.failedFiles + stats.passedFiles + stats.skippedFiles;
@@ -2320,7 +2500,7 @@ async function buildNoTestFilesMatchedError(configPath, selectors, includeFuzz =
2320
2500
  if (configuredFiles.length) {
2321
2501
  const sample = configuredFiles
2322
2502
  .slice(0, 5)
2323
- .map((file) => path.basename(file))
2503
+ .map((file) => formatSpecDisplayPath(file))
2324
2504
  .join(", ");
2325
2505
  lines.push(`Configured specs (${configuredFiles.length}): ${sample}${configuredFiles.length > 5 ? ", ..." : ""}`);
2326
2506
  }
@@ -2628,7 +2808,7 @@ async function handleMissingWebBrowsers(missing) {
2628
2808
  const details = "no web-capable browser was found in PATH, BROWSER, or Playwright cache";
2629
2809
  const selected = choosePreferredBrowserInstall(missing);
2630
2810
  const installCommand = selected == "webkit"
2631
- ? 'npx -y playwright install webkit'
2811
+ ? "npx -y playwright install webkit"
2632
2812
  : `npx -y playwright install ${selected}`;
2633
2813
  if (!canPromptForWebInstall()) {
2634
2814
  throw new Error(`web target requires a browser for mode(s) ${scope}; ${details}. Export BROWSER or install one with "${installCommand}".`);
@@ -2748,10 +2928,10 @@ function isPlaywrightBrowserExecutable(browser) {
2748
2928
  function extractPlaywrightDepsSummary(result) {
2749
2929
  const stdout = typeof result.stdout == "string"
2750
2930
  ? result.stdout
2751
- : result.stdout?.toString("utf8") ?? "";
2931
+ : (result.stdout?.toString("utf8") ?? "");
2752
2932
  const stderr = typeof result.stderr == "string"
2753
2933
  ? result.stderr
2754
- : result.stderr?.toString("utf8") ?? "";
2934
+ : (result.stderr?.toString("utf8") ?? "");
2755
2935
  return [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
2756
2936
  }
2757
2937
  function canPromptForWebInstall() {
@@ -2886,9 +3066,7 @@ function resolveSystemBrowserExecutable(browser) {
2886
3066
  "Firefox.app/Contents/MacOS/firefox",
2887
3067
  "Firefox Developer Edition.app/Contents/MacOS/firefox",
2888
3068
  ],
2889
- msedge: [
2890
- "Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2891
- ],
3069
+ msedge: ["Microsoft Edge.app/Contents/MacOS/Microsoft Edge"],
2892
3070
  webkit: [],
2893
3071
  };
2894
3072
  for (const root of macSearchRoots) {