@rstest/core 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/3145.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import "node:module";
2
2
  import { __webpack_require__ } from "./rslib-runtime.js";
3
- import node_fs, { existsSync, readFileSync, statSync } from "node:fs";
3
+ import node_fs, { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
4
4
  import { loadConfig, mergeRsbuildConfig } from "@rsbuild/core";
5
5
  import { stripVTControlCharacters } from "node:util";
6
6
  import promises from "node:fs/promises";
7
7
  import node_path, { dirname as external_node_path_dirname, resolve as external_node_path_resolve } from "node:path";
8
8
  import { createRequire } from "node:module";
9
9
  import node_process from "node:process";
10
- import { basename, isTTY, dirname as pathe_M_eThtNZ_dirname, resolve as pathe_M_eThtNZ_resolve, relative, getAbsolutePath, join, bgColor, formatRootStr, determineAgent, logger as logger_logger, castArray, dist_m, prettyTime, isDebug, color, isAbsolute, getTaskNameWithPrefix, normalize, formatError } from "./6830.js";
10
+ import { basename, isTTY, dirname as pathe_M_eThtNZ_dirname, resolve as pathe_M_eThtNZ_resolve, relative, getAbsolutePath, join, bgColor, formatRootStr, determineAgent, logger as logger_logger, castArray, dist_m, prettyTime, isDebug, color as logger_color, isAbsolute, getTaskNameWithPrefix, normalize, formatError } from "./6830.js";
11
11
  import { isDynamicPattern, glob, DEFAULT_CONFIG_NAME, prettyTestPath, globalApis, TEMP_RSTEST_OUTPUT_DIR_GLOB, formatTestPath, filterProjects, DEFAULT_CONFIG_EXTENSIONS, TS_CONFIG_FILE } from "./4411.js";
12
12
  import { posix } from "./7011.js";
13
13
  import { parse as stack_trace_parser_esm_parse } from "./1672.js";
@@ -587,7 +587,7 @@ function prepareCli() {
587
587
  if (!npm_execpath || npm_execpath.includes('npx-cli.js') || npm_execpath.includes('.bun')) logger_logger.log();
588
588
  }
589
589
  function showRstest() {
590
- logger_logger.greet(" Rstest v0.9.2");
590
+ logger_logger.greet(" Rstest v0.9.3");
591
591
  logger_logger.log('');
592
592
  }
593
593
  const applyCommonOptions = (cli)=>{
@@ -638,7 +638,7 @@ const runRest = async ({ options, filters, command })=>{
638
638
  function setupCommands() {
639
639
  const cli = cac('rstest');
640
640
  cli.help();
641
- cli.version("0.9.2");
641
+ cli.version("0.9.3");
642
642
  applyCommonOptions(cli);
643
643
  cli.command('[...filters]', 'run tests').option('-w, --watch', 'Run tests in watch mode').action(async (filters, options)=>{
644
644
  if (!determineAgent().isAgent) showRstest();
@@ -692,6 +692,27 @@ function setupCommands() {
692
692
  process.exit(1);
693
693
  }
694
694
  });
695
+ cli.command('merge-reports [path]', 'Merge blob reports from multiple shards into a unified report').option('--cleanup', 'Remove blob reports directory after merging').action(async (path, options)=>{
696
+ if (!determineAgent().isAgent) showRstest();
697
+ try {
698
+ const { initCli } = await Promise.resolve(init_namespaceObject);
699
+ const { config, configFilePath, projects } = await initCli(options);
700
+ const { createRstest } = await Promise.resolve(src_core_namespaceObject);
701
+ const rstest = createRstest({
702
+ config,
703
+ configFilePath,
704
+ projects
705
+ }, 'merge-reports', []);
706
+ await rstest.mergeReports({
707
+ path,
708
+ cleanup: options.cleanup
709
+ });
710
+ } catch (err) {
711
+ logger_logger.error('Failed to merge reports.');
712
+ logger_logger.error(formatError(err));
713
+ process.exit(1);
714
+ }
715
+ });
695
716
  cli.command('init [project]', 'Initialize rstest configuration').option('--yes', 'Use default options (non-interactive)').action(async (project, options)=>{
696
717
  try {
697
718
  let selectedProject = project;
@@ -709,7 +730,7 @@ function setupCommands() {
709
730
  ]
710
731
  });
711
732
  if (isCancel(selected)) {
712
- console.log(color.yellow('Operation cancelled.'));
733
+ console.log(logger_color.yellow('Operation cancelled.'));
713
734
  process.exit(0);
714
735
  }
715
736
  selectedProject = selected;
@@ -736,7 +757,7 @@ const resolveConfigPath = (root, customConfig)=>{
736
757
  if (customConfig) {
737
758
  const customConfigPath = isAbsolute(customConfig) ? customConfig : join(root, customConfig);
738
759
  if (node_fs.existsSync(customConfigPath)) return customConfigPath;
739
- throw `Cannot find config file: ${color.dim(customConfigPath)}`;
760
+ throw `Cannot find config file: ${logger_color.dim(customConfigPath)}`;
740
761
  }
741
762
  const configFilePath = findConfig(join(root, DEFAULT_CONFIG_NAME));
742
763
  if (configFilePath) return configFilePath;
@@ -758,18 +779,29 @@ async function config_loadConfig({ cwd = process.cwd(), path, envMode, configLoa
758
779
  loader: configLoader
759
780
  });
760
781
  let config = content;
761
- if (config.extends) {
762
- const extendsConfig = 'function' == typeof config.extends ? await config.extends(Object.freeze({
763
- ...config
764
- })) : config.extends;
765
- delete extendsConfig.projects;
766
- config = mergeRstestConfig(extendsConfig, config);
767
- }
782
+ config = await resolveExtends(config);
768
783
  return {
769
784
  content: config,
770
785
  filePath: configFilePath
771
786
  };
772
787
  }
788
+ const resolveExtendEntry = async (entry, userConfig)=>{
789
+ const resolved = 'function' == typeof entry ? await entry(userConfig) : entry;
790
+ if ('projects' in resolved) {
791
+ const { projects: _projects, ...rest } = resolved;
792
+ return rest;
793
+ }
794
+ return resolved;
795
+ };
796
+ const resolveExtends = async (config)=>{
797
+ if (!config.extends) return config;
798
+ const userConfig = Object.freeze({
799
+ ...config
800
+ });
801
+ const extendsEntries = castArray(config.extends);
802
+ const resolvedExtends = await Promise.all(extendsEntries.map((entry)=>resolveExtendEntry(entry, userConfig)));
803
+ return mergeRstestConfig(...resolvedExtends, config);
804
+ };
773
805
  const mergeProjectConfig = (...configs)=>mergeRstestConfig(...configs);
774
806
  const mergeRstestConfig = (...configs)=>configs.reduce((result, config)=>{
775
807
  const merged = mergeRsbuildConfig(result, {
@@ -848,7 +880,8 @@ const createDefaultConfig = ()=>({
848
880
  provider: 'playwright',
849
881
  browser: 'chromium',
850
882
  headless: dist_m,
851
- strictPort: false
883
+ strictPort: false,
884
+ providerOptions: {}
852
885
  },
853
886
  coverage: {
854
887
  exclude: [
@@ -872,7 +905,8 @@ const createDefaultConfig = ()=>({
872
905
  ],
873
906
  reportsDirectory: './coverage',
874
907
  clean: true,
875
- reportOnFailure: false
908
+ reportOnFailure: false,
909
+ allowExternal: false
876
910
  }
877
911
  });
878
912
  const withDefaultConfig = (config)=>{
@@ -895,7 +929,8 @@ const withDefaultConfig = (config)=>{
895
929
  headless: merged.browser?.headless ?? dist_m,
896
930
  port: merged.browser?.port,
897
931
  strictPort: merged.browser?.strictPort ?? false,
898
- viewport: merged.browser?.viewport
932
+ viewport: merged.browser?.viewport,
933
+ providerOptions: merged.browser?.providerOptions ?? {}
899
934
  };
900
935
  return {
901
936
  ...merged,
@@ -947,7 +982,11 @@ function mergeWithCLIOptions(config, options) {
947
982
  if (void 0 !== options.bail && ('number' == typeof options.bail || 'boolean' == typeof options.bail)) config.bail = Number(options.bail);
948
983
  if (void 0 !== options.coverage) {
949
984
  config.coverage ??= {};
950
- config.coverage.enabled = options.coverage;
985
+ if ('boolean' == typeof options.coverage) config.coverage.enabled = options.coverage;
986
+ else {
987
+ if (void 0 !== options.coverage.enabled) config.coverage.enabled = options.coverage.enabled;
988
+ if (void 0 !== options.coverage.allowExternal) config.coverage.allowExternal = options.coverage.allowExternal;
989
+ }
951
990
  }
952
991
  if (options.exclude) config.exclude = castArray(options.exclude);
953
992
  if (options.include) config.include = castArray(options.include);
@@ -1032,16 +1071,9 @@ async function resolveProjects({ config, root, options }) {
1032
1071
  await Promise.all((rstestConfig.projects || []).map(async (p)=>{
1033
1072
  if ('object' == typeof p) {
1034
1073
  const projectRoot = p.root ? formatRootStr(p.root, root) : root;
1035
- let projectConfig = {
1074
+ const projectConfig = await resolveExtends({
1036
1075
  ...p
1037
- };
1038
- if (projectConfig.extends) {
1039
- const extendsConfig = 'function' == typeof projectConfig.extends ? await projectConfig.extends(Object.freeze({
1040
- ...projectConfig
1041
- })) : projectConfig.extends;
1042
- delete extendsConfig.projects;
1043
- projectConfig = mergeRstestConfig(extendsConfig, projectConfig);
1044
- }
1076
+ });
1045
1077
  projectConfigs.push({
1046
1078
  config: mergeWithCLIOptions({
1047
1079
  root: projectRoot,
@@ -1088,8 +1120,8 @@ async function resolveProjects({ config, root, options }) {
1088
1120
  const projects = await getProjects(config, root).then((p)=>filterProjects(p, options));
1089
1121
  if (!projects.length) {
1090
1122
  let errorMsg = `No projects found, please make sure you have at least one valid project.
1091
- ${color.gray('projects:')} ${JSON.stringify(config.projects, null, 2)}`;
1092
- if (options.project) errorMsg += `\n${color.gray('projectName filter:')} ${JSON.stringify(options.project, null, 2)}`;
1123
+ ${logger_color.gray('projects:')} ${JSON.stringify(config.projects, null, 2)}`;
1124
+ if (options.project) errorMsg += `\n${logger_color.gray('projectName filter:')} ${JSON.stringify(options.project, null, 2)}`;
1093
1125
  throw errorMsg;
1094
1126
  }
1095
1127
  const names = new Set();
@@ -1195,53 +1227,53 @@ function addSnapshotResult(summary, result) {
1195
1227
  summary.total += result.added + result.matched + result.unmatched + result.updated;
1196
1228
  }
1197
1229
  const getSummaryStatusString = (tasks, name = 'tests', showTotal = true)=>{
1198
- if (0 === tasks.length) return color.dim(`no ${name}`);
1230
+ if (0 === tasks.length) return logger_color.dim(`no ${name}`);
1199
1231
  const passed = tasks.filter((result)=>'pass' === result.status);
1200
1232
  const failed = tasks.filter((result)=>'fail' === result.status);
1201
1233
  const skipped = tasks.filter((result)=>'skip' === result.status);
1202
1234
  const todo = tasks.filter((result)=>'todo' === result.status);
1203
1235
  const status = [
1204
- failed.length ? color.bold(color.red(`${failed.length} failed`)) : null,
1205
- passed.length ? color.bold(color.green(`${passed.length} passed`)) : null,
1206
- skipped.length ? color.yellow(`${skipped.length} skipped`) : null,
1207
- todo.length ? color.gray(`${todo.length} todo`) : null
1236
+ failed.length ? logger_color.bold(logger_color.red(`${failed.length} failed`)) : null,
1237
+ passed.length ? logger_color.bold(logger_color.green(`${passed.length} passed`)) : null,
1238
+ skipped.length ? logger_color.yellow(`${skipped.length} skipped`) : null,
1239
+ todo.length ? logger_color.gray(`${todo.length} todo`) : null
1208
1240
  ].filter(Boolean);
1209
- return status.join(color.dim(' | ')) + (showTotal && status.length > 1 ? color.gray(` (${tasks.length})`) : '');
1241
+ return status.join(logger_color.dim(' | ')) + (showTotal && status.length > 1 ? logger_color.gray(` (${tasks.length})`) : '');
1210
1242
  };
1211
1243
  const printSnapshotSummaryLog = (snapshots, rootDir)=>{
1212
1244
  const summary = [];
1213
- if (snapshots.added) summary.push(color.bold(color.green(`${snapshots.added} written`)));
1214
- if (snapshots.unmatched) summary.push(color.bold(color.red(`${snapshots.unmatched} failed`)));
1215
- if (snapshots.updated) summary.push(color.bold(color.green(`${snapshots.updated} updated `)));
1216
- if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(color.bold(color.green(`${snapshots.filesRemoved} files removed `)));
1217
- else summary.push(color.bold(color.yellow(`${snapshots.filesRemoved} files obsolete `)));
1245
+ if (snapshots.added) summary.push(logger_color.bold(logger_color.green(`${snapshots.added} written`)));
1246
+ if (snapshots.unmatched) summary.push(logger_color.bold(logger_color.red(`${snapshots.unmatched} failed`)));
1247
+ if (snapshots.updated) summary.push(logger_color.bold(logger_color.green(`${snapshots.updated} updated `)));
1248
+ if (snapshots.filesRemoved) if (snapshots.didUpdate) summary.push(logger_color.bold(logger_color.green(`${snapshots.filesRemoved} files removed `)));
1249
+ else summary.push(logger_color.bold(logger_color.yellow(`${snapshots.filesRemoved} files obsolete `)));
1218
1250
  if (snapshots.filesRemovedList?.length) {
1219
1251
  const [head, ...tail] = snapshots.filesRemovedList;
1220
- summary.push(`${color.gray("➜")} ${formatTestPath(rootDir, head)}`);
1252
+ summary.push(`${logger_color.gray("➜")} ${formatTestPath(rootDir, head)}`);
1221
1253
  for (const key of tail)summary.push(` ${formatTestPath(rootDir, key)}`);
1222
1254
  }
1223
1255
  if (snapshots.unchecked) {
1224
- if (snapshots.didUpdate) summary.push(color.bold(color.green(`${snapshots.unchecked} removed`)));
1225
- else summary.push(color.bold(color.yellow(`${snapshots.unchecked} obsolete`)));
1256
+ if (snapshots.didUpdate) summary.push(logger_color.bold(logger_color.green(`${snapshots.unchecked} removed`)));
1257
+ else summary.push(logger_color.bold(logger_color.yellow(`${snapshots.unchecked} obsolete`)));
1226
1258
  for (const uncheckedFile of snapshots.uncheckedKeysByFile){
1227
- summary.push(`${color.gray("➜")} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
1259
+ summary.push(`${logger_color.gray("➜")} ${formatTestPath(rootDir, uncheckedFile.filePath)}`);
1228
1260
  for (const key of uncheckedFile.keys)summary.push(` ${key}`);
1229
1261
  }
1230
1262
  }
1231
1263
  for (const [index, snapshot] of summary.entries()){
1232
1264
  const title = 0 === index ? 'Snapshots' : '';
1233
- logger_logger.log(`${color.gray(title.padStart(12))} ${snapshot}`);
1265
+ logger_logger.log(`${logger_color.gray(title.padStart(12))} ${snapshot}`);
1234
1266
  }
1235
1267
  };
1236
- const TestFileSummaryLabel = color.gray('Test Files'.padStart(11));
1237
- const TestSummaryLabel = color.gray('Tests'.padStart(11));
1238
- const DurationLabel = color.gray('Duration'.padStart(11));
1268
+ const TestFileSummaryLabel = logger_color.gray('Test Files'.padStart(11));
1269
+ const TestSummaryLabel = logger_color.gray('Tests'.padStart(11));
1270
+ const DurationLabel = logger_color.gray('Duration'.padStart(11));
1239
1271
  const printSummaryLog = ({ results, testResults, snapshotSummary, duration, rootPath })=>{
1240
1272
  logger_logger.log('');
1241
1273
  printSnapshotSummaryLog(snapshotSummary, rootPath);
1242
1274
  logger_logger.log(`${TestFileSummaryLabel} ${getSummaryStatusString(results)}`);
1243
1275
  logger_logger.log(`${TestSummaryLabel} ${getSummaryStatusString(testResults)}`);
1244
- logger_logger.log(`${DurationLabel} ${prettyTime(duration.totalTime)} ${color.gray(`(build ${prettyTime(duration.buildTime)}, tests ${prettyTime(duration.testTime)})`)}`);
1276
+ logger_logger.log(`${DurationLabel} ${prettyTime(duration.totalTime)} ${logger_color.gray(`(build ${prettyTime(duration.buildTime)}, tests ${prettyTime(duration.testTime)})`)}`);
1245
1277
  logger_logger.log('');
1246
1278
  };
1247
1279
  const printSummaryErrorLogs = async ({ testResults, results, rootPath, unhandledErrors, getSourcemap, filterRerunTestPaths })=>{
@@ -1251,7 +1283,7 @@ const printSummaryErrorLogs = async ({ testResults, results, rootPath, unhandled
1251
1283
  ];
1252
1284
  if (0 === failedTests.length && !unhandledErrors?.length) return;
1253
1285
  logger_logger.stderr('');
1254
- logger_logger.stderr(color.bold('Summary of all failing tests:'));
1286
+ logger_logger.stderr(logger_color.bold('Summary of all failing tests:'));
1255
1287
  logger_logger.stderr('');
1256
1288
  const { printError } = await Promise.resolve(error_namespaceObject);
1257
1289
  for (const error of unhandledErrors || []){
@@ -1261,7 +1293,7 @@ const printSummaryErrorLogs = async ({ testResults, results, rootPath, unhandled
1261
1293
  for (const test of failedTests){
1262
1294
  const relativePath = posix.relative(rootPath, test.testPath);
1263
1295
  const nameStr = getTaskNameWithPrefix(test);
1264
- logger_logger.stderr(`${bgColor('bgRed', ' FAIL ')} ${prettyTestPath(relativePath)} ${nameStr.length ? `${color.dim(">")} ${nameStr}` : ''}`);
1296
+ logger_logger.stderr(`${bgColor('bgRed', ' FAIL ')} ${prettyTestPath(relativePath)} ${nameStr.length ? `${logger_color.dim(">")} ${nameStr}` : ''}`);
1265
1297
  if (test.errors) {
1266
1298
  const { printError } = await Promise.resolve(error_namespaceObject);
1267
1299
  for (const error of test.errors)await printError(error, getSourcemap, rootPath);
@@ -1418,14 +1450,14 @@ class StatusRenderer {
1418
1450
  const relativePath = relative(this.rootPath, module);
1419
1451
  summary.push(`${bgColor('bgYellow', ' RUNS ')} ${prettyTestPath(relativePath)}`);
1420
1452
  if (runningTests.length && shouldDisplayRunningTests(runningTests)) {
1421
- let caseLog = ` ${color.gray("➜")} ${getTaskNameWithPrefix(runningTests[0])} ${color.magenta(prettyTime(now - runningTests[0].startTime))}`;
1422
- if (runningTests.length > 1) caseLog += color.gray(` and ${runningTests.length - 1} more cases`);
1453
+ let caseLog = ` ${logger_color.gray("➜")} ${getTaskNameWithPrefix(runningTests[0])} ${logger_color.magenta(prettyTime(now - runningTests[0].startTime))}`;
1454
+ if (runningTests.length > 1) caseLog += logger_color.gray(` and ${runningTests.length - 1} more cases`);
1423
1455
  summary.push(caseLog);
1424
1456
  }
1425
1457
  }
1426
1458
  summary.push('');
1427
1459
  if (0 === testModules.length) summary.push(`${TestFileSummaryLabel} ${runningModules.size} total`);
1428
- else summary.push(`${TestFileSummaryLabel} ${getSummaryStatusString(testModules, '', false)} ${color.dim('|')} ${runningModules.size + testModules.length} total`);
1460
+ else summary.push(`${TestFileSummaryLabel} ${getSummaryStatusString(testModules, '', false)} ${logger_color.dim('|')} ${runningModules.size + testModules.length} total`);
1429
1461
  const testResults = Array.from(runningModules.values()).flatMap(({ results })=>results).concat(testModules.flatMap((mod)=>mod.results));
1430
1462
  if (testResults.length) summary.push(`${TestSummaryLabel} ${getSummaryStatusString(testResults, '', false)}`);
1431
1463
  summary.push(`${DurationLabel} ${prettyTime(Date.now() - this.startTime)}`);
@@ -1453,10 +1485,10 @@ const statusStr = {
1453
1485
  skip: '-'
1454
1486
  };
1455
1487
  const statusColor = {
1456
- fail: color.red,
1457
- pass: color.green,
1458
- todo: color.gray,
1459
- skip: color.gray
1488
+ fail: logger_color.red,
1489
+ pass: logger_color.green,
1490
+ todo: logger_color.gray,
1491
+ skip: logger_color.gray
1460
1492
  };
1461
1493
  const statusColorfulStr = {
1462
1494
  fail: statusColor.fail(statusStr.fail),
@@ -1467,23 +1499,23 @@ const statusColorfulStr = {
1467
1499
  const logCase = (result, options)=>{
1468
1500
  const isSlowCase = (result.duration || 0) > options.slowTestThreshold;
1469
1501
  if (options.hideSkippedTests && 'skip' === result.status) return;
1470
- const icon = isSlowCase && 'pass' === result.status ? color.yellow(statusStr[result.status]) : statusColorfulStr[result.status];
1502
+ const icon = isSlowCase && 'pass' === result.status ? logger_color.yellow(statusStr[result.status]) : statusColorfulStr[result.status];
1471
1503
  const nameStr = getTaskNameWithPrefix(result);
1472
1504
  const duration = void 0 !== result.duration ? ` (${prettyTime(result.duration)})` : '';
1473
- const retry = result.retryCount ? color.yellow(` (retry x${result.retryCount})`) : '';
1474
- const heap = result.heap ? ` ${color.magenta(formatHeapUsed(result.heap))}` : '';
1475
- logger_logger.log(` ${icon} ${nameStr}${color.gray(duration)}${retry}${heap}`);
1476
- if (result.errors) for (const error of result.errors)logger_logger.log(color.red(` ${error.message}`));
1505
+ const retry = result.retryCount ? logger_color.yellow(` (retry x${result.retryCount})`) : '';
1506
+ const heap = result.heap ? ` ${logger_color.magenta(formatHeapUsed(result.heap))}` : '';
1507
+ logger_logger.log(` ${icon} ${nameStr}${logger_color.gray(duration)}${retry}${heap}`);
1508
+ if (result.errors) for (const error of result.errors)logger_logger.log(logger_color.red(` ${error.message}`));
1477
1509
  };
1478
1510
  const formatHeapUsed = (heap)=>`${Math.floor(heap / 1024 / 1024)} MB heap used`;
1479
1511
  const logFileTitle = (test, relativePath, alwaysShowTime = false, showProjectName = false)=>{
1480
- let title = ` ${color.bold(statusColorfulStr[test.status])}`;
1512
+ let title = ` ${logger_color.bold(statusColorfulStr[test.status])}`;
1481
1513
  if (showProjectName && test.project) title += ` ${statusColor[test.status](`[${test.project}]`)}`;
1482
1514
  title += ` ${prettyTestPath(relativePath)}`;
1483
- const formatDuration = (duration)=>color.green(prettyTime(duration));
1484
- title += ` ${color.gray(`(${test.results.length})`)}`;
1515
+ const formatDuration = (duration)=>logger_color.green(prettyTime(duration));
1516
+ title += ` ${logger_color.gray(`(${test.results.length})`)}`;
1485
1517
  if (alwaysShowTime) title += ` ${formatDuration(test.duration)}`;
1486
- if (test.heap) title += ` ${color.magenta(formatHeapUsed(test.heap))}`;
1518
+ if (test.heap) title += ` ${logger_color.magenta(formatHeapUsed(test.heap))}`;
1487
1519
  logger_logger.log(title);
1488
1520
  };
1489
1521
  class DefaultReporter {
@@ -1538,7 +1570,7 @@ class DefaultReporter {
1538
1570
  } else titles.push(testPath);
1539
1571
  const logOutput = 'stdout' === log.type ? logger_logger.log : logger_logger.stderr;
1540
1572
  logOutput('');
1541
- logOutput(`${log.name}${color.gray(color.dim(` | ${titles.join(color.gray(color.dim(' | ')))}`))}`);
1573
+ logOutput(`${log.name}${logger_color.gray(logger_color.dim(` | ${titles.join(logger_color.gray(logger_color.dim(' | ')))}`))}`);
1542
1574
  logOutput(log.content);
1543
1575
  logOutput('');
1544
1576
  }
@@ -1565,6 +1597,44 @@ class DefaultReporter {
1565
1597
  });
1566
1598
  }
1567
1599
  }
1600
+ const DEFAULT_OUTPUT_DIR = '.rstest-reports';
1601
+ class BlobReporter {
1602
+ config;
1603
+ outputDir;
1604
+ consoleLogs = [];
1605
+ constructor({ rootPath, config, options }){
1606
+ this.config = config;
1607
+ this.outputDir = options?.outputDir ? join(rootPath, options.outputDir) : join(rootPath, DEFAULT_OUTPUT_DIR);
1608
+ }
1609
+ onUserConsoleLog(log) {
1610
+ this.consoleLogs.push(log);
1611
+ }
1612
+ async onTestRunEnd({ results, testResults, duration, snapshotSummary, unhandledErrors }) {
1613
+ const shard = this.config.shard;
1614
+ const fileName = shard ? `blob-${shard.index}-${shard.count}.json` : 'blob.json';
1615
+ const blobData = {
1616
+ version: "0.9.3",
1617
+ shard: shard ? {
1618
+ index: shard.index,
1619
+ count: shard.count
1620
+ } : void 0,
1621
+ results,
1622
+ testResults,
1623
+ duration,
1624
+ snapshotSummary,
1625
+ unhandledErrors: unhandledErrors?.map((e)=>({
1626
+ message: e.message,
1627
+ stack: e.stack,
1628
+ name: e.name
1629
+ })),
1630
+ consoleLogs: this.consoleLogs.length > 0 ? this.consoleLogs : void 0
1631
+ };
1632
+ mkdirSync(this.outputDir, {
1633
+ recursive: true
1634
+ });
1635
+ writeFileSync(join(this.outputDir, fileName), JSON.stringify(blobData), 'utf-8');
1636
+ }
1637
+ }
1568
1638
  class GithubActionsReporter {
1569
1639
  onWritePath;
1570
1640
  rootPath;
@@ -1619,15 +1689,15 @@ function escapeData(s) {
1619
1689
  }
1620
1690
  function ansiRegex({ onlyFirst = false } = {}) {
1621
1691
  const ST = '(?:\\u0007|\\u001B\\u005C|\\u009C)';
1622
- const pattern = [
1623
- `[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?${ST})`,
1624
- '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
1625
- ].join('|');
1692
+ const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
1693
+ const csi = '[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]';
1694
+ const pattern = `${osc}|${csi}`;
1626
1695
  return new RegExp(pattern, onlyFirst ? void 0 : 'g');
1627
1696
  }
1628
1697
  const regex = ansiRegex();
1629
1698
  function stripAnsi(string) {
1630
1699
  if ('string' != typeof string) throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
1700
+ if (!string.includes('\u001B') && !string.includes('\u009B')) return string;
1631
1701
  return string.replace(regex, '');
1632
1702
  }
1633
1703
  const schemeRegex = /^[\w+.-]+:\/\//;
@@ -1941,7 +2011,7 @@ const hintNotDefinedError = (message)=>{
1941
2011
  'jest',
1942
2012
  'vitest'
1943
2013
  ].includes(varName)) return message.replace(`${varName} is not defined`, `${varName} is not defined. Did you mean rstest?`);
1944
- if ('React' === varName) return message.replace(`${varName} is not defined`, `${varName} is not defined. Did you forget to install "${color.yellow('@rsbuild/plugin-react')}" plugin?`);
2014
+ if ('React' === varName) return message.replace(`${varName} is not defined`, `${varName} is not defined. Did you forget to install "${logger_color.yellow('@rsbuild/plugin-react')}" plugin?`);
1945
2015
  }
1946
2016
  return message;
1947
2017
  };
@@ -1951,14 +2021,14 @@ async function error_printError(error, getSourcemap, rootPath) {
1951
2021
  const tips = [
1952
2022
  'Error: not support import `vitest` in Rstest test environment.\n',
1953
2023
  'Solution:',
1954
- ` - Update your code to use imports from "${color.yellow('@rstest/core')}" instead of "${color.yellow('vitest')}".`,
2024
+ ` - Update your code to use imports from "${logger_color.yellow('@rstest/core')}" instead of "${logger_color.yellow('vitest')}".`,
1955
2025
  ' - Enable `globals` configuration and use global API.'
1956
2026
  ];
1957
- logger_logger.stderr(`${color.red(tips.join('\n'))}\n`);
2027
+ logger_logger.stderr(`${logger_color.red(tips.join('\n'))}\n`);
1958
2028
  return;
1959
2029
  }
1960
2030
  if (error.message.includes('is not defined')) error.message = hintNotDefinedError(error.message);
1961
- logger_logger.stderr(`${color.red(color.bold(errorName))}${color.red(`: ${error.message}`)}\n`);
2031
+ logger_logger.stderr(`${logger_color.red(logger_color.bold(errorName))}${logger_color.red(`: ${error.message}`)}\n`);
1962
2032
  if (error.diff) {
1963
2033
  logger_logger.stderr(error.diff);
1964
2034
  logger_logger.stderr('');
@@ -1969,7 +2039,7 @@ async function error_printError(error, getSourcemap, rootPath) {
1969
2039
  fullStack: error.fullStack,
1970
2040
  getSourcemap
1971
2041
  });
1972
- if (!stackFrames.length && !(error.fullStack || isDebug()) && !error.stack.endsWith(error.message)) logger_logger.stderr(color.gray("No error stack found, set 'DEBUG=rstest' to show fullStack."));
2042
+ if (!stackFrames.length && !(error.fullStack || isDebug()) && !error.stack.endsWith(error.message)) logger_logger.stderr(logger_color.gray("No error stack found, set 'DEBUG=rstest' to show fullStack."));
1973
2043
  if (stackFrames[0]) await printCodeFrame(stackFrames[0]);
1974
2044
  printStack(stackFrames, rootPath);
1975
2045
  }
@@ -1996,7 +2066,7 @@ function formatStack(frame, rootPath) {
1996
2066
  return '<unknown>' !== frame.methodName ? `at ${frame.methodName} (${formatTestPath(rootPath, frame.file)}:${frame.lineNumber}:${frame.column})` : `at ${formatTestPath(rootPath, frame.file)}:${frame.lineNumber}:${frame.column}`;
1997
2067
  }
1998
2068
  function printStack(stackFrames, rootPath) {
1999
- for (const frame of stackFrames)logger_logger.stderr(color.gray(` ${formatStack(frame, rootPath)}`));
2069
+ for (const frame of stackFrames)logger_logger.stderr(logger_color.gray(` ${formatStack(frame, rootPath)}`));
2000
2070
  stackFrames.length && logger_logger.stderr('');
2001
2071
  }
2002
2072
  const stackIgnores = [
@@ -3201,7 +3271,7 @@ class MdReporter {
3201
3271
  }
3202
3272
  renderFrontMatter(lines) {
3203
3273
  const frontMatter = {
3204
- tool: "@rstest/core@0.9.2",
3274
+ tool: "@rstest/core@0.9.3",
3205
3275
  timestamp: new Date().toISOString()
3206
3276
  };
3207
3277
  if (this.options.header.env) frontMatter.runtime = {
@@ -3575,7 +3645,7 @@ class Rstest {
3575
3645
  updateSnapshot: rstestConfig.update ? 'all' : dist_m ? 'none' : 'new'
3576
3646
  });
3577
3647
  this.snapshotManager = snapshotManager;
3578
- this.version = "0.9.2";
3648
+ this.version = "0.9.3";
3579
3649
  this.rootPath = rootPath;
3580
3650
  this.originalConfig = userConfig;
3581
3651
  this.normalizedConfig = rstestConfig;
@@ -3628,6 +3698,9 @@ class Rstest {
3628
3698
  options: {
3629
3699
  showProjectName: projects.length > 1
3630
3700
  }
3701
+ }).filter((r)=>{
3702
+ if ('merge-reports' === command && r instanceof BlobReporter) return false;
3703
+ return true;
3631
3704
  }) : [];
3632
3705
  this.reporters = reporters;
3633
3706
  }
@@ -3652,7 +3725,8 @@ const reportersMap = {
3652
3725
  verbose: VerboseReporter,
3653
3726
  'github-actions': GithubActionsReporter,
3654
3727
  junit: JUnitReporter,
3655
- md: MdReporter
3728
+ md: MdReporter,
3729
+ blob: BlobReporter
3656
3730
  };
3657
3731
  function createReporters(reporters, initConfig = {}) {
3658
3732
  const result = castArray(reporters).map((reporter)=>{
@@ -3693,10 +3767,15 @@ function core_createRstest({ config, projects, configFilePath }, command, fileFi
3693
3767
  const { listTests } = await import("./0~listTests.js");
3694
3768
  return listTests(context, options);
3695
3769
  };
3770
+ const mergeReports = async (options)=>{
3771
+ const { mergeReports } = await import("./0~mergeReports.js");
3772
+ await mergeReports(context, options);
3773
+ };
3696
3774
  return {
3697
3775
  context,
3698
3776
  runTests,
3699
- listTests
3777
+ listTests,
3778
+ mergeReports
3700
3779
  };
3701
3780
  }
3702
3781
  function defineConfig(config) {
package/dist/4411.js CHANGED
@@ -5,7 +5,7 @@ import node_fs, * as __rspack_external_node_fs_5ea92f0c from "node:fs";
5
5
  import node_path, { basename, dirname, normalize, posix, relative as external_node_path_relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { createRequire } from "module";
8
- import { logger as logger_logger, parsePosix, color, castArray } from "./6830.js";
8
+ import { logger as logger_logger, parsePosix, color as logger_color, castArray } from "./6830.js";
9
9
  import { posix as dist_posix } from "./7011.js";
10
10
  __webpack_require__.add({
11
11
  "../../node_modules/.pnpm/picomatch@4.0.3/node_modules/picomatch/index.js" (module, __unused_rspack_exports, __webpack_require__) {
@@ -2266,7 +2266,7 @@ const getTestEntries = async ({ include, exclude, rootPath, projectRoot, fileFil
2266
2266
  };
2267
2267
  const prettyTestPath = (testPath)=>{
2268
2268
  const { dir, base } = parsePosix(testPath);
2269
- return `${'.' !== dir ? color.gray(`${dir}/`) : ''}${color.cyan(base)}`;
2269
+ return `${'.' !== dir ? logger_color.gray(`${dir}/`) : ''}${logger_color.cyan(base)}`;
2270
2270
  };
2271
2271
  const formatTestPath = (root, testFilePath)=>{
2272
2272
  let testPath = testFilePath;
@@ -2304,7 +2304,7 @@ async function resolveShardedEntries(context) {
2304
2304
  const shardedEntries = getShardedFiles(allTestEntriesBeforeSharding, shard);
2305
2305
  const totalTestFileCount = allTestEntriesBeforeSharding.length;
2306
2306
  const testFilesInShardCount = shardedEntries.length;
2307
- logger_logger.log(color.green(`Running shard ${shard.index} of ${shard.count} (${testFilesInShardCount} of ${totalTestFileCount} test files)\n`));
2307
+ logger_logger.log(logger_color.green(`Running shard ${shard.index} of ${shard.count} (${testFilesInShardCount} of ${totalTestFileCount} test files)\n`));
2308
2308
  const shardedEntriesByProject = new Map();
2309
2309
  for (const { project, alias, testPath } of shardedEntries){
2310
2310
  if (!shardedEntriesByProject.has(project)) shardedEntriesByProject.set(project, {});