@poleski/quality-tools 0.1.3 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @poleski/quality-tools
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 289c605: Stop host projects from seeing Stryker warnings about the upstream Vitest runner plugin when using the bundled mutation config, and throttle Stryker mutation progress into the existing one-minute heartbeat.
8
+
3
9
  ## 0.1.3
4
10
 
5
11
  ### Patch Changes
package/dist/cli/main.js CHANGED
@@ -1472,6 +1472,99 @@ function buildMutationEnv(options = {}) {
1472
1472
  };
1473
1473
  }
1474
1474
 
1475
+ // src/mutation/runner/progress.ts
1476
+ var ANSI_PATTERN = new RegExp(
1477
+ `${String.fromCharCode(27)}(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])`,
1478
+ "g"
1479
+ );
1480
+ var PROGRESS_PATTERN = /Mutation testing\s+(?:\[(?<bracketStatus>[^\]]*)\]\s*)?(?<percent>\d+%)\s+\((?<timing>elapsed:[^)]+)\)\s+(?<count>\d+\/\d+)\s+(?:Mutants?|tested)(?:\s+\((?<tailStatus>\d+\s+survived,\s*\d+\s+timed out)\))?/i;
1481
+ var STATUS_TAIL_PATTERN = /(?:^|\s)tested\s+\((?<status>\d+\s+survived,\s*\d+\s+timed out)\)\s*$/i;
1482
+ function cleanProgressText(text) {
1483
+ return text.replace(ANSI_PATTERN, "").trim();
1484
+ }
1485
+ function normalizeStatus(status) {
1486
+ const trimmed = status?.trim();
1487
+ return trimmed && trimmed.length > 0 ? trimmed : void 0;
1488
+ }
1489
+ var MutationProgressTracker = class {
1490
+ latest;
1491
+ formatLatest() {
1492
+ if (!this.latest) {
1493
+ return void 0;
1494
+ }
1495
+ return [
1496
+ "Mutation testing",
1497
+ `[${this.latest.status ?? ""}]`,
1498
+ this.latest.percent,
1499
+ `(${this.latest.timing})`,
1500
+ this.latest.count,
1501
+ "Mutants"
1502
+ ].join(" ");
1503
+ }
1504
+ observe(text) {
1505
+ const cleanText = cleanProgressText(text);
1506
+ if (cleanText.length === 0) {
1507
+ return false;
1508
+ }
1509
+ const progressMatch = PROGRESS_PATTERN.exec(cleanText);
1510
+ if (progressMatch?.groups) {
1511
+ this.latest = {
1512
+ count: progressMatch.groups.count,
1513
+ percent: progressMatch.groups.percent,
1514
+ timing: progressMatch.groups.timing,
1515
+ status: normalizeStatus(progressMatch.groups.tailStatus) ?? normalizeStatus(progressMatch.groups.bracketStatus) ?? this.latest?.status
1516
+ };
1517
+ return true;
1518
+ }
1519
+ const statusMatch = STATUS_TAIL_PATTERN.exec(cleanText);
1520
+ if (statusMatch?.groups && this.latest) {
1521
+ this.latest = {
1522
+ ...this.latest,
1523
+ status: normalizeStatus(statusMatch.groups.status) ?? this.latest.status
1524
+ };
1525
+ return true;
1526
+ }
1527
+ return false;
1528
+ }
1529
+ };
1530
+ function createMutationProgressOutputForwarder(tracker, writeOutput) {
1531
+ let pending = "";
1532
+ const handleSegment = (segment, delimiter) => {
1533
+ if (tracker.observe(segment)) {
1534
+ return;
1535
+ }
1536
+ writeOutput(`${segment}${delimiter}`);
1537
+ };
1538
+ const flushPendingProgress = () => {
1539
+ if (tracker.observe(pending)) {
1540
+ pending = "";
1541
+ return true;
1542
+ }
1543
+ return false;
1544
+ };
1545
+ return {
1546
+ flush() {
1547
+ if (pending.length === 0 || flushPendingProgress()) {
1548
+ return;
1549
+ }
1550
+ writeOutput(pending);
1551
+ pending = "";
1552
+ },
1553
+ write(text) {
1554
+ pending += text;
1555
+ let delimiterIndex = pending.search(/[\r\n]/);
1556
+ while (delimiterIndex >= 0) {
1557
+ const segment = pending.slice(0, delimiterIndex);
1558
+ const delimiter = pending[delimiterIndex] === "\n" ? "\n" : "";
1559
+ pending = pending.slice(delimiterIndex + 1);
1560
+ handleSegment(segment, delimiter);
1561
+ delimiterIndex = pending.search(/[\r\n]/);
1562
+ }
1563
+ flushPendingProgress();
1564
+ }
1565
+ };
1566
+ }
1567
+
1475
1568
  // src/mutation/runner/run.ts
1476
1569
  var MUTATION_PROGRESS_INTERVAL_MS = 6e4;
1477
1570
  function formatElapsedDuration(durationMs) {
@@ -1483,18 +1576,35 @@ function formatElapsedDuration(durationMs) {
1483
1576
  function runStryker(args2, env, target) {
1484
1577
  return new Promise((resolve8, reject) => {
1485
1578
  const startedAt = Date.now();
1579
+ const progressTracker = new MutationProgressTracker();
1580
+ const stdoutForwarder = createMutationProgressOutputForwarder(
1581
+ progressTracker,
1582
+ (text) => process.stdout.write(text)
1583
+ );
1584
+ const stderrForwarder = createMutationProgressOutputForwarder(
1585
+ progressTracker,
1586
+ (text) => process.stderr.write(text)
1587
+ );
1486
1588
  const child = spawn(process.execPath, [strykerBinPath(), ...args2], {
1487
1589
  cwd: REPO_ROOT,
1488
1590
  env,
1489
- stdio: "inherit"
1591
+ stdio: ["inherit", "pipe", "pipe"]
1592
+ });
1593
+ child.stdout?.on("data", (chunk) => {
1594
+ stdoutForwarder.write(String(chunk));
1595
+ });
1596
+ child.stderr?.on("data", (chunk) => {
1597
+ stderrForwarder.write(String(chunk));
1490
1598
  });
1491
1599
  const progressTimer = setInterval(() => {
1492
1600
  console.error(
1493
- `[mutation] Still running ${target.relativePath} after ${formatElapsedDuration(Date.now() - startedAt)}...`
1601
+ progressTracker.formatLatest() ?? `[mutation] Still running ${target.relativePath} after ${formatElapsedDuration(Date.now() - startedAt)}...`
1494
1602
  );
1495
1603
  }, MUTATION_PROGRESS_INTERVAL_MS);
1496
1604
  const clearProgressTimer = () => {
1497
1605
  clearInterval(progressTimer);
1606
+ stdoutForwarder.flush();
1607
+ stderrForwarder.flush();
1498
1608
  };
1499
1609
  child.once("error", (error) => {
1500
1610
  clearProgressTimer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poleski/quality-tools",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Portable TypeScript quality checks for project structure, complexity, mutation, and test health",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "scripts": {
44
44
  "build": "esbuild src/cli/main.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/cli/main.js",
45
45
  "ci": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
46
- "release": "pnpm run ci && pnpm publish --access public",
46
+ "release": "pnpm run ci && pnpm publish --no-git-checks",
47
47
  "changeset": "changeset",
48
48
  "cli": "tsx src/cli/main.ts",
49
49
  "quality-tools": "pnpm run cli",
@@ -13,6 +13,10 @@ const { vitestWrapper } = await import(path.join(vitestRunnerRoot, 'dist/src/vit
13
13
  const STRYKER_SETUP = path.join(vitestRunnerRoot, 'dist/src/stryker-setup.js');
14
14
  const STRYKER_SETUP_SOURCE_MAP = 'stryker-setup.js.map';
15
15
 
16
+ export const strykerValidationSchema = JSON.parse(
17
+ fs.readFileSync(path.join(vitestRunnerRoot, 'dist/schema/vitest-runner-options.json'), 'utf-8'),
18
+ );
19
+
16
20
  function createStrykerSetupSourceMap(setupFilePath) {
17
21
  return JSON.stringify({
18
22
  version: 3,
@@ -21,7 +21,6 @@ module.exports = {
21
21
  testRunner: 'quality-tools-vitest',
22
22
  plugins: [
23
23
  path.join(packageRoot, 'stryker/quality-tools-vitest-runner.mjs'),
24
- '@stryker-mutator/vitest-runner',
25
24
  ],
26
25
  vitest: {
27
26
  configFile: path.isAbsolute(vitestConfig) ? vitestConfig : path.join(hostRoot, vitestConfig),