@t09tanaka/test-gates 0.4.2 → 0.4.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/CHANGELOG.md +29 -0
- package/README.md +55 -8
- package/dist/cli.js +1112 -350
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1034,13 +1034,13 @@ function judgeSelfcheck(input) {
|
|
|
1034
1034
|
function selectControls(input) {
|
|
1035
1035
|
const { gates, mode, only, shard } = input;
|
|
1036
1036
|
if (only.length > 0) {
|
|
1037
|
-
const unknown = only.filter((
|
|
1037
|
+
const unknown = only.filter((path15) => !gates.includes(path15));
|
|
1038
1038
|
if (unknown.length > 0) {
|
|
1039
1039
|
return {
|
|
1040
1040
|
error: `--gate ${unknown.join(", ")}: not a gate of test-gates.json (write the path as it is written there)`
|
|
1041
1041
|
};
|
|
1042
1042
|
}
|
|
1043
|
-
return { selected: gates.filter((
|
|
1043
|
+
return { selected: gates.filter((path15) => only.includes(path15)) };
|
|
1044
1044
|
}
|
|
1045
1045
|
if (shard !== null) {
|
|
1046
1046
|
if (shard.total > gates.length) {
|
|
@@ -1193,8 +1193,8 @@ function planControls(input) {
|
|
|
1193
1193
|
if (silent !== void 0) {
|
|
1194
1194
|
return everyoneAlone(`the gate run left no note for the spec of ${silent.path}`);
|
|
1195
1195
|
}
|
|
1196
|
-
const
|
|
1197
|
-
const short = all.find((gate) => accountedFor(totals[gate.file],
|
|
1196
|
+
const partsOf3 = (gate) => notes.flatMap((note) => gate.file in note.files ? [note.files[gate.file]] : []);
|
|
1197
|
+
const short = all.find((gate) => accountedFor(totals[gate.file], partsOf3(gate)) === "no");
|
|
1198
1198
|
if (short !== void 0) {
|
|
1199
1199
|
return everyoneAlone(
|
|
1200
1200
|
`the notes of the specs do not add up to what the runner counted for ${short.path}`
|
|
@@ -1208,7 +1208,7 @@ function planControls(input) {
|
|
|
1208
1208
|
if (all.some((other) => other.path !== gate.path && other.path.includes(gate.path))) {
|
|
1209
1209
|
return false;
|
|
1210
1210
|
}
|
|
1211
|
-
if (accountedFor(totals[gate.file],
|
|
1211
|
+
if (accountedFor(totals[gate.file], partsOf3(gate)) !== "yes") {
|
|
1212
1212
|
return false;
|
|
1213
1213
|
}
|
|
1214
1214
|
const touching = notes.filter((note) => {
|
|
@@ -1419,9 +1419,9 @@ function runCheck(dir, io2) {
|
|
|
1419
1419
|
}
|
|
1420
1420
|
|
|
1421
1421
|
// src/commands/doctor.ts
|
|
1422
|
-
import { spawnSync as
|
|
1423
|
-
import
|
|
1424
|
-
import
|
|
1422
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
1423
|
+
import fs11 from "fs";
|
|
1424
|
+
import path11 from "path";
|
|
1425
1425
|
|
|
1426
1426
|
// src/core/mutation-result.ts
|
|
1427
1427
|
var DETECTED = /* @__PURE__ */ new Set(["Killed", "Timeout"]);
|
|
@@ -1688,6 +1688,80 @@ function judgeMutationReport(input) {
|
|
|
1688
1688
|
}
|
|
1689
1689
|
return { violations, survivors, unexpectedTimeouts, summary };
|
|
1690
1690
|
}
|
|
1691
|
+
var UNKNOWN_TEST = "not-in-the-first-run:";
|
|
1692
|
+
function sameMutant(a, b) {
|
|
1693
|
+
return a.mutatorName === b.mutatorName && (a.replacement ?? "") === (b.replacement ?? "") && a.location.start.line === b.location.start.line && a.location.start.column === b.location.start.column && a.location.end.line === b.location.end.line && a.location.end.column === b.location.end.column;
|
|
1694
|
+
}
|
|
1695
|
+
function mergeRetry(first, retry, gates) {
|
|
1696
|
+
const outcome = { retried: 0, statuses: {}, missing: 0 };
|
|
1697
|
+
const firstId = translator(first, retry);
|
|
1698
|
+
const files = { ...first.files };
|
|
1699
|
+
const again = retry.files ?? {};
|
|
1700
|
+
for (const named of gates) {
|
|
1701
|
+
const gate = named.path.split("/").filter((part) => part !== ".").join("/");
|
|
1702
|
+
const result = Object.hasOwn(files, gate) ? files[gate] : void 0;
|
|
1703
|
+
if (result?.mutants === void 0) {
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const expected = expectedTimeoutMutants(named, result.source, result.mutants);
|
|
1707
|
+
const rerun = Object.hasOwn(again, gate) ? again[gate]?.mutants : void 0;
|
|
1708
|
+
files[gate] = {
|
|
1709
|
+
...result,
|
|
1710
|
+
mutants: result.mutants.map((mutant) => {
|
|
1711
|
+
if (mutant.status !== "Timeout" || expected.has(mutant)) {
|
|
1712
|
+
return mutant;
|
|
1713
|
+
}
|
|
1714
|
+
outcome.retried += 1;
|
|
1715
|
+
const found = rerun?.find((other) => sameMutant(other, mutant));
|
|
1716
|
+
if (found === void 0) {
|
|
1717
|
+
outcome.missing += 1;
|
|
1718
|
+
return mutant;
|
|
1719
|
+
}
|
|
1720
|
+
outcome.statuses[found.status] = (outcome.statuses[found.status] ?? 0) + 1;
|
|
1721
|
+
return {
|
|
1722
|
+
...found,
|
|
1723
|
+
...found.coveredBy === void 0 ? {} : { coveredBy: found.coveredBy.map(firstId) },
|
|
1724
|
+
...found.killedBy === void 0 ? {} : { killedBy: found.killedBy.map(firstId) }
|
|
1725
|
+
};
|
|
1726
|
+
})
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
return { report: { ...first, files }, outcome };
|
|
1730
|
+
}
|
|
1731
|
+
function testKey(file, test) {
|
|
1732
|
+
const start = test.location?.start;
|
|
1733
|
+
return JSON.stringify([file, test.name ?? "", start?.line, start?.column]);
|
|
1734
|
+
}
|
|
1735
|
+
function* keyedTests(report) {
|
|
1736
|
+
for (const [file, entry] of Object.entries(report.testFiles ?? {})) {
|
|
1737
|
+
if (entry.tests !== void 0) {
|
|
1738
|
+
for (const test of entry.tests) {
|
|
1739
|
+
yield { id: test.id, key: testKey(file, test) };
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
function translator(first, retry) {
|
|
1745
|
+
const firstIds = /* @__PURE__ */ new Map();
|
|
1746
|
+
for (const { id, key } of keyedTests(first)) {
|
|
1747
|
+
firstIds.set(key, firstIds.has(key) ? null : id);
|
|
1748
|
+
}
|
|
1749
|
+
const ids = /* @__PURE__ */ new Map();
|
|
1750
|
+
for (const { id, key } of keyedTests(retry)) {
|
|
1751
|
+
ids.set(id, firstIds.get(key));
|
|
1752
|
+
}
|
|
1753
|
+
return (id) => {
|
|
1754
|
+
const found = ids.get(id);
|
|
1755
|
+
return typeof found === "string" ? found : `${UNKNOWN_TEST}${id}`;
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
function formatRetry(outcome) {
|
|
1759
|
+
const statuses = Object.entries(outcome.statuses).sort(([a], [b]) => a.localeCompare(b)).map(([status, count]) => `${count} ${status}`);
|
|
1760
|
+
if (outcome.missing > 0) {
|
|
1761
|
+
statuses.push(`${outcome.missing} not in the report (still Timeout)`);
|
|
1762
|
+
}
|
|
1763
|
+
return `test-gates mutation: ${outcome.retried} mutant(s) that timed out were run again one at a time (--concurrency 1): ${statuses.join(", ")}`;
|
|
1764
|
+
}
|
|
1691
1765
|
function formatSurvivor(survivor) {
|
|
1692
1766
|
const occurrence = survivor.sameKeyCount > 1 ? ` / occurrence ${survivor.occurrence} of ${survivor.sameKeyCount}` : "";
|
|
1693
1767
|
return `${survivor.file}:${survivor.line}:${survivor.column} / ${survivor.mutator} / ${survivor.original} \u2192 ${survivor.replacement}${occurrence} (${survivor.status})`;
|
|
@@ -1697,7 +1771,7 @@ function formatSummary(summary) {
|
|
|
1697
1771
|
}
|
|
1698
1772
|
|
|
1699
1773
|
// src/core/reuse-guard.ts
|
|
1700
|
-
var RECORD_FORMAT =
|
|
1774
|
+
var RECORD_FORMAT = 3;
|
|
1701
1775
|
function isStringRecord(value) {
|
|
1702
1776
|
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
1703
1777
|
}
|
|
@@ -1709,11 +1783,14 @@ function parseAffected(value) {
|
|
|
1709
1783
|
}
|
|
1710
1784
|
const parsed = /* @__PURE__ */ Object.create(null);
|
|
1711
1785
|
for (const [gate, entry] of Object.entries(gates)) {
|
|
1712
|
-
const { specs, files } = entry ?? {};
|
|
1786
|
+
const { specs, files, blind } = entry ?? {};
|
|
1713
1787
|
if (!isStringList(specs) || !isStringList(files)) {
|
|
1714
1788
|
return null;
|
|
1715
1789
|
}
|
|
1716
|
-
|
|
1790
|
+
if (blind !== void 0 && typeof blind !== "string") {
|
|
1791
|
+
return null;
|
|
1792
|
+
}
|
|
1793
|
+
parsed[gate] = { specs, files, ...blind === void 0 ? {} : { blind } };
|
|
1717
1794
|
}
|
|
1718
1795
|
return { tests, gates: parsed };
|
|
1719
1796
|
}
|
|
@@ -1876,7 +1953,8 @@ var CONFIG_NAMES = [
|
|
|
1876
1953
|
/\.d\.[cm]?ts$/
|
|
1877
1954
|
];
|
|
1878
1955
|
var ROOT_MOCKS = "__mocks__";
|
|
1879
|
-
var DOCUMENT = /\.(?:md|markdown|txt|rst|adoc)$/i;
|
|
1956
|
+
var DOCUMENT = /\.(?:md|markdown|txt|rst|adoc|html?)$/i;
|
|
1957
|
+
var ENTRY_PAGE = /^index\.html?$/i;
|
|
1880
1958
|
var PASSIVE = /\.(?:[cm]?[jt]sx?|json|snap|css|scss|sass|less|svg|png|jpe?g|gif|webp|avif|ico)$/i;
|
|
1881
1959
|
function kindOf(key, subproject) {
|
|
1882
1960
|
const segments = localSegments(key, subproject);
|
|
@@ -1887,7 +1965,7 @@ function kindOf(key, subproject) {
|
|
|
1887
1965
|
if (isNestedMock(key, subproject)) {
|
|
1888
1966
|
return "other";
|
|
1889
1967
|
}
|
|
1890
|
-
if (DOCUMENT.test(name)) {
|
|
1968
|
+
if (DOCUMENT.test(name) && !ENTRY_PAGE.test(name)) {
|
|
1891
1969
|
return "document";
|
|
1892
1970
|
}
|
|
1893
1971
|
const inside = subproject === "" || key.startsWith(`${subproject}/`);
|
|
@@ -1904,6 +1982,7 @@ function isNestedMock(key, subproject) {
|
|
|
1904
1982
|
}
|
|
1905
1983
|
function gateDependencies(input) {
|
|
1906
1984
|
const closureOf = (key) => Object.hasOwn(input.closures, key) ? input.closures[key] : [key];
|
|
1985
|
+
const blind = input.blind ?? {};
|
|
1907
1986
|
const result = /* @__PURE__ */ Object.create(null);
|
|
1908
1987
|
for (const gate of input.gates) {
|
|
1909
1988
|
const covering = Object.hasOwn(input.covering, gate.key) ? input.covering[gate.key] : [];
|
|
@@ -1917,7 +1996,13 @@ function gateDependencies(input) {
|
|
|
1917
1996
|
for (const spec of specs) {
|
|
1918
1997
|
closureOf(spec).forEach((file) => files.add(file));
|
|
1919
1998
|
}
|
|
1920
|
-
|
|
1999
|
+
const sorted2 = [...files].sort();
|
|
2000
|
+
const reason = sorted2.find((file) => Object.hasOwn(blind, file));
|
|
2001
|
+
result[gate.key] = {
|
|
2002
|
+
specs: [...specs].sort(),
|
|
2003
|
+
files: sorted2,
|
|
2004
|
+
...reason === void 0 ? {} : { blind: blind[reason] }
|
|
2005
|
+
};
|
|
1921
2006
|
}
|
|
1922
2007
|
return result;
|
|
1923
2008
|
}
|
|
@@ -1948,6 +2033,15 @@ function planAffected(input) {
|
|
|
1948
2033
|
rerun.push({ gate: gate.path, changed: [] });
|
|
1949
2034
|
continue;
|
|
1950
2035
|
}
|
|
2036
|
+
const blind = gate.blind ?? last.blind;
|
|
2037
|
+
if (blind !== void 0) {
|
|
2038
|
+
if (changed.length > 0 || input.seeded === true) {
|
|
2039
|
+
rerun.push({ gate: gate.path, changed, blind });
|
|
2040
|
+
} else {
|
|
2041
|
+
reused.push(gate.path);
|
|
2042
|
+
}
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
1951
2045
|
const files = [.../* @__PURE__ */ new Set([...last.files, ...gate.files])];
|
|
1952
2046
|
const differing = files.filter((file) => input.before.files[file] !== input.now[file]).sort();
|
|
1953
2047
|
if (differing.length > 0) {
|
|
@@ -1999,13 +2093,23 @@ function describePlan(plan, shown = 5, timeouts = 0, expected = 0) {
|
|
|
1999
2093
|
const lines = [
|
|
2000
2094
|
`test-gates mutation --affected: ${plan.reused.length} of ${total} gate(s) reused, ${plan.rerun.length} run again`
|
|
2001
2095
|
];
|
|
2002
|
-
|
|
2096
|
+
const told = plan.rerun.filter((entry) => entry.blind === void 0);
|
|
2097
|
+
for (const { gate, changed } of told.slice(0, shown)) {
|
|
2003
2098
|
lines.push(
|
|
2004
2099
|
changed.length === 0 ? ` ${gate}: the last run left no result of it that can be reused` : ` ${gate}: changed ${listed(changed)}`
|
|
2005
2100
|
);
|
|
2006
2101
|
}
|
|
2007
|
-
if (
|
|
2008
|
-
lines.push(` and ${
|
|
2102
|
+
if (told.length > shown) {
|
|
2103
|
+
lines.push(` and ${told.length - shown} more`);
|
|
2104
|
+
}
|
|
2105
|
+
const reasons = [
|
|
2106
|
+
...new Set(plan.rerun.flatMap((entry) => entry.blind === void 0 ? [] : [entry.blind]))
|
|
2107
|
+
];
|
|
2108
|
+
if (reasons.length > 0) {
|
|
2109
|
+
const more = reasons.length > 1 ? ` (and ${reasons.length - 1} more reason(s))` : "";
|
|
2110
|
+
lines.push(
|
|
2111
|
+
` ${plan.rerun.length - told.length} gate(s) run again for any change, as what they depend on cannot be told: ${reasons[0]}${more}`
|
|
2112
|
+
);
|
|
2009
2113
|
}
|
|
2010
2114
|
if (timeouts > 0) {
|
|
2011
2115
|
lines.push(` and ${timeouts} mutant(s) of reused gates that timed out in the last run`);
|
|
@@ -2052,12 +2156,22 @@ function diagnose(facts) {
|
|
|
2052
2156
|
fix: "write such a path from the directory of its file (path.resolve(__dirname, \u2026)) so that it can be followed, or keep it out of the files the run loads"
|
|
2053
2157
|
});
|
|
2054
2158
|
}
|
|
2159
|
+
const everyBlind = facts.blindGates.length >= facts.gates;
|
|
2055
2160
|
if (facts.blind !== null) {
|
|
2056
2161
|
findings.push({
|
|
2057
2162
|
level: facts.hooks.some((hook) => hook.affected) ? "problem" : "note",
|
|
2058
2163
|
text: `with --affected, a change of any file runs every mutant: what a gate depends on cannot be told (${facts.blind})`,
|
|
2059
2164
|
fix: "keep what reads files or loads modules by a computed name out of what the gates and their specs import, or run without --affected"
|
|
2060
2165
|
});
|
|
2166
|
+
} else if (facts.blindGates.length > 0) {
|
|
2167
|
+
const gates = facts.blindGates.map((one) => one.gate);
|
|
2168
|
+
const reasons = [...new Set(facts.blindGates.map((one) => one.reason))];
|
|
2169
|
+
const more = reasons.length > 1 ? ` (and ${reasons.length - 1} more reason(s))` : "";
|
|
2170
|
+
findings.push({
|
|
2171
|
+
level: everyBlind && facts.hooks.some((hook) => hook.affected) ? "problem" : "note",
|
|
2172
|
+
text: `with --affected, a change of any file runs ${gates.length} of ${facts.gates} gate(s) again (${listed(gates)}): what they depend on cannot be told (${reasons[0]}${more})`,
|
|
2173
|
+
fix: "keep what reads files by a path no import names out of what those gates and their specs import"
|
|
2174
|
+
});
|
|
2061
2175
|
}
|
|
2062
2176
|
if (facts.scope !== null && "every" in facts.scope) {
|
|
2063
2177
|
findings.push({
|
|
@@ -2066,6 +2180,21 @@ function diagnose(facts) {
|
|
|
2066
2180
|
fix: facts.scope.every.startsWith(NOT_THE_HELPER) ? "make the runner config with createJestGatesConfig or createVitestGatesConfig" : null
|
|
2067
2181
|
});
|
|
2068
2182
|
}
|
|
2183
|
+
if (facts.sandboxes !== null) {
|
|
2184
|
+
const { tempDir, count, kept, running } = facts.sandboxes;
|
|
2185
|
+
findings.push(
|
|
2186
|
+
running === null ? {
|
|
2187
|
+
level: "note",
|
|
2188
|
+
text: `${count} sandbox(es) an interrupted Stryker run left in ${tempDir}: a tool that walks the subproject (a test runner, a script) finds a copy of every file in them`,
|
|
2189
|
+
fix: kept === null ? "the next test-gates mutation removes them" : `remove them by hand; the next test-gates mutation keeps them, because ${kept}`
|
|
2190
|
+
} : {
|
|
2191
|
+
// The sandboxes may be those of that run: removing them would break it.
|
|
2192
|
+
level: "note",
|
|
2193
|
+
text: `another test-gates mutation is running in this subproject (pid ${running}): the ${count} sandbox(es) in ${tempDir} are in use`,
|
|
2194
|
+
fix: "leave them; the first test-gates mutation after that run ends removes what is left"
|
|
2195
|
+
}
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2069
2198
|
if (facts.hooks.length === 0) {
|
|
2070
2199
|
findings.push({
|
|
2071
2200
|
level: "note",
|
|
@@ -2073,7 +2202,8 @@ function diagnose(facts) {
|
|
|
2073
2202
|
fix: null
|
|
2074
2203
|
});
|
|
2075
2204
|
}
|
|
2076
|
-
|
|
2205
|
+
const toldApart = facts.blind === null && !everyBlind;
|
|
2206
|
+
for (const hook of facts.hooks.filter((one) => !one.affected && toldApart)) {
|
|
2077
2207
|
findings.push({
|
|
2078
2208
|
level: "problem",
|
|
2079
2209
|
text: `${hook.file} runs test-gates mutation without --affected: a change of any file of the subproject runs every mutant`,
|
|
@@ -2099,39 +2229,322 @@ function formatFindings(findings) {
|
|
|
2099
2229
|
var RUNS_MUTATION = /(?<![\w-])test-gates(?:\.js)?\s+mutation(?![\w-])|(?<![\w:-])test:gates:mutation(?![\w:-])/;
|
|
2100
2230
|
var TARGET = /(?:--dir|--prefix|--workspace|-w)(?:=|\s+)(['"]?)([^\s'";&|)]+)\1/;
|
|
2101
2231
|
var CD = /\bcd\s+(['"]?)([^\s'";&|)]+)\1/;
|
|
2232
|
+
var NONE = [];
|
|
2233
|
+
var COMMAND = String.raw`(?<=(?:^|[\n;&|(){]|\b(?:then|else|do))\s*)`;
|
|
2234
|
+
var TOKEN = new RegExp(
|
|
2235
|
+
String.raw`\n|;;&?|;&|${COMMAND}(?:case\s+(\S+)\s+in|for\s+(\w+)(?:\s+in\s+([^;\n]*))?|(esac|do|done))(?![\w-])`,
|
|
2236
|
+
"g"
|
|
2237
|
+
);
|
|
2238
|
+
var PATTERN = /\s*(?:esac(?![\w-])|\(?([^()\n;]*)\))/y;
|
|
2239
|
+
var VARIABLE = /^("?)\$(?:(\w+)|\{(\w+)\})\1$/;
|
|
2240
|
+
var REFERENCE = /\$(?:(\w+)|\{(\w+)\})/g;
|
|
2241
|
+
function namesOf(words) {
|
|
2242
|
+
const names = words.map((word) => word.trim().replace(/^(["'])(.*)\1$/, "$2"));
|
|
2243
|
+
return names.some((name) => name === "" || /[$*?[{}~\\`"']/.test(name)) ? null : names;
|
|
2244
|
+
}
|
|
2245
|
+
function partsOf(text) {
|
|
2246
|
+
const parts = [];
|
|
2247
|
+
let scopes = NONE;
|
|
2248
|
+
let start = 0;
|
|
2249
|
+
let loop = null;
|
|
2250
|
+
const enter = (scope) => {
|
|
2251
|
+
scopes = [...scopes, scope];
|
|
2252
|
+
};
|
|
2253
|
+
const leave = (kind) => {
|
|
2254
|
+
const left = scopes.at(-1)?.kind === kind;
|
|
2255
|
+
scopes = scopes.slice(0, -1);
|
|
2256
|
+
return left;
|
|
2257
|
+
};
|
|
2258
|
+
TOKEN.lastIndex = 0;
|
|
2259
|
+
for (; ; ) {
|
|
2260
|
+
const token = TOKEN.exec(text);
|
|
2261
|
+
parts.push({ text: text.slice(start, token?.index), scopes });
|
|
2262
|
+
if (token === null) {
|
|
2263
|
+
return scopes.length === 0 ? parts : null;
|
|
2264
|
+
}
|
|
2265
|
+
const [word, subject, variable, list, keyword] = token;
|
|
2266
|
+
let awaiting = false;
|
|
2267
|
+
if (subject !== void 0) {
|
|
2268
|
+
const named = VARIABLE.exec(subject);
|
|
2269
|
+
enter({
|
|
2270
|
+
kind: "case",
|
|
2271
|
+
id: /* @__PURE__ */ Symbol(),
|
|
2272
|
+
variable: named?.[2] ?? named?.[3] ?? null,
|
|
2273
|
+
names: null
|
|
2274
|
+
});
|
|
2275
|
+
awaiting = true;
|
|
2276
|
+
} else if (variable !== void 0) {
|
|
2277
|
+
loop = {
|
|
2278
|
+
kind: "loop",
|
|
2279
|
+
id: /* @__PURE__ */ Symbol(),
|
|
2280
|
+
variable,
|
|
2281
|
+
names: list === void 0 ? null : namesOf(list.trim().split(/\s+/))
|
|
2282
|
+
};
|
|
2283
|
+
} else if (keyword === "do") {
|
|
2284
|
+
enter(loop ?? { kind: "loop", id: /* @__PURE__ */ Symbol(), variable: null, names: null });
|
|
2285
|
+
loop = null;
|
|
2286
|
+
} else if (keyword === "done" || keyword === "esac") {
|
|
2287
|
+
if (!leave(keyword === "done" ? "loop" : "case")) {
|
|
2288
|
+
return null;
|
|
2289
|
+
}
|
|
2290
|
+
} else if (word === ";&") {
|
|
2291
|
+
return null;
|
|
2292
|
+
} else if (word !== "\n") {
|
|
2293
|
+
if (scopes.at(-1)?.kind !== "case") {
|
|
2294
|
+
return null;
|
|
2295
|
+
}
|
|
2296
|
+
awaiting = true;
|
|
2297
|
+
}
|
|
2298
|
+
start = TOKEN.lastIndex;
|
|
2299
|
+
if (awaiting) {
|
|
2300
|
+
PATTERN.lastIndex = start;
|
|
2301
|
+
const pattern = PATTERN.exec(text);
|
|
2302
|
+
if (pattern === null) {
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2305
|
+
start = PATTERN.lastIndex;
|
|
2306
|
+
TOKEN.lastIndex = start;
|
|
2307
|
+
const top = scopes.at(-1);
|
|
2308
|
+
scopes = scopes.slice(0, -1);
|
|
2309
|
+
if (pattern[1] !== void 0) {
|
|
2310
|
+
enter({ ...top, names: namesOf(pattern[1].split("|")) });
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
function sibling(of, scopes) {
|
|
2316
|
+
const apart = of.findIndex((scope, at) => scope !== scopes[at]);
|
|
2317
|
+
return apart !== -1 && of[apart].id === scopes[apart]?.id;
|
|
2318
|
+
}
|
|
2319
|
+
function expansions(target, scopes) {
|
|
2320
|
+
return [...target.matchAll(REFERENCE)].reduce(
|
|
2321
|
+
(words, [reference, plain2, braced]) => {
|
|
2322
|
+
const names = [...scopes].reverse().find((scope) => scope.variable === (plain2 ?? braced))?.names ?? [reference];
|
|
2323
|
+
return words.flatMap((word) => names.map((name) => word.replace(reference, name)));
|
|
2324
|
+
},
|
|
2325
|
+
[target]
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2102
2328
|
function hookUses(input) {
|
|
2103
2329
|
const lines = input.text.split("\n").map((line) => line.replace(/(?:^|\s)#.*/, ""));
|
|
2330
|
+
const parts = partsOf(lines.join("\n")) ?? lines.map((text) => ({ text, scopes: NONE }));
|
|
2104
2331
|
const uses = [];
|
|
2105
|
-
|
|
2106
|
-
|
|
2332
|
+
const cds = parts.map((part) => ({ cd: CD.exec(part.text)?.[2], scopes: part.scopes }));
|
|
2333
|
+
parts.forEach((part, index) => {
|
|
2334
|
+
if (!RUNS_MUTATION.test(part.text)) {
|
|
2107
2335
|
return;
|
|
2108
2336
|
}
|
|
2109
|
-
const named = TARGET.exec(
|
|
2110
|
-
const
|
|
2111
|
-
const
|
|
2112
|
-
const
|
|
2337
|
+
const named = TARGET.exec(part.text) ?? CD.exec(part.text);
|
|
2338
|
+
const before = cds.slice(0, index).reverse().find((one) => one.cd !== void 0 && !sibling(one.scopes, part.scopes));
|
|
2339
|
+
const target = named?.[2] ?? before?.cd;
|
|
2340
|
+
const scopes = named !== null || before?.scopes === part.scopes ? part.scopes : NONE;
|
|
2341
|
+
const ours = target === void 0 || expansions(target, scopes).some((word) => {
|
|
2342
|
+
const normalized = word.split("/").filter((one) => one !== "." && one !== "").join("/");
|
|
2343
|
+
return normalized.includes("$") || normalized.includes("..") || normalized === input.subproject;
|
|
2344
|
+
});
|
|
2113
2345
|
if (ours) {
|
|
2114
2346
|
uses.push({
|
|
2115
2347
|
file: input.file,
|
|
2116
|
-
affected: /--affected\b/.test(
|
|
2348
|
+
affected: /--affected\b/.test(part.text) || /test:gates:mutation\b/.test(part.text) && input.script.includes("--affected")
|
|
2117
2349
|
});
|
|
2118
2350
|
}
|
|
2119
2351
|
});
|
|
2120
2352
|
return uses;
|
|
2121
2353
|
}
|
|
2122
2354
|
|
|
2123
|
-
// src/
|
|
2355
|
+
// src/sandboxes.ts
|
|
2124
2356
|
import { spawnSync } from "child_process";
|
|
2357
|
+
import fs4 from "fs";
|
|
2358
|
+
import path4 from "path";
|
|
2359
|
+
|
|
2360
|
+
// src/core/sandboxes.ts
|
|
2361
|
+
var SANDBOX = /^sandbox-[A-Za-z0-9]{6}$/;
|
|
2362
|
+
var MARKER = /^test-gates-run-([1-9][0-9]*)$/;
|
|
2363
|
+
function markerName(pid) {
|
|
2364
|
+
return `test-gates-run-${pid}`;
|
|
2365
|
+
}
|
|
2366
|
+
function markerText(pid, started) {
|
|
2367
|
+
return `${pid}
|
|
2368
|
+
${started ?? ""}
|
|
2369
|
+
`;
|
|
2370
|
+
}
|
|
2371
|
+
function markerAlive(input) {
|
|
2372
|
+
const recorded = input.text?.split("\n")[1]?.trim() || null;
|
|
2373
|
+
return input.there && (recorded === null || input.started === null || sameStart(recorded, input.started));
|
|
2374
|
+
}
|
|
2375
|
+
var START_SLACK_SECONDS = 5;
|
|
2376
|
+
function sameStart(a, b) {
|
|
2377
|
+
const difference = Math.abs(Date.parse(a) - Date.parse(b));
|
|
2378
|
+
return Number.isNaN(difference) ? a === b : difference <= START_SLACK_SECONDS * 1e3;
|
|
2379
|
+
}
|
|
2380
|
+
function insideSubproject(tempDir) {
|
|
2381
|
+
const parts = tempDir.split("/");
|
|
2382
|
+
return !/^[A-Za-z]:/.test(tempDir) && parts.every((part) => part !== "" && part !== "." && part !== "..");
|
|
2383
|
+
}
|
|
2384
|
+
function planSandboxes(facts) {
|
|
2385
|
+
const sandboxes = facts.entries.filter((entry) => entry.directory && SANDBOX.test(entry.name)).map((entry) => entry.name).sort();
|
|
2386
|
+
if (facts.tempDir === null) {
|
|
2387
|
+
return {
|
|
2388
|
+
kept: sandboxes,
|
|
2389
|
+
why: "the Stryker config could not be loaded to see where they are",
|
|
2390
|
+
running: null
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
if (!insideSubproject(facts.tempDir)) {
|
|
2394
|
+
return {
|
|
2395
|
+
kept: sandboxes,
|
|
2396
|
+
why: `tempDirName (${facts.tempDir}) is not a directory inside the subproject`,
|
|
2397
|
+
running: null
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
if (facts.keeps) {
|
|
2401
|
+
return { kept: sandboxes, why: "the Stryker config sets cleanTempDir: false", running: null };
|
|
2402
|
+
}
|
|
2403
|
+
const markers = [];
|
|
2404
|
+
for (const entry of facts.entries) {
|
|
2405
|
+
const pid = MARKER.exec(entry.name)?.[1];
|
|
2406
|
+
if (pid === void 0 || entry.directory) {
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
if (facts.alive(Number(pid), entry.name)) {
|
|
2410
|
+
return {
|
|
2411
|
+
kept: sandboxes,
|
|
2412
|
+
why: `another test-gates mutation is running in this subproject (pid ${pid})`,
|
|
2413
|
+
running: Number(pid)
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
markers.push(entry.name);
|
|
2417
|
+
}
|
|
2418
|
+
return { remove: sandboxes, markers };
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// src/sandboxes.ts
|
|
2422
|
+
function there(pid) {
|
|
2423
|
+
try {
|
|
2424
|
+
process.kill(pid, 0);
|
|
2425
|
+
return true;
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
return error.code === "EPERM";
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
function startedAt(pid) {
|
|
2431
|
+
const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
2432
|
+
encoding: "utf8",
|
|
2433
|
+
env: { ...process.env, LC_ALL: "C" },
|
|
2434
|
+
timeout: 5e3
|
|
2435
|
+
});
|
|
2436
|
+
const started = result.status === 0 ? result.stdout.trim() : "";
|
|
2437
|
+
return started === "" ? null : started;
|
|
2438
|
+
}
|
|
2439
|
+
function alive(dir) {
|
|
2440
|
+
return (pid, marker) => {
|
|
2441
|
+
if (!there(pid)) {
|
|
2442
|
+
return false;
|
|
2443
|
+
}
|
|
2444
|
+
let text = null;
|
|
2445
|
+
try {
|
|
2446
|
+
text = fs4.readFileSync(path4.join(dir, marker), "utf8");
|
|
2447
|
+
} catch {
|
|
2448
|
+
}
|
|
2449
|
+
return markerAlive({ there: true, text, started: startedAt(pid) });
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
function relativeTempDir(subproject, dir) {
|
|
2453
|
+
let real = dir;
|
|
2454
|
+
try {
|
|
2455
|
+
real = fs4.realpathSync(dir);
|
|
2456
|
+
} catch {
|
|
2457
|
+
}
|
|
2458
|
+
return path4.relative(fs4.realpathSync(subproject), real).split(path4.sep).join("/");
|
|
2459
|
+
}
|
|
2460
|
+
function sandboxPlan(subproject, setting) {
|
|
2461
|
+
const tempDir = setting.dir === null ? null : relativeTempDir(subproject, setting.dir);
|
|
2462
|
+
let entries = [];
|
|
2463
|
+
if (setting.dir !== null) {
|
|
2464
|
+
try {
|
|
2465
|
+
entries = fs4.readdirSync(setting.dir, { withFileTypes: true });
|
|
2466
|
+
} catch {
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
const plan = planSandboxes({
|
|
2470
|
+
tempDir,
|
|
2471
|
+
entries: entries.map((entry) => ({ name: entry.name, directory: entry.isDirectory() })),
|
|
2472
|
+
keeps: setting.keeps,
|
|
2473
|
+
alive: alive(setting.dir ?? subproject)
|
|
2474
|
+
});
|
|
2475
|
+
return { plan, shown: tempDir ?? ".stryker-tmp" };
|
|
2476
|
+
}
|
|
2477
|
+
function removeStaleSandboxes(subproject, setting) {
|
|
2478
|
+
const { plan, shown } = sandboxPlan(subproject, setting);
|
|
2479
|
+
if ("kept" in plan) {
|
|
2480
|
+
if (plan.kept.length === 0) {
|
|
2481
|
+
return [];
|
|
2482
|
+
}
|
|
2483
|
+
return plan.running === null ? [
|
|
2484
|
+
`test-gates mutation: ${plan.kept.length} sandbox(es) left in ${shown} by an earlier Stryker run are kept: ${plan.why}`
|
|
2485
|
+
] : [
|
|
2486
|
+
`test-gates mutation: ${plan.why}, so the ${plan.kept.length} sandbox(es) in ${shown} are kept: they are in use`
|
|
2487
|
+
];
|
|
2488
|
+
}
|
|
2489
|
+
const dir = setting.dir;
|
|
2490
|
+
for (const marker of plan.markers) {
|
|
2491
|
+
try {
|
|
2492
|
+
fs4.rmSync(path4.join(dir, marker), { force: true });
|
|
2493
|
+
} catch {
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
const failed = [];
|
|
2497
|
+
for (const sandbox of plan.remove) {
|
|
2498
|
+
try {
|
|
2499
|
+
fs4.rmSync(path4.join(dir, sandbox), { recursive: true, force: true });
|
|
2500
|
+
} catch (error) {
|
|
2501
|
+
failed.push(`${sandbox} (${error.message})`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
const removed = plan.remove.length - failed.length;
|
|
2505
|
+
return [
|
|
2506
|
+
...removed > 0 ? [
|
|
2507
|
+
`test-gates mutation: removed ${removed} sandbox(es) an earlier Stryker run left in ${shown}`
|
|
2508
|
+
] : [],
|
|
2509
|
+
...failed.length > 0 ? [`test-gates mutation: could not remove from ${shown}: ${failed.join(", ")}`] : []
|
|
2510
|
+
];
|
|
2511
|
+
}
|
|
2512
|
+
function markRun(subproject, setting) {
|
|
2513
|
+
const dir = setting.dir;
|
|
2514
|
+
if (dir === null || !insideSubproject(relativeTempDir(subproject, dir))) {
|
|
2515
|
+
return () => void 0;
|
|
2516
|
+
}
|
|
2517
|
+
const marker = path4.join(dir, markerName(process.pid));
|
|
2518
|
+
try {
|
|
2519
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
2520
|
+
if (!insideSubproject(relativeTempDir(subproject, dir))) {
|
|
2521
|
+
return () => void 0;
|
|
2522
|
+
}
|
|
2523
|
+
fs4.writeFileSync(marker, markerText(process.pid, startedAt(process.pid)));
|
|
2524
|
+
} catch {
|
|
2525
|
+
return () => void 0;
|
|
2526
|
+
}
|
|
2527
|
+
return () => {
|
|
2528
|
+
try {
|
|
2529
|
+
fs4.rmSync(marker, { force: true });
|
|
2530
|
+
fs4.rmdirSync(dir);
|
|
2531
|
+
} catch {
|
|
2532
|
+
}
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/snapshot.ts
|
|
2537
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2125
2538
|
import { createHash } from "crypto";
|
|
2126
|
-
import
|
|
2539
|
+
import fs6 from "fs";
|
|
2127
2540
|
import { createRequire } from "module";
|
|
2128
2541
|
import os from "os";
|
|
2129
|
-
import
|
|
2542
|
+
import path6 from "path";
|
|
2130
2543
|
|
|
2131
2544
|
// src/commands/loaded-files.ts
|
|
2132
|
-
import
|
|
2545
|
+
import fs5 from "fs";
|
|
2133
2546
|
import { builtinModules } from "module";
|
|
2134
|
-
import
|
|
2547
|
+
import path5 from "path";
|
|
2135
2548
|
var SCRIPT = /\.[cm]?[jt]sx?$/;
|
|
2136
2549
|
var COMPONENT = /\.(?:vue|svelte|astro|mdx)$/;
|
|
2137
2550
|
var EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
@@ -2139,7 +2552,7 @@ var QUOTED = /(['"`])([^'"`\n]+)\1/g;
|
|
|
2139
2552
|
var RUNNER_LOAD = /\b(?:requireActual|requireMock|importActual|importMock|mock|doMock|unstable_mockModule|createMockFromModule|genMockFromModule)\s*(?:<[^()\n]*>)?\s*\(\s*(['"`])([^'"`\n]+)\1/g;
|
|
2140
2553
|
function isFile(file) {
|
|
2141
2554
|
try {
|
|
2142
|
-
return
|
|
2555
|
+
return fs5.statSync(file, { throwIfNoEntry: false })?.isFile() === true;
|
|
2143
2556
|
} catch {
|
|
2144
2557
|
return false;
|
|
2145
2558
|
}
|
|
@@ -2172,9 +2585,9 @@ function filesOf(base, directory, declarations) {
|
|
|
2172
2585
|
...extensions.map((extension) => base + extension),
|
|
2173
2586
|
// `./fee.js` written in a TypeScript file is `./fee.ts`
|
|
2174
2587
|
...extensions.map((extension) => base.replace(/\.[cm]?jsx?$/, extension)),
|
|
2175
|
-
...directory ? extensions.map((extension) =>
|
|
2588
|
+
...directory ? extensions.map((extension) => path5.join(base, `index${extension}`)) : []
|
|
2176
2589
|
];
|
|
2177
|
-
const data = [`${base}.json`, ...directory ? [
|
|
2590
|
+
const data = [`${base}.json`, ...directory ? [path5.join(base, "index.json")] : []];
|
|
2178
2591
|
const scripts = [.../* @__PURE__ */ new Set([base, ...candidates(EXTENSIONS), ...data])].filter(isFile);
|
|
2179
2592
|
return scripts.length > 0 || !declarations ? scripts : [...new Set(candidates(DECLARATIONS))].filter(isFile);
|
|
2180
2593
|
}
|
|
@@ -2183,11 +2596,11 @@ function resolveFile(base, directory = true, declarations = true) {
|
|
|
2183
2596
|
}
|
|
2184
2597
|
function installed(specifier, from) {
|
|
2185
2598
|
const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
2186
|
-
for (let current =
|
|
2187
|
-
if (
|
|
2599
|
+
for (let current = path5.dirname(from); ; current = path5.dirname(current)) {
|
|
2600
|
+
if (fs5.existsSync(path5.join(current, "node_modules", name))) {
|
|
2188
2601
|
return true;
|
|
2189
2602
|
}
|
|
2190
|
-
if (current ===
|
|
2603
|
+
if (current === path5.dirname(current)) {
|
|
2191
2604
|
return false;
|
|
2192
2605
|
}
|
|
2193
2606
|
}
|
|
@@ -2197,18 +2610,18 @@ function loadedFiles(manifest, configFile) {
|
|
|
2197
2610
|
}
|
|
2198
2611
|
function loadedFilesNow(manifest, configFile) {
|
|
2199
2612
|
const aliases = manifest.settings.importAliases;
|
|
2200
|
-
const relative = (file) =>
|
|
2613
|
+
const relative = (file) => path5.relative(manifest.dir, file).split(path5.sep).join("/");
|
|
2201
2614
|
const target = (specifier, from) => {
|
|
2202
2615
|
if (specifier.startsWith(".")) {
|
|
2203
|
-
return resolveFiles(
|
|
2616
|
+
return resolveFiles(path5.resolve(path5.dirname(from), specifier));
|
|
2204
2617
|
}
|
|
2205
2618
|
const alias = aliases.find((candidate) => specifier.startsWith(candidate.prefix));
|
|
2206
2619
|
return alias === void 0 ? [] : resolveFiles(
|
|
2207
|
-
|
|
2620
|
+
path5.resolve(manifest.dir, alias.target + specifier.slice(alias.prefix.length))
|
|
2208
2621
|
);
|
|
2209
2622
|
};
|
|
2210
2623
|
const read2 = (file) => {
|
|
2211
|
-
const source =
|
|
2624
|
+
const source = fs5.readFileSync(file, "utf8");
|
|
2212
2625
|
const imports = parseImports(source).filter((ref) => !ref.typeOnly);
|
|
2213
2626
|
const specifiers = [
|
|
2214
2627
|
.../* @__PURE__ */ new Set([
|
|
@@ -2220,7 +2633,7 @@ function loadedFilesNow(manifest, configFile) {
|
|
|
2220
2633
|
return { source, specifiers, strings };
|
|
2221
2634
|
};
|
|
2222
2635
|
const named = /* @__PURE__ */ new Set();
|
|
2223
|
-
const configs = configFile === null ? [] : [
|
|
2636
|
+
const configs = configFile === null ? [] : [path5.resolve(manifest.dir, configFile)];
|
|
2224
2637
|
const seenConfigs = new Set(configs);
|
|
2225
2638
|
const entry = configs[0];
|
|
2226
2639
|
for (let file = configs.shift(); file !== void 0; file = configs.shift()) {
|
|
@@ -2228,8 +2641,8 @@ function loadedFilesNow(manifest, configFile) {
|
|
|
2228
2641
|
const paths = strings.filter((text) => file === entry || /^(?:\.|<rootDir>\/)/.test(text));
|
|
2229
2642
|
for (const text of paths) {
|
|
2230
2643
|
const stripped = text.replace(/^<rootDir>\//, "");
|
|
2231
|
-
const here = resolveFiles(
|
|
2232
|
-
const found = here.length > 0 ? here : resolveFiles(
|
|
2644
|
+
const here = resolveFiles(path5.resolve(path5.dirname(file), stripped), false);
|
|
2645
|
+
const found = here.length > 0 ? here : resolveFiles(path5.resolve(manifest.dir, stripped), false);
|
|
2233
2646
|
for (const one of found.filter((candidate) => SCRIPT.test(candidate))) {
|
|
2234
2647
|
named.add(one);
|
|
2235
2648
|
}
|
|
@@ -2270,7 +2683,7 @@ function loadedFilesNow(manifest, configFile) {
|
|
|
2270
2683
|
}
|
|
2271
2684
|
}
|
|
2272
2685
|
for (const text of strings.filter((one) => one.startsWith("."))) {
|
|
2273
|
-
next.push(...resolveFiles(
|
|
2686
|
+
next.push(...resolveFiles(path5.resolve(path5.dirname(file), text)));
|
|
2274
2687
|
}
|
|
2275
2688
|
const visited = {
|
|
2276
2689
|
file: { path: relative(file), source, imports },
|
|
@@ -2300,7 +2713,7 @@ function loadedFilesNow(manifest, configFile) {
|
|
|
2300
2713
|
const specs = [...new Set(manifest.gates.flatMap((gate) => gate.existingSpecs))];
|
|
2301
2714
|
const roots = [
|
|
2302
2715
|
closure(null, [...named]),
|
|
2303
|
-
...specs.map((spec) => closure(spec, [
|
|
2716
|
+
...specs.map((spec) => closure(spec, [path5.join(manifest.dir, spec)]))
|
|
2304
2717
|
];
|
|
2305
2718
|
return { files: [...known.values()].map((visited) => visited.file), roots };
|
|
2306
2719
|
}
|
|
@@ -2352,17 +2765,17 @@ function hashOf(content) {
|
|
|
2352
2765
|
}
|
|
2353
2766
|
function hashFile(file) {
|
|
2354
2767
|
try {
|
|
2355
|
-
return hashOf(
|
|
2768
|
+
return hashOf(fs6.readFileSync(file));
|
|
2356
2769
|
} catch {
|
|
2357
2770
|
return null;
|
|
2358
2771
|
}
|
|
2359
2772
|
}
|
|
2360
2773
|
function isInside(parent, child) {
|
|
2361
|
-
const relative =
|
|
2362
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
2774
|
+
const relative = path6.relative(parent, child);
|
|
2775
|
+
return relative === "" || !relative.startsWith("..") && !path6.isAbsolute(relative);
|
|
2363
2776
|
}
|
|
2364
2777
|
function toPosix(file) {
|
|
2365
|
-
return file.split(
|
|
2778
|
+
return file.split(path6.sep).join("/");
|
|
2366
2779
|
}
|
|
2367
2780
|
function gitEnvironment(env) {
|
|
2368
2781
|
if (env.GIT_DIR === void 0 || env.GIT_WORK_TREE !== void 0) {
|
|
@@ -2373,7 +2786,7 @@ function gitEnvironment(env) {
|
|
|
2373
2786
|
return rest;
|
|
2374
2787
|
}
|
|
2375
2788
|
function git(cwd, args) {
|
|
2376
|
-
const result =
|
|
2789
|
+
const result = spawnSync2("git", args, {
|
|
2377
2790
|
cwd,
|
|
2378
2791
|
env: gitEnvironment(process.env),
|
|
2379
2792
|
encoding: "utf8",
|
|
@@ -2386,7 +2799,7 @@ function globLiteral(relative) {
|
|
|
2386
2799
|
}
|
|
2387
2800
|
function gitLister(root) {
|
|
2388
2801
|
return (directory, recursive) => {
|
|
2389
|
-
const relative = globLiteral(toPosix(
|
|
2802
|
+
const relative = globLiteral(toPosix(path6.relative(root, directory)));
|
|
2390
2803
|
const pattern = recursive ? `${relative === "" ? "" : `${relative}/`}**` : `${relative === "" ? "" : `${relative}/`}*`;
|
|
2391
2804
|
const listed2 = git(root, [
|
|
2392
2805
|
"ls-files",
|
|
@@ -2405,8 +2818,8 @@ function gitLister(root) {
|
|
|
2405
2818
|
}
|
|
2406
2819
|
function walkLister(root) {
|
|
2407
2820
|
const walk = (directory, recursive, found) => {
|
|
2408
|
-
for (const entry of
|
|
2409
|
-
const file =
|
|
2821
|
+
for (const entry of fs6.readdirSync(directory, { withFileTypes: true })) {
|
|
2822
|
+
const file = path6.join(directory, entry.name);
|
|
2410
2823
|
if (NEVER.includes(entry.name)) {
|
|
2411
2824
|
continue;
|
|
2412
2825
|
}
|
|
@@ -2415,7 +2828,7 @@ function walkLister(root) {
|
|
|
2415
2828
|
walk(file, true, found);
|
|
2416
2829
|
}
|
|
2417
2830
|
} else {
|
|
2418
|
-
found.push(toPosix(
|
|
2831
|
+
found.push(toPosix(path6.relative(root, file)));
|
|
2419
2832
|
}
|
|
2420
2833
|
}
|
|
2421
2834
|
return found;
|
|
@@ -2425,16 +2838,16 @@ function walkLister(root) {
|
|
|
2425
2838
|
function upTo(from, to) {
|
|
2426
2839
|
const directories = [from];
|
|
2427
2840
|
while (directories[directories.length - 1] !== to) {
|
|
2428
|
-
directories.push(
|
|
2841
|
+
directories.push(path6.dirname(directories[directories.length - 1]));
|
|
2429
2842
|
}
|
|
2430
2843
|
return directories;
|
|
2431
2844
|
}
|
|
2432
2845
|
function declaresWorkspaces(directory) {
|
|
2433
|
-
if (
|
|
2846
|
+
if (fs6.existsSync(path6.join(directory, "pnpm-workspace.yaml"))) {
|
|
2434
2847
|
return true;
|
|
2435
2848
|
}
|
|
2436
2849
|
try {
|
|
2437
|
-
const manifest = JSON.parse(
|
|
2850
|
+
const manifest = JSON.parse(fs6.readFileSync(path6.join(directory, "package.json"), "utf8"));
|
|
2438
2851
|
return manifest.workspaces !== void 0;
|
|
2439
2852
|
} catch {
|
|
2440
2853
|
return false;
|
|
@@ -2442,7 +2855,7 @@ function declaresWorkspaces(directory) {
|
|
|
2442
2855
|
}
|
|
2443
2856
|
function walkRoot(dir) {
|
|
2444
2857
|
const candidates = withinBounds(dir);
|
|
2445
|
-
const repository = candidates.find((directory) =>
|
|
2858
|
+
const repository = candidates.find((directory) => fs6.existsSync(path6.join(directory, ".git")));
|
|
2446
2859
|
if (repository !== void 0) {
|
|
2447
2860
|
return repository;
|
|
2448
2861
|
}
|
|
@@ -2451,11 +2864,11 @@ function walkRoot(dir) {
|
|
|
2451
2864
|
function withinBounds(dir) {
|
|
2452
2865
|
let home = null;
|
|
2453
2866
|
try {
|
|
2454
|
-
home =
|
|
2867
|
+
home = fs6.realpathSync(os.homedir());
|
|
2455
2868
|
} catch {
|
|
2456
2869
|
}
|
|
2457
|
-
return upTo(dir,
|
|
2458
|
-
(directory) => directory !==
|
|
2870
|
+
return upTo(dir, path6.parse(dir).root).filter(
|
|
2871
|
+
(directory) => directory !== path6.parse(dir).root && (home === null || !isInside(directory, home))
|
|
2459
2872
|
);
|
|
2460
2873
|
}
|
|
2461
2874
|
function linkedPackages(nodeModules, root) {
|
|
@@ -2463,20 +2876,20 @@ function linkedPackages(nodeModules, root) {
|
|
|
2463
2876
|
const visit = (directory, depth) => {
|
|
2464
2877
|
let entries;
|
|
2465
2878
|
try {
|
|
2466
|
-
entries =
|
|
2879
|
+
entries = fs6.readdirSync(directory, { withFileTypes: true });
|
|
2467
2880
|
} catch {
|
|
2468
2881
|
return;
|
|
2469
2882
|
}
|
|
2470
2883
|
for (const entry of entries) {
|
|
2471
|
-
const file =
|
|
2884
|
+
const file = path6.join(directory, entry.name);
|
|
2472
2885
|
if (entry.isSymbolicLink()) {
|
|
2473
2886
|
let target;
|
|
2474
2887
|
try {
|
|
2475
|
-
target =
|
|
2888
|
+
target = fs6.realpathSync(file);
|
|
2476
2889
|
} catch {
|
|
2477
2890
|
continue;
|
|
2478
2891
|
}
|
|
2479
|
-
if (target.split(
|
|
2892
|
+
if (target.split(path6.sep).includes("node_modules") || !fs6.statSync(target).isDirectory()) {
|
|
2480
2893
|
continue;
|
|
2481
2894
|
}
|
|
2482
2895
|
if (!isInside(root, target)) {
|
|
@@ -2498,11 +2911,11 @@ function installedVersion(start, name) {
|
|
|
2498
2911
|
for (; ; ) {
|
|
2499
2912
|
try {
|
|
2500
2913
|
const manifest = JSON.parse(
|
|
2501
|
-
|
|
2914
|
+
fs6.readFileSync(path6.join(current, "node_modules", name, "package.json"), "utf8")
|
|
2502
2915
|
);
|
|
2503
2916
|
return typeof manifest.version === "string" ? manifest.version : "unknown";
|
|
2504
2917
|
} catch {
|
|
2505
|
-
const parent =
|
|
2918
|
+
const parent = path6.dirname(current);
|
|
2506
2919
|
if (parent === current) {
|
|
2507
2920
|
return null;
|
|
2508
2921
|
}
|
|
@@ -2511,7 +2924,7 @@ function installedVersion(start, name) {
|
|
|
2511
2924
|
}
|
|
2512
2925
|
}
|
|
2513
2926
|
function nodeOnPath(cwd) {
|
|
2514
|
-
const result =
|
|
2927
|
+
const result = spawnSync2("node", ["--version"], {
|
|
2515
2928
|
cwd,
|
|
2516
2929
|
encoding: "utf8",
|
|
2517
2930
|
shell: process.platform === "win32",
|
|
@@ -2528,12 +2941,12 @@ function snapshotOf(input) {
|
|
|
2528
2941
|
return resolving(() => snapshotNow(input));
|
|
2529
2942
|
}
|
|
2530
2943
|
function snapshotNow(input) {
|
|
2531
|
-
const dir =
|
|
2944
|
+
const dir = fs6.realpathSync(input.dir);
|
|
2532
2945
|
const top = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
2533
|
-
let root = top === null ? dir :
|
|
2946
|
+
let root = top === null ? dir : fs6.realpathSync(top.trim());
|
|
2534
2947
|
let lister = gitLister(root);
|
|
2535
2948
|
let mode = "git";
|
|
2536
|
-
const manifest = toPosix(
|
|
2949
|
+
const manifest = toPosix(path6.relative(root, path6.join(dir, "test-gates.json")));
|
|
2537
2950
|
if (top === null || !lister(dir, false).includes(manifest)) {
|
|
2538
2951
|
mode = "walk";
|
|
2539
2952
|
root = walkRoot(dir);
|
|
@@ -2541,17 +2954,17 @@ function snapshotNow(input) {
|
|
|
2541
2954
|
}
|
|
2542
2955
|
const listRoot = root;
|
|
2543
2956
|
const list = lister;
|
|
2544
|
-
const followed = input.scope === void 0 ? null : input.scope(list(dir, true).map((file) =>
|
|
2957
|
+
const followed = input.scope === void 0 ? null : input.scope(list(dir, true).map((file) => path6.join(listRoot, file)));
|
|
2545
2958
|
const walk = walkLister(listRoot);
|
|
2546
2959
|
const written = input.written.map((file) => {
|
|
2547
|
-
const target =
|
|
2960
|
+
const target = path6.resolve(dir, file);
|
|
2548
2961
|
try {
|
|
2549
|
-
return
|
|
2962
|
+
return fs6.realpathSync(target);
|
|
2550
2963
|
} catch {
|
|
2551
2964
|
return target;
|
|
2552
2965
|
}
|
|
2553
2966
|
});
|
|
2554
|
-
const swallowing = written.find((target) => isInside(target,
|
|
2967
|
+
const swallowing = written.find((target) => isInside(target, path6.join(dir, "test-gates.json")));
|
|
2555
2968
|
if (swallowing !== void 0) {
|
|
2556
2969
|
throw new SnapshotError(`the run writes to ${swallowing}, which holds test-gates.json`);
|
|
2557
2970
|
}
|
|
@@ -2568,12 +2981,12 @@ function snapshotNow(input) {
|
|
|
2568
2981
|
if (!isInside(listRoot, directory)) {
|
|
2569
2982
|
const written2 = reference === void 0 ? "" : ` with ${JSON.stringify(reference)}`;
|
|
2570
2983
|
throw new SnapshotError(
|
|
2571
|
-
`${toPosix(
|
|
2984
|
+
`${toPosix(path6.relative(dir, from))} points outside of what can be listed${written2} (${directory})`
|
|
2572
2985
|
);
|
|
2573
2986
|
}
|
|
2574
2987
|
if (directory === listRoot && listRoot !== dir && widened.length < WIDENED_SHOWN) {
|
|
2575
2988
|
const written2 = reference === void 0 ? "" : ` ${JSON.stringify(reference)}`;
|
|
2576
|
-
widened.push(`${toPosix(
|
|
2989
|
+
widened.push(`${toPosix(path6.relative(listRoot, from))}${written2}`);
|
|
2577
2990
|
}
|
|
2578
2991
|
if (![...queued].some((region) => isInside(region, directory))) {
|
|
2579
2992
|
queued.add(directory);
|
|
@@ -2582,10 +2995,10 @@ function snapshotNow(input) {
|
|
|
2582
2995
|
};
|
|
2583
2996
|
const follow = (fixed, open, anchored, file, reference) => {
|
|
2584
2997
|
const from = (start) => {
|
|
2585
|
-
const target =
|
|
2998
|
+
const target = path6.resolve(start, fixed);
|
|
2586
2999
|
let isDirectory2 = false;
|
|
2587
3000
|
try {
|
|
2588
|
-
isDirectory2 =
|
|
3001
|
+
isDirectory2 = fs6.statSync(target).isDirectory();
|
|
2589
3002
|
} catch {
|
|
2590
3003
|
}
|
|
2591
3004
|
return {
|
|
@@ -2594,9 +3007,9 @@ function snapshotNow(input) {
|
|
|
2594
3007
|
found: isDirectory2 || resolveFile(target, true, false) !== null
|
|
2595
3008
|
};
|
|
2596
3009
|
};
|
|
2597
|
-
const fromFile = from(
|
|
3010
|
+
const fromFile = from(path6.dirname(file));
|
|
2598
3011
|
const targets = [fromFile];
|
|
2599
|
-
if (!anchored &&
|
|
3012
|
+
if (!anchored && path6.dirname(file) !== dir) {
|
|
2600
3013
|
const fromRun = from(dir);
|
|
2601
3014
|
if (fromRun.found && !fromFile.found) {
|
|
2602
3015
|
targets[0] = fromRun;
|
|
@@ -2615,24 +3028,24 @@ function snapshotNow(input) {
|
|
|
2615
3028
|
continue;
|
|
2616
3029
|
}
|
|
2617
3030
|
if (!isDirectory2 && !isInside(listRoot, target) && resolveFile(target, true, false) === null) {
|
|
2618
|
-
files[toPosix(
|
|
3031
|
+
files[toPosix(path6.relative(listRoot, target))] = "missing";
|
|
2619
3032
|
continue;
|
|
2620
3033
|
}
|
|
2621
|
-
const directory = isDirectory2 ? target :
|
|
2622
|
-
if (
|
|
3034
|
+
const directory = isDirectory2 ? target : path6.dirname(target);
|
|
3035
|
+
if (fs6.existsSync(directory)) {
|
|
2623
3036
|
enqueue(directory, file, reference, anchored && !open);
|
|
2624
3037
|
}
|
|
2625
3038
|
}
|
|
2626
3039
|
};
|
|
2627
3040
|
const reached = /* @__PURE__ */ new Set();
|
|
2628
3041
|
const reach = (file) => {
|
|
2629
|
-
if (reached.has(file) || DOCUMENTS.includes(
|
|
3042
|
+
if (reached.has(file) || DOCUMENTS.includes(path6.extname(file).toLowerCase())) {
|
|
2630
3043
|
return;
|
|
2631
3044
|
}
|
|
2632
3045
|
reached.add(file);
|
|
2633
3046
|
let text;
|
|
2634
3047
|
try {
|
|
2635
|
-
const content =
|
|
3048
|
+
const content = fs6.readFileSync(file);
|
|
2636
3049
|
if (content.length > SCAN_LIMIT || content.subarray(0, 8e3).includes(0)) {
|
|
2637
3050
|
return;
|
|
2638
3051
|
}
|
|
@@ -2664,7 +3077,7 @@ function snapshotNow(input) {
|
|
|
2664
3077
|
const end = (match.index ?? 0) + match[0].length;
|
|
2665
3078
|
const open = match[3] === void 0 || /^\s*[,+.}[]/.test(text.slice(end, end + 200));
|
|
2666
3079
|
const reference = match[0].slice(0, match[0].length - (match[3]?.length ?? 0));
|
|
2667
|
-
follow(
|
|
3080
|
+
follow(path6.posix.join(...segments), open, true, file, reference);
|
|
2668
3081
|
}
|
|
2669
3082
|
for (const match of text.matchAll(UPWARD)) {
|
|
2670
3083
|
const at = match.index ?? 0;
|
|
@@ -2684,35 +3097,35 @@ function snapshotNow(input) {
|
|
|
2684
3097
|
}
|
|
2685
3098
|
};
|
|
2686
3099
|
const add = (relative) => {
|
|
2687
|
-
const file =
|
|
3100
|
+
const file = path6.join(listRoot, relative);
|
|
2688
3101
|
if (relative in files || written.some((target) => isInside(target, file))) {
|
|
2689
3102
|
return;
|
|
2690
3103
|
}
|
|
2691
3104
|
let stat;
|
|
2692
3105
|
try {
|
|
2693
|
-
stat =
|
|
3106
|
+
stat = fs6.lstatSync(file);
|
|
2694
3107
|
} catch {
|
|
2695
3108
|
files[relative] = "missing";
|
|
2696
3109
|
return;
|
|
2697
3110
|
}
|
|
2698
3111
|
if (stat.isSymbolicLink()) {
|
|
2699
|
-
files[relative] = `link ${
|
|
3112
|
+
files[relative] = `link ${fs6.readlinkSync(file)}`;
|
|
2700
3113
|
let target;
|
|
2701
3114
|
try {
|
|
2702
|
-
target =
|
|
3115
|
+
target = fs6.realpathSync(file);
|
|
2703
3116
|
} catch {
|
|
2704
3117
|
return;
|
|
2705
3118
|
}
|
|
2706
|
-
enqueue(
|
|
3119
|
+
enqueue(fs6.statSync(target).isDirectory() ? target : path6.dirname(target), file);
|
|
2707
3120
|
} else if (stat.isDirectory()) {
|
|
2708
3121
|
files[relative] = "directory";
|
|
2709
3122
|
walk(file, true).forEach(add);
|
|
2710
3123
|
} else if (!stat.isFile()) {
|
|
2711
3124
|
files[relative] = "special";
|
|
2712
3125
|
} else {
|
|
2713
|
-
const content =
|
|
3126
|
+
const content = fs6.readFileSync(file);
|
|
2714
3127
|
files[relative] = hashOf(content);
|
|
2715
|
-
const document = DOCUMENTS.includes(
|
|
3128
|
+
const document = DOCUMENTS.includes(path6.extname(file).toLowerCase());
|
|
2716
3129
|
const loaded = followed === null || !isInside(dir, file) || followed.has(file);
|
|
2717
3130
|
if (loaded && !document && content.length <= SCAN_LIMIT && !content.subarray(0, 8e3).includes(0)) {
|
|
2718
3131
|
scan(content.toString("utf8").replace(/\\[\\/]/g, "/"), file);
|
|
@@ -2723,32 +3136,32 @@ function snapshotNow(input) {
|
|
|
2723
3136
|
const besideAncestors = /* @__PURE__ */ new Set();
|
|
2724
3137
|
const ancestorFiles = ancestors.flatMap((directory) => list(directory, false));
|
|
2725
3138
|
for (const relative of ancestorFiles) {
|
|
2726
|
-
besideAncestors.add(
|
|
3139
|
+
besideAncestors.add(path6.join(listRoot, relative));
|
|
2727
3140
|
}
|
|
2728
3141
|
ancestorFiles.forEach(add);
|
|
2729
3142
|
for (const directory of [dir, ...ancestors]) {
|
|
2730
|
-
const patches =
|
|
2731
|
-
if (
|
|
3143
|
+
const patches = path6.join(directory, "patches");
|
|
3144
|
+
if (fs6.existsSync(patches) && fs6.statSync(patches).isDirectory()) {
|
|
2732
3145
|
enqueue(patches, directory);
|
|
2733
3146
|
}
|
|
2734
3147
|
}
|
|
2735
3148
|
for (const directory of [dir, ...ancestors]) {
|
|
2736
|
-
for (const linked of linkedPackages(
|
|
3149
|
+
for (const linked of linkedPackages(path6.join(directory, "node_modules"), listRoot)) {
|
|
2737
3150
|
if (!isInside(linked, dir)) {
|
|
2738
|
-
enqueue(linked,
|
|
3151
|
+
enqueue(linked, path6.join(directory, "node_modules"));
|
|
2739
3152
|
}
|
|
2740
3153
|
}
|
|
2741
3154
|
}
|
|
2742
3155
|
const above = withinBounds(listRoot).slice(1);
|
|
2743
3156
|
for (const directory of [dir, ...ancestors, ...above]) {
|
|
2744
3157
|
const candidates = [
|
|
2745
|
-
...INSTALL_MARKERS.map((marker) =>
|
|
2746
|
-
...above.includes(directory) ? LOCK_FILES.map((name) =>
|
|
3158
|
+
...INSTALL_MARKERS.map((marker) => path6.join(directory, "node_modules", marker)),
|
|
3159
|
+
...above.includes(directory) ? LOCK_FILES.map((name) => path6.join(directory, name)) : []
|
|
2747
3160
|
];
|
|
2748
3161
|
for (const file of candidates) {
|
|
2749
3162
|
const hash = hashFile(file);
|
|
2750
3163
|
if (hash !== null) {
|
|
2751
|
-
files[toPosix(
|
|
3164
|
+
files[toPosix(path6.relative(listRoot, file))] = hash;
|
|
2752
3165
|
}
|
|
2753
3166
|
}
|
|
2754
3167
|
}
|
|
@@ -2760,9 +3173,9 @@ function snapshotNow(input) {
|
|
|
2760
3173
|
};
|
|
2761
3174
|
drain();
|
|
2762
3175
|
for (const file of followed ?? []) {
|
|
2763
|
-
const parts =
|
|
2764
|
-
if (isInside(listRoot, file) && !parts.some((part) => NEVER.includes(part)) &&
|
|
2765
|
-
add(toPosix(
|
|
3176
|
+
const parts = path6.relative(listRoot, file).split(path6.sep);
|
|
3177
|
+
if (isInside(listRoot, file) && !parts.some((part) => NEVER.includes(part)) && fs6.statSync(file, { throwIfNoEntry: false })?.isFile() === true) {
|
|
3178
|
+
add(toPosix(path6.relative(listRoot, file)));
|
|
2766
3179
|
}
|
|
2767
3180
|
}
|
|
2768
3181
|
drain();
|
|
@@ -2782,14 +3195,18 @@ function snapshotNow(input) {
|
|
|
2782
3195
|
}
|
|
2783
3196
|
|
|
2784
3197
|
// src/affected-files.ts
|
|
2785
|
-
import
|
|
3198
|
+
import fs7 from "fs";
|
|
2786
3199
|
import { builtinModules as builtinModules2 } from "module";
|
|
2787
|
-
import
|
|
3200
|
+
import path7 from "path";
|
|
2788
3201
|
var AffectedError = class extends Error {
|
|
2789
3202
|
};
|
|
2790
|
-
var RUNTIME_PATHS = /\b(?:require\.(?:context|resolve)|process\.cwd|createRequire|toMatchFileSnapshot|readFileSync|readdirSync)\b|\bimport\.meta\.(?:glob|resolve)\b/;
|
|
3203
|
+
var RUNTIME_PATHS = /\b(?:require\.(?:context|resolve)|process\.cwd|createRequire|toMatchFileSnapshot|readFileSync|readFile|readdirSync|fromFile|fromURL)\b|\bimport\.meta\.(?:glob|resolve)\b/;
|
|
3204
|
+
var PATH_FROM_TOP = /^[^\s./?#*:][^\s?#*:]*\.(?:html?|md|json)$/i;
|
|
3205
|
+
var withoutQuery = (specifier) => specifier.replace(/\?[^/]*$/, "");
|
|
3206
|
+
var RUNTIME_LOADS = /\b(?:require\.(?:context|resolve)|process\.cwd|createRequire)\b|\bimport\.meta\.(?:glob|resolve)\b/;
|
|
2791
3207
|
var LOCATION = /\b(?:__dirname|__filename)\b|\bimport\.meta\.(?:url|dirname|filename)\b/;
|
|
2792
3208
|
var LOCATION_READS = /\b(?:readFile|readdir|readlink|existsSync|stat|statSync|lstat|lstatSync|access|accessSync|realpath|realpathSync|open|openSync|opendir|opendirSync|createReadStream|watch|watchFile|glob|globSync|promises)\b|\b(?:requireActual|requireMock|importActual|importMock|mock|doMock|unstable_mockModule|setMock|createMockFromModule)\(\s*(?![\s'"`)])|\bWorker\(/;
|
|
3209
|
+
var LOCATION_LOADS = /\b(?:requireActual|requireMock|importActual|importMock|mock|doMock|unstable_mockModule|setMock|createMockFromModule)\(\s*(?![\s'"`)])|\bWorker\(/;
|
|
2793
3210
|
var LOCATION_SAFE = /* @__PURE__ */ new Set([
|
|
2794
3211
|
"path",
|
|
2795
3212
|
"path/posix",
|
|
@@ -2810,30 +3227,31 @@ var FILE_MODULES = /* @__PURE__ */ new Set([
|
|
|
2810
3227
|
"fast-glob",
|
|
2811
3228
|
"globby"
|
|
2812
3229
|
]);
|
|
3230
|
+
var LOADING_MODULES = /* @__PURE__ */ new Set(["child_process", "module", "worker_threads"]);
|
|
2813
3231
|
var bare = (specifier) => specifier.replace(/^node:/, "");
|
|
2814
3232
|
function linkedPackage(specifier, from) {
|
|
2815
3233
|
const name = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
|
|
2816
|
-
for (let current =
|
|
2817
|
-
const candidate =
|
|
2818
|
-
if (
|
|
3234
|
+
for (let current = path7.dirname(from); ; current = path7.dirname(current)) {
|
|
3235
|
+
const candidate = path7.join(current, "node_modules", name);
|
|
3236
|
+
if (fs7.existsSync(candidate)) {
|
|
2819
3237
|
const target = realpath(candidate);
|
|
2820
|
-
return target.split(
|
|
3238
|
+
return target.split(path7.sep).includes("node_modules") ? null : target;
|
|
2821
3239
|
}
|
|
2822
|
-
if (current ===
|
|
3240
|
+
if (current === path7.dirname(current)) {
|
|
2823
3241
|
return null;
|
|
2824
3242
|
}
|
|
2825
3243
|
}
|
|
2826
3244
|
}
|
|
2827
|
-
var toPosix2 = (file) => file.split(
|
|
3245
|
+
var toPosix2 = (file) => file.split(path7.sep).join("/");
|
|
2828
3246
|
function realpath(file) {
|
|
2829
3247
|
try {
|
|
2830
|
-
return
|
|
3248
|
+
return fs7.realpathSync.native(file);
|
|
2831
3249
|
} catch {
|
|
2832
3250
|
return file;
|
|
2833
3251
|
}
|
|
2834
3252
|
}
|
|
2835
3253
|
function runnerConfigs(dir, stryker, options) {
|
|
2836
|
-
const files = [
|
|
3254
|
+
const files = [path7.resolve(dir, stryker)];
|
|
2837
3255
|
const named = options.testRunner === "vitest" ? options.vitestConfig : options.jestConfig;
|
|
2838
3256
|
if (options.testRunner !== "vitest" && options.testRunner !== "jest") {
|
|
2839
3257
|
throw new AffectedError(
|
|
@@ -2841,20 +3259,20 @@ function runnerConfigs(dir, stryker, options) {
|
|
|
2841
3259
|
);
|
|
2842
3260
|
}
|
|
2843
3261
|
if (typeof named === "string") {
|
|
2844
|
-
files.push(
|
|
3262
|
+
files.push(path7.resolve(dir, named));
|
|
2845
3263
|
} else {
|
|
2846
3264
|
const names = options.testRunner === "vitest" ? ["vitest.config", "vite.config"] : ["jest.config"];
|
|
2847
3265
|
for (const name of names) {
|
|
2848
3266
|
for (const extension of [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"]) {
|
|
2849
|
-
const file =
|
|
2850
|
-
if (
|
|
3267
|
+
const file = path7.join(dir, name + extension);
|
|
3268
|
+
if (fs7.existsSync(file)) {
|
|
2851
3269
|
files.push(file);
|
|
2852
3270
|
}
|
|
2853
3271
|
}
|
|
2854
3272
|
}
|
|
2855
3273
|
}
|
|
2856
3274
|
return {
|
|
2857
|
-
files: files.filter((file) =>
|
|
3275
|
+
files: files.filter((file) => fs7.existsSync(file)).map(realpath),
|
|
2858
3276
|
runner: options.testRunner,
|
|
2859
3277
|
checkers: Array.isArray(options.checkers) && options.checkers.length > 0
|
|
2860
3278
|
};
|
|
@@ -2875,24 +3293,24 @@ function typesErased(configs, texts, compiler) {
|
|
|
2875
3293
|
erased = /\bts-jest\b/.test(all) ? TS_JEST_STRIPS.test(all) && !TS_JEST_UNSURE.test(all) : STRIPPING_TRANSFORMS.test(all) && !/\bpreset\b/.test(all);
|
|
2876
3294
|
}
|
|
2877
3295
|
}
|
|
2878
|
-
const inline = erased && !KEEPS_IMPORTS.test(all) && compiler.some(({ file }) => /^tsconfig/.test(
|
|
3296
|
+
const inline = erased && !KEEPS_IMPORTS.test(all) && compiler.some(({ file }) => /^tsconfig/.test(path7.basename(file))) && compiler.every(({ text }) => text !== null && !KEEPS_IMPORTS.test(text));
|
|
2879
3297
|
return { erased, inline };
|
|
2880
3298
|
}
|
|
2881
3299
|
function compilerConfigs(dir, root, keys) {
|
|
2882
3300
|
const above = /* @__PURE__ */ new Set();
|
|
2883
|
-
for (let current =
|
|
2884
|
-
const relative =
|
|
2885
|
-
if (relative.startsWith("..") ||
|
|
3301
|
+
for (let current = path7.dirname(dir); ; current = path7.dirname(current)) {
|
|
3302
|
+
const relative = path7.relative(root, current);
|
|
3303
|
+
if (relative.startsWith("..") || path7.isAbsolute(relative)) {
|
|
2886
3304
|
break;
|
|
2887
3305
|
}
|
|
2888
3306
|
above.add(current);
|
|
2889
|
-
if (current ===
|
|
3307
|
+
if (current === path7.dirname(current)) {
|
|
2890
3308
|
break;
|
|
2891
3309
|
}
|
|
2892
3310
|
}
|
|
2893
|
-
const compared = new Set(keys.map((key) =>
|
|
3311
|
+
const compared = new Set(keys.map((key) => path7.join(root, key)));
|
|
2894
3312
|
const starts = [...compared].filter(
|
|
2895
|
-
(file) => (COMPILER_CONFIG.test(
|
|
3313
|
+
(file) => (COMPILER_CONFIG.test(path7.basename(file)) || path7.basename(file) === "package.json") && (file.startsWith(`${dir}${path7.sep}`) || above.has(path7.dirname(file)))
|
|
2896
3314
|
);
|
|
2897
3315
|
const found = [];
|
|
2898
3316
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2903,9 +3321,9 @@ function compilerConfigs(dir, root, keys) {
|
|
|
2903
3321
|
}
|
|
2904
3322
|
seen.add(file);
|
|
2905
3323
|
let text = null;
|
|
2906
|
-
if (compared.has(file) || file.split(
|
|
3324
|
+
if (compared.has(file) || file.split(path7.sep).includes("node_modules")) {
|
|
2907
3325
|
try {
|
|
2908
|
-
text =
|
|
3326
|
+
text = fs7.readFileSync(file, "utf8");
|
|
2909
3327
|
} catch {
|
|
2910
3328
|
text = null;
|
|
2911
3329
|
}
|
|
@@ -2921,24 +3339,24 @@ function extended(base, from) {
|
|
|
2921
3339
|
const candidates = (start) => [
|
|
2922
3340
|
start,
|
|
2923
3341
|
`${start}.json`,
|
|
2924
|
-
|
|
3342
|
+
path7.join(start, "tsconfig.json")
|
|
2925
3343
|
];
|
|
2926
|
-
if (base.startsWith(".") ||
|
|
2927
|
-
return candidates(
|
|
3344
|
+
if (base.startsWith(".") || path7.isAbsolute(base)) {
|
|
3345
|
+
return candidates(path7.resolve(path7.dirname(from), base)).find(isFile2) ?? null;
|
|
2928
3346
|
}
|
|
2929
|
-
for (let current =
|
|
2930
|
-
const file = candidates(
|
|
3347
|
+
for (let current = path7.dirname(from); ; current = path7.dirname(current)) {
|
|
3348
|
+
const file = candidates(path7.join(current, "node_modules", base)).find(isFile2);
|
|
2931
3349
|
if (file !== void 0) {
|
|
2932
3350
|
return file;
|
|
2933
3351
|
}
|
|
2934
|
-
if (current ===
|
|
3352
|
+
if (current === path7.dirname(current)) {
|
|
2935
3353
|
return null;
|
|
2936
3354
|
}
|
|
2937
3355
|
}
|
|
2938
3356
|
}
|
|
2939
3357
|
function isFile2(file) {
|
|
2940
3358
|
try {
|
|
2941
|
-
return
|
|
3359
|
+
return fs7.statSync(file).isFile();
|
|
2942
3360
|
} catch {
|
|
2943
3361
|
return false;
|
|
2944
3362
|
}
|
|
@@ -2949,29 +3367,33 @@ function fileEdges(file, context) {
|
|
|
2949
3367
|
if (known !== void 0) {
|
|
2950
3368
|
return known;
|
|
2951
3369
|
}
|
|
2952
|
-
const shown = (one) => toPosix2(
|
|
3370
|
+
const shown = (one) => toPosix2(path7.relative(dir, one));
|
|
2953
3371
|
const target = (specifier, from) => {
|
|
2954
3372
|
if (specifier.startsWith(".")) {
|
|
2955
|
-
return resolveFiles(
|
|
3373
|
+
return resolveFiles(path7.resolve(path7.dirname(from), specifier));
|
|
2956
3374
|
}
|
|
2957
3375
|
const alias = aliases.find((candidate) => specifier.startsWith(candidate.prefix));
|
|
2958
|
-
return alias === void 0 ? [] : resolveFiles(
|
|
3376
|
+
return alias === void 0 ? [] : resolveFiles(path7.resolve(dir, alias.target + specifier.slice(alias.prefix.length)));
|
|
3377
|
+
};
|
|
3378
|
+
const found = { loads: [], unknown: null, loading: null, computed: false, unnamed: [] };
|
|
3379
|
+
const loading = (reason) => {
|
|
3380
|
+
found.unknown ??= reason;
|
|
3381
|
+
found.loading ??= reason;
|
|
2959
3382
|
};
|
|
2960
|
-
const found = { loads: [], unknown: null, computed: false, unnamed: [] };
|
|
2961
3383
|
cache.set(file, found);
|
|
2962
|
-
const snapshotFile =
|
|
2963
|
-
|
|
3384
|
+
const snapshotFile = path7.join(
|
|
3385
|
+
path7.dirname(file),
|
|
2964
3386
|
"__snapshots__",
|
|
2965
|
-
`${
|
|
3387
|
+
`${path7.basename(file)}.snap`
|
|
2966
3388
|
);
|
|
2967
|
-
if (
|
|
3389
|
+
if (fs7.existsSync(snapshotFile)) {
|
|
2968
3390
|
found.loads.push(realpath(snapshotFile));
|
|
2969
3391
|
}
|
|
2970
|
-
const mocks =
|
|
2971
|
-
const name =
|
|
3392
|
+
const mocks = path7.join(path7.dirname(file), "__mocks__");
|
|
3393
|
+
const name = path7.basename(file);
|
|
2972
3394
|
const candidates = isDirectory(mocks) ? [
|
|
2973
|
-
|
|
2974
|
-
...resolveFiles(
|
|
3395
|
+
path7.join(mocks, name),
|
|
3396
|
+
...resolveFiles(path7.join(mocks, name.replace(/\.[^.]+$/, "")), false, false)
|
|
2975
3397
|
] : [];
|
|
2976
3398
|
for (const mock of new Set(candidates)) {
|
|
2977
3399
|
if (isFile2(mock)) {
|
|
@@ -2981,18 +3403,23 @@ function fileEdges(file, context) {
|
|
|
2981
3403
|
if (!SCRIPT.test(file) && !COMPONENT.test(file)) {
|
|
2982
3404
|
return found;
|
|
2983
3405
|
}
|
|
2984
|
-
const source =
|
|
3406
|
+
const source = fs7.readFileSync(file, "utf8");
|
|
2985
3407
|
const code = withoutComments(source);
|
|
2986
3408
|
if (COMPONENT.test(file)) {
|
|
2987
|
-
|
|
3409
|
+
loading(`${shown(file)} is a component, which can use others that it does not import`);
|
|
2988
3410
|
}
|
|
2989
3411
|
const uncheckable = findUncheckableImports(code)[0];
|
|
2990
3412
|
const runtime = RUNTIME_PATHS.exec(code);
|
|
3413
|
+
const runtimeLoads = RUNTIME_LOADS.exec(code);
|
|
2991
3414
|
if (uncheckable !== void 0) {
|
|
2992
3415
|
found.unknown = `${shown(file)}:${uncheckable} imports a module whose path is not written out`;
|
|
3416
|
+
found.loading = found.unknown;
|
|
2993
3417
|
found.computed = true;
|
|
2994
3418
|
} else if (runtime !== null) {
|
|
2995
3419
|
found.unknown = `${shown(file)} uses ${runtime[0]}, which can reach files that no import names`;
|
|
3420
|
+
if (runtimeLoads !== null) {
|
|
3421
|
+
found.loading ??= `${shown(file)} uses ${runtimeLoads[0]}, which can reach files that no import names`;
|
|
3422
|
+
}
|
|
2996
3423
|
}
|
|
2997
3424
|
const location = LOCATION.exec(code);
|
|
2998
3425
|
const refs = parseImports(code);
|
|
@@ -3007,30 +3434,39 @@ function fileEdges(file, context) {
|
|
|
3007
3434
|
typeLines.set(key, (typeLines.get(key) ?? 0) + 1);
|
|
3008
3435
|
}
|
|
3009
3436
|
for (const specifier of specifiers) {
|
|
3010
|
-
if (
|
|
3437
|
+
if (path7.isAbsolute(specifier) || specifier.startsWith("file:")) {
|
|
3011
3438
|
const resolved2 = resolveFiles(
|
|
3012
|
-
specifier.startsWith("file:") ? new URL(specifier).pathname : specifier
|
|
3439
|
+
withoutQuery(specifier.startsWith("file:") ? new URL(specifier).pathname : specifier)
|
|
3013
3440
|
);
|
|
3014
3441
|
if (resolved2.length === 0) {
|
|
3015
|
-
|
|
3442
|
+
loading(`${shown(file)} imports ${specifier}, which leads to no file`);
|
|
3016
3443
|
} else {
|
|
3017
3444
|
found.loads.push(...resolved2.map(realpath));
|
|
3018
3445
|
}
|
|
3019
3446
|
} else if (isProjectImport(specifier, aliases)) {
|
|
3020
|
-
const resolved2 = target(specifier, file);
|
|
3447
|
+
const resolved2 = target(withoutQuery(specifier), file);
|
|
3021
3448
|
if (resolved2.length === 0) {
|
|
3022
|
-
|
|
3449
|
+
loading(`${shown(file)} imports ${specifier}, which leads to no file`);
|
|
3023
3450
|
} else {
|
|
3024
3451
|
found.loads.push(...resolved2.map(realpath));
|
|
3025
3452
|
}
|
|
3026
3453
|
} else if (FILE_MODULES.has(bare(specifier))) {
|
|
3027
|
-
|
|
3454
|
+
const reason = `${shown(file)} imports ${specifier}, which can reach files that no import names`;
|
|
3455
|
+
if (LOADING_MODULES.has(bare(specifier))) {
|
|
3456
|
+
loading(reason);
|
|
3457
|
+
} else {
|
|
3458
|
+
found.unknown ??= reason;
|
|
3459
|
+
}
|
|
3028
3460
|
} else if (!specifier.startsWith("node:") && !builtinModules2.includes(specifier)) {
|
|
3029
3461
|
if (!installed(specifier, file)) {
|
|
3030
|
-
|
|
3462
|
+
loading(
|
|
3463
|
+
`${shown(file)} imports ${specifier}, which is neither a file nor an installed package (name an alias in settings.importAliases)`
|
|
3464
|
+
);
|
|
3031
3465
|
found.unnamed.push(specifier);
|
|
3032
3466
|
} else if (linkedPackage(specifier, file) !== null) {
|
|
3033
|
-
|
|
3467
|
+
loading(
|
|
3468
|
+
`${shown(file)} imports ${specifier}, a package of the repository linked into node_modules`
|
|
3469
|
+
);
|
|
3034
3470
|
found.computed = true;
|
|
3035
3471
|
}
|
|
3036
3472
|
}
|
|
@@ -3038,10 +3474,14 @@ function fileEdges(file, context) {
|
|
|
3038
3474
|
if (location !== null) {
|
|
3039
3475
|
const reads = LOCATION_READS.exec(code);
|
|
3040
3476
|
const reader = [...specifiers].find(
|
|
3041
|
-
(specifier) => !specifier.startsWith(".") && !
|
|
3477
|
+
(specifier) => !specifier.startsWith(".") && !path7.isAbsolute(specifier) && !specifier.startsWith("file:") && !isProjectImport(specifier, aliases) && !LOCATION_SAFE.has(bare(specifier))
|
|
3042
3478
|
);
|
|
3043
3479
|
if (reads !== null) {
|
|
3044
3480
|
found.unknown ??= `${shown(file)} uses ${location[0]} and ${reads[0].replace(/\(\s*$/, "")}, which can reach files that no import names`;
|
|
3481
|
+
const loads = LOCATION_LOADS.exec(code);
|
|
3482
|
+
if (loads !== null) {
|
|
3483
|
+
found.loading ??= `${shown(file)} uses ${location[0]} and ${loads[0].replace(/\(\s*$/, "")}, which can reach files that no import names`;
|
|
3484
|
+
}
|
|
3045
3485
|
} else if (reader !== void 0) {
|
|
3046
3486
|
found.unknown ??= `${shown(file)} uses ${location[0]} and imports ${reader}, which can read files by it that no import names`;
|
|
3047
3487
|
}
|
|
@@ -3060,7 +3500,9 @@ function fileEdges(file, context) {
|
|
|
3060
3500
|
if (left > 0) {
|
|
3061
3501
|
typeLines.set(key, left - 1);
|
|
3062
3502
|
} else if (text.startsWith(".") && !specifiers.has(text)) {
|
|
3063
|
-
found.loads.push(...resolveFiles(
|
|
3503
|
+
found.loads.push(...resolveFiles(path7.resolve(path7.dirname(file), text)).map(realpath));
|
|
3504
|
+
} else if (PATH_FROM_TOP.test(text) && isFile2(path7.resolve(dir, text))) {
|
|
3505
|
+
found.loads.push(realpath(path7.resolve(dir, text)));
|
|
3064
3506
|
}
|
|
3065
3507
|
}
|
|
3066
3508
|
return found;
|
|
@@ -3072,25 +3514,25 @@ function inputsOf(manifest, input) {
|
|
|
3072
3514
|
const dir = realpath(manifest.dir);
|
|
3073
3515
|
const aliases = manifest.settings.importAliases;
|
|
3074
3516
|
const placed = [...input.tests, ...Object.values(input.covering).flat()];
|
|
3075
|
-
const absolute = placed.find((file) =>
|
|
3517
|
+
const absolute = placed.find((file) => path7.isAbsolute(file) || file.startsWith(".."));
|
|
3076
3518
|
if (absolute !== void 0) {
|
|
3077
3519
|
throw new AffectedError(`the report names a spec file outside of the subproject: ${absolute}`);
|
|
3078
3520
|
}
|
|
3079
3521
|
const keyOf2 = (file) => {
|
|
3080
|
-
const relative =
|
|
3081
|
-
if (relative.startsWith("..") ||
|
|
3522
|
+
const relative = path7.relative(input.root, realpath(file));
|
|
3523
|
+
if (relative.startsWith("..") || path7.isAbsolute(relative)) {
|
|
3082
3524
|
throw new AffectedError(`${file} is outside of the files that are compared`);
|
|
3083
3525
|
}
|
|
3084
3526
|
return toPosix2(relative);
|
|
3085
3527
|
};
|
|
3086
|
-
const shown = (file) => toPosix2(
|
|
3528
|
+
const shown = (file) => toPosix2(path7.relative(dir, file));
|
|
3087
3529
|
const plain2 = /* @__PURE__ */ new Map();
|
|
3088
3530
|
const configTexts = [];
|
|
3089
3531
|
const runnerSources = [];
|
|
3090
3532
|
const reached = new Set(input.configs.files);
|
|
3091
3533
|
const pending = [...input.configs.files];
|
|
3092
3534
|
for (let file = pending.shift(); file !== void 0; file = pending.shift()) {
|
|
3093
|
-
const text =
|
|
3535
|
+
const text = fs7.readFileSync(file, "utf8");
|
|
3094
3536
|
configTexts.push(text);
|
|
3095
3537
|
runnerSources.push({ file, text });
|
|
3096
3538
|
for (const next of fileEdges(file, { dir, aliases, cache: plain2 }).loads) {
|
|
@@ -3108,7 +3550,7 @@ function inputsOf(manifest, input) {
|
|
|
3108
3550
|
// Not what lies outside of the compared files: this package, imported by path, is installed.
|
|
3109
3551
|
runner: [
|
|
3110
3552
|
...runnerSources.filter(({ file }) => {
|
|
3111
|
-
const relative =
|
|
3553
|
+
const relative = path7.relative(input.root, realpath(file));
|
|
3112
3554
|
return !relative.startsWith("..") && toPosix2(relative) in input.files;
|
|
3113
3555
|
}),
|
|
3114
3556
|
...jestInPackage(dir, input.configs)
|
|
@@ -3121,14 +3563,21 @@ function inputsOf(manifest, input) {
|
|
|
3121
3563
|
}
|
|
3122
3564
|
const edges = /* @__PURE__ */ new Map();
|
|
3123
3565
|
const edgesOf = (file) => fileEdges(file, { dir, aliases, cache: edges, types });
|
|
3566
|
+
const blind = /* @__PURE__ */ Object.create(null);
|
|
3124
3567
|
const closure = (start, strict) => {
|
|
3125
3568
|
const seen = /* @__PURE__ */ new Set([start]);
|
|
3126
3569
|
const queue = [start];
|
|
3127
3570
|
for (let file = queue.shift(); file !== void 0; file = queue.shift()) {
|
|
3128
3571
|
const found = edgesOf(file);
|
|
3129
|
-
if (strict && found.unknown !== null) {
|
|
3572
|
+
if (strict === "every" && found.unknown !== null) {
|
|
3130
3573
|
throw new AffectedError(found.unknown);
|
|
3131
3574
|
}
|
|
3575
|
+
if (strict === "gate" && found.unknown !== null) {
|
|
3576
|
+
if (found.loading !== null) {
|
|
3577
|
+
throw new AffectedError(found.loading);
|
|
3578
|
+
}
|
|
3579
|
+
blind[keyOf2(file)] = found.unknown;
|
|
3580
|
+
}
|
|
3132
3581
|
for (const next of found.loads) {
|
|
3133
3582
|
if (!seen.has(next)) {
|
|
3134
3583
|
seen.add(next);
|
|
@@ -3139,8 +3588,8 @@ function inputsOf(manifest, input) {
|
|
|
3139
3588
|
return [...seen];
|
|
3140
3589
|
};
|
|
3141
3590
|
const isCompared = (file) => {
|
|
3142
|
-
const relative =
|
|
3143
|
-
return !relative.startsWith("..") && !
|
|
3591
|
+
const relative = path7.relative(input.root, realpath(file));
|
|
3592
|
+
return !relative.startsWith("..") && !path7.isAbsolute(relative) && toPosix2(relative) in input.files;
|
|
3144
3593
|
};
|
|
3145
3594
|
const keysOf = (files) => files.map((file) => {
|
|
3146
3595
|
const key = keyOf2(file);
|
|
@@ -3159,10 +3608,10 @@ function inputsOf(manifest, input) {
|
|
|
3159
3608
|
if (!SCRIPT.test(loaded)) {
|
|
3160
3609
|
continue;
|
|
3161
3610
|
}
|
|
3162
|
-
for (const match of
|
|
3611
|
+
for (const match of fs7.readFileSync(loaded, "utf8").matchAll(QUOTED)) {
|
|
3163
3612
|
const text = match[2].replace(/^<rootDir>\//, "");
|
|
3164
|
-
const here = resolveFiles(
|
|
3165
|
-
const candidates = here.length > 0 ? here : resolveFiles(
|
|
3613
|
+
const here = resolveFiles(path7.resolve(path7.dirname(loaded), text), false, false);
|
|
3614
|
+
const candidates = here.length > 0 ? here : resolveFiles(path7.resolve(dir, text), false, false);
|
|
3166
3615
|
for (const found of candidates) {
|
|
3167
3616
|
if (text.includes("*") || !isCompared(found)) {
|
|
3168
3617
|
continue;
|
|
@@ -3177,7 +3626,7 @@ function inputsOf(manifest, input) {
|
|
|
3177
3626
|
}
|
|
3178
3627
|
}
|
|
3179
3628
|
const gatePaths = new Map(
|
|
3180
|
-
manifest.gates.map((gate) => [realpath(
|
|
3629
|
+
manifest.gates.map((gate) => [realpath(path7.join(dir, gate.path)), gate.path])
|
|
3181
3630
|
);
|
|
3182
3631
|
const tests = [
|
|
3183
3632
|
.../* @__PURE__ */ new Set([
|
|
@@ -3188,13 +3637,13 @@ function inputsOf(manifest, input) {
|
|
|
3188
3637
|
];
|
|
3189
3638
|
const runAsSuch = new Set(
|
|
3190
3639
|
[...tests, ...manifest.gates.map((gate) => gate.path)].map(
|
|
3191
|
-
(file) => realpath(
|
|
3640
|
+
(file) => realpath(path7.join(dir, file))
|
|
3192
3641
|
)
|
|
3193
3642
|
);
|
|
3194
|
-
const subprojectKey = toPosix2(
|
|
3643
|
+
const subprojectKey = toPosix2(path7.relative(input.root, dir));
|
|
3195
3644
|
const isConfig = (file) => input.configs.files.includes(file) || kindOf(keyOf2(file), subprojectKey) === "config";
|
|
3196
3645
|
for (const file of named.filter((one) => !isConfig(one) && !runAsSuch.has(one))) {
|
|
3197
|
-
for (const loaded of closure(file,
|
|
3646
|
+
for (const loaded of closure(file, "every")) {
|
|
3198
3647
|
const gate = gatePaths.get(loaded);
|
|
3199
3648
|
if (gate !== void 0) {
|
|
3200
3649
|
throw new AffectedError(
|
|
@@ -3204,16 +3653,16 @@ function inputsOf(manifest, input) {
|
|
|
3204
3653
|
config.add(loaded);
|
|
3205
3654
|
}
|
|
3206
3655
|
}
|
|
3207
|
-
const fromSubproject = (file) =>
|
|
3656
|
+
const fromSubproject = (file) => path7.join(dir, file);
|
|
3208
3657
|
const closures = /* @__PURE__ */ Object.create(null);
|
|
3209
3658
|
for (const file of [...manifest.gates.map((gate) => gate.path), ...tests]) {
|
|
3210
3659
|
const absolute2 = fromSubproject(file);
|
|
3211
|
-
if (!
|
|
3660
|
+
if (!fs7.existsSync(absolute2)) {
|
|
3212
3661
|
continue;
|
|
3213
3662
|
}
|
|
3214
|
-
closures[keysOf([absolute2])[0]] = keysOf(closure(realpath(absolute2),
|
|
3663
|
+
closures[keysOf([absolute2])[0]] = keysOf(closure(realpath(absolute2), "gate"));
|
|
3215
3664
|
}
|
|
3216
|
-
const keyed = (files) => files.map((file) => toPosix2(
|
|
3665
|
+
const keyed = (files) => files.map((file) => toPosix2(path7.relative(input.root, path7.join(dir, file))));
|
|
3217
3666
|
const dependencies = gateDependencies({
|
|
3218
3667
|
gates: manifest.gates.map((gate) => ({
|
|
3219
3668
|
key: keyed([gate.path])[0],
|
|
@@ -3223,9 +3672,10 @@ function inputsOf(manifest, input) {
|
|
|
3223
3672
|
covering: Object.fromEntries(
|
|
3224
3673
|
Object.entries(input.covering).map(([gate, specs]) => [keyed([gate])[0], keyed(specs)])
|
|
3225
3674
|
),
|
|
3226
|
-
closures
|
|
3675
|
+
closures,
|
|
3676
|
+
blind
|
|
3227
3677
|
});
|
|
3228
|
-
const subproject = toPosix2(
|
|
3678
|
+
const subproject = toPosix2(path7.relative(input.root, dir));
|
|
3229
3679
|
const prefix = subproject === "" ? "" : `${subproject}/`;
|
|
3230
3680
|
const gates = /* @__PURE__ */ Object.create(null);
|
|
3231
3681
|
for (const gate of manifest.gates) {
|
|
@@ -3236,12 +3686,13 @@ function inputsOf(manifest, input) {
|
|
|
3236
3686
|
specs: found.specs.map(
|
|
3237
3687
|
(spec) => spec.startsWith(prefix) ? spec.slice(prefix.length) : spec
|
|
3238
3688
|
),
|
|
3239
|
-
files: found.files
|
|
3689
|
+
files: found.files,
|
|
3690
|
+
...found.blind === void 0 ? {} : { blind: found.blind }
|
|
3240
3691
|
};
|
|
3241
3692
|
}
|
|
3242
3693
|
const packageMocks = Object.keys(input.files).filter(
|
|
3243
3694
|
(key) => isNestedMock(key, subproject) && mockNames(key).some(
|
|
3244
|
-
(name) => builtinModules2.includes(name) || installed(name,
|
|
3695
|
+
(name) => builtinModules2.includes(name) || installed(name, path7.join(input.root, key))
|
|
3245
3696
|
)
|
|
3246
3697
|
);
|
|
3247
3698
|
return {
|
|
@@ -3297,7 +3748,7 @@ function scopeOf(manifest, configs, listed2) {
|
|
|
3297
3748
|
const cache = /* @__PURE__ */ new Map();
|
|
3298
3749
|
const context = { dir, aliases: manifest.settings.importAliases, cache };
|
|
3299
3750
|
const runner = configs.files.slice(1);
|
|
3300
|
-
const texts = runner.map((file) => withoutComments(
|
|
3751
|
+
const texts = runner.map((file) => withoutComments(fs7.readFileSync(file, "utf8")));
|
|
3301
3752
|
if (runner.length === 0 || !texts.every((text) => OWN.test(text))) {
|
|
3302
3753
|
return { every: NOT_THE_HELPER2 };
|
|
3303
3754
|
}
|
|
@@ -3312,21 +3763,21 @@ function scopeOf(manifest, configs, listed2) {
|
|
|
3312
3763
|
fileEdges(file, context).loads.forEach((loaded) => parts.add(loaded));
|
|
3313
3764
|
}
|
|
3314
3765
|
}
|
|
3315
|
-
if ([...parts].some((file) => PROJECTS.test(withoutComments(
|
|
3766
|
+
if ([...parts].some((file) => PROJECTS.test(withoutComments(fs7.readFileSync(file, "utf8"))))) {
|
|
3316
3767
|
return {
|
|
3317
3768
|
every: "the runner config, or a file it is put together from, names projects or a workspace"
|
|
3318
3769
|
};
|
|
3319
3770
|
}
|
|
3320
|
-
const named =
|
|
3321
|
-
const mocks =
|
|
3771
|
+
const named = fs7.readdirSync(dir).filter((name) => READ_BY_NAME.some((pattern) => pattern.test(name))).map((name) => path7.join(dir, name));
|
|
3772
|
+
const mocks = path7.join(dir, "__mocks__");
|
|
3322
3773
|
const seeds = [
|
|
3323
3774
|
...configs.files,
|
|
3324
3775
|
...named,
|
|
3325
|
-
...manifest.gates.flatMap((gate) => [gate.path, ...gate.existingSpecs]).map((file) =>
|
|
3326
|
-
...listed2.filter((file) => file.startsWith(`${mocks}${
|
|
3327
|
-
].filter((file) =>
|
|
3776
|
+
...manifest.gates.flatMap((gate) => [gate.path, ...gate.existingSpecs]).map((file) => path7.join(dir, file)),
|
|
3777
|
+
...listed2.filter((file) => file.startsWith(`${mocks}${path7.sep}`))
|
|
3778
|
+
].filter((file) => fs7.existsSync(file)).map(realpath);
|
|
3328
3779
|
const naming = new Set(
|
|
3329
|
-
[...configs.files, ...named].filter((file) =>
|
|
3780
|
+
[...configs.files, ...named].filter((file) => fs7.existsSync(file)).map(realpath)
|
|
3330
3781
|
);
|
|
3331
3782
|
const seen = new Set(seeds);
|
|
3332
3783
|
const queue = [...seeds];
|
|
@@ -3338,7 +3789,7 @@ function scopeOf(manifest, configs, listed2) {
|
|
|
3338
3789
|
}
|
|
3339
3790
|
};
|
|
3340
3791
|
for (let file = queue.shift(); file !== void 0; file = queue.shift()) {
|
|
3341
|
-
if (
|
|
3792
|
+
if (fs7.statSync(file).isDirectory()) {
|
|
3342
3793
|
continue;
|
|
3343
3794
|
}
|
|
3344
3795
|
const edges = fileEdges(file, context);
|
|
@@ -3349,11 +3800,11 @@ function scopeOf(manifest, configs, listed2) {
|
|
|
3349
3800
|
for (const specifier of edges.unnamed) {
|
|
3350
3801
|
endingAs(specifier, listed2).forEach(add);
|
|
3351
3802
|
}
|
|
3352
|
-
if (naming.has(file) || SCRIPT.test(file) &&
|
|
3803
|
+
if (naming.has(file) || SCRIPT.test(file) && path7.dirname(file) === dir) {
|
|
3353
3804
|
for (const named2 of namedIn(file)) {
|
|
3354
3805
|
const text = named2.replace(/^<rootDir>\//, "");
|
|
3355
|
-
const here = resolveFiles(
|
|
3356
|
-
const found = here.length > 0 ? here : resolveFiles(
|
|
3806
|
+
const here = resolveFiles(path7.resolve(path7.dirname(file), text), false);
|
|
3807
|
+
const found = here.length > 0 ? here : resolveFiles(path7.resolve(dir, text), false);
|
|
3357
3808
|
for (const one of text.includes("*") ? [] : found) {
|
|
3358
3809
|
if (!SCRIPT.test(one)) {
|
|
3359
3810
|
naming.add(realpath(one));
|
|
@@ -3377,7 +3828,7 @@ function endingAs(specifier, files) {
|
|
|
3377
3828
|
});
|
|
3378
3829
|
}
|
|
3379
3830
|
function namedIn(file) {
|
|
3380
|
-
const text =
|
|
3831
|
+
const text = fs7.readFileSync(file, "utf8");
|
|
3381
3832
|
if (SCRIPT.test(file)) {
|
|
3382
3833
|
return [...text.matchAll(QUOTED)].map((match) => match[2]);
|
|
3383
3834
|
}
|
|
@@ -3531,15 +3982,15 @@ function mayStartPattern(before) {
|
|
|
3531
3982
|
return before.last === "" || !")]".includes(before.last);
|
|
3532
3983
|
}
|
|
3533
3984
|
function jestInPackage(dir, configs) {
|
|
3534
|
-
const file =
|
|
3535
|
-
return configs.runner === "jest" && configs.files.length === 1 &&
|
|
3985
|
+
const file = path7.join(dir, "package.json");
|
|
3986
|
+
return configs.runner === "jest" && configs.files.length === 1 && fs7.existsSync(file) ? [{ file, text: fs7.readFileSync(file, "utf8"), mapperOnly: true }] : [];
|
|
3536
3987
|
}
|
|
3537
3988
|
var STRING_LITERAL = String.raw`'(?:[^'\\\n]|\\.)*'|"(?:[^"\\\n]|\\.)*"`;
|
|
3538
3989
|
function stringValue(text) {
|
|
3539
3990
|
const match = new RegExp(`^\\s*(${STRING_LITERAL})\\s*$`).exec(text);
|
|
3540
3991
|
return match === null ? null : match[1].slice(1, -1).replace(/\\(.)/g, "$1");
|
|
3541
3992
|
}
|
|
3542
|
-
function
|
|
3993
|
+
function partsOf2(text, at) {
|
|
3543
3994
|
const parts = [];
|
|
3544
3995
|
let depth = 0;
|
|
3545
3996
|
let start = at + 1;
|
|
@@ -3576,7 +4027,7 @@ function property(part) {
|
|
|
3576
4027
|
}
|
|
3577
4028
|
function aliasPath(expression, file, text, depth = 0) {
|
|
3578
4029
|
const value = expression.trim();
|
|
3579
|
-
const here =
|
|
4030
|
+
const here = path7.dirname(file);
|
|
3580
4031
|
if (value === "__dirname" || value === "import.meta.dirname" || /^(?:path\.)?dirname\(\s*(?:url\.)?fileURLToPath\(\s*import\.meta\.url\s*\)\s*\)$/.test(value)) {
|
|
3581
4032
|
return here;
|
|
3582
4033
|
}
|
|
@@ -3584,18 +4035,18 @@ function aliasPath(expression, file, text, depth = 0) {
|
|
|
3584
4035
|
`^(?:(?:url\\.)?fileURLToPath\\(\\s*new URL\\(\\s*(${STRING_LITERAL})\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)|new URL\\(\\s*(${STRING_LITERAL})\\s*,\\s*import\\.meta\\.url\\s*\\)\\.pathname)$`
|
|
3585
4036
|
).exec(value);
|
|
3586
4037
|
if (url !== null) {
|
|
3587
|
-
return
|
|
4038
|
+
return path7.resolve(here, stringValue(url[1] ?? url[2]));
|
|
3588
4039
|
}
|
|
3589
4040
|
const call = /^(?:path\.)?(?:resolve|join)\s*\(/.exec(value);
|
|
3590
4041
|
if (call !== null) {
|
|
3591
|
-
const found =
|
|
4042
|
+
const found = partsOf2(value, call[0].length - 1);
|
|
3592
4043
|
if (found === null || value.slice(found.end + 1).trim() !== "" || found.parts.length === 0) {
|
|
3593
4044
|
return null;
|
|
3594
4045
|
}
|
|
3595
4046
|
const [first, ...rest] = found.parts;
|
|
3596
4047
|
const base = aliasPath(first, file, text, depth + 1);
|
|
3597
4048
|
const parts = rest.map(stringValue);
|
|
3598
|
-
return base === null || parts.some((part) => part === null) ? null :
|
|
4049
|
+
return base === null || parts.some((part) => part === null) ? null : path7.resolve(base, ...parts);
|
|
3599
4050
|
}
|
|
3600
4051
|
if (/^[A-Za-z_$][\w$]*$/.test(value) && depth < 4) {
|
|
3601
4052
|
const declarations = text.match(
|
|
@@ -3611,7 +4062,7 @@ function aliasPath(expression, file, text, depth = 0) {
|
|
|
3611
4062
|
var PACKAGE_NAME = /^(?:@[\w-][\w.-]*\/)?[\w-][\w.-]*$/;
|
|
3612
4063
|
function isDirectory(file) {
|
|
3613
4064
|
try {
|
|
3614
|
-
return
|
|
4065
|
+
return fs7.statSync(file).isDirectory();
|
|
3615
4066
|
} catch {
|
|
3616
4067
|
return false;
|
|
3617
4068
|
}
|
|
@@ -3637,8 +4088,8 @@ function unexplainedMaps(input) {
|
|
|
3637
4088
|
const { dir, aliases } = input;
|
|
3638
4089
|
const reasons = [];
|
|
3639
4090
|
const mapped = [];
|
|
3640
|
-
const shown = (file) => toPosix2(
|
|
3641
|
-
const under = (base, rest) => rest === "" ? `${base}/` :
|
|
4091
|
+
const shown = (file) => toPosix2(path7.relative(dir, file));
|
|
4092
|
+
const under = (base, rest) => rest === "" ? `${base}/` : path7.join(base, rest);
|
|
3642
4093
|
const named = (prefix, target) => {
|
|
3643
4094
|
const alias = aliases.find((candidate) => prefix.startsWith(candidate.prefix));
|
|
3644
4095
|
if (alias === void 0) {
|
|
@@ -3646,10 +4097,10 @@ function unexplainedMaps(input) {
|
|
|
3646
4097
|
}
|
|
3647
4098
|
const full = alias.target + prefix.slice(alias.prefix.length);
|
|
3648
4099
|
const slash = full === "" || full.endsWith("/");
|
|
3649
|
-
return
|
|
4100
|
+
return path7.resolve(dir, full) === path7.resolve(target) && slash === target.endsWith("/");
|
|
3650
4101
|
};
|
|
3651
4102
|
const advice = (prefix, target) => {
|
|
3652
|
-
const relative = toPosix2(
|
|
4103
|
+
const relative = toPosix2(path7.relative(dir, target));
|
|
3653
4104
|
const shownTarget = relative === "" ? "./" : `${relative}/`;
|
|
3654
4105
|
const shorter = aliases.find((alias) => prefix.startsWith(alias.prefix));
|
|
3655
4106
|
const before = shorter === void 0 ? "" : shorter.prefix === prefix ? `, in place of the alias of ${prefix}` : `, before the alias of ${shorter.prefix}`;
|
|
@@ -3662,7 +4113,7 @@ function unexplainedMaps(input) {
|
|
|
3662
4113
|
let prefix = null;
|
|
3663
4114
|
let to = null;
|
|
3664
4115
|
if (list) {
|
|
3665
|
-
const object = /^\s*\{/.test(part) ?
|
|
4116
|
+
const object = /^\s*\{/.test(part) ? partsOf2(part, part.indexOf("{")) : null;
|
|
3666
4117
|
const fields = Object.fromEntries(
|
|
3667
4118
|
(object?.parts ?? []).map(property).map((one) => [one?.key ?? "", one?.value ?? ""])
|
|
3668
4119
|
);
|
|
@@ -3714,7 +4165,7 @@ function unexplainedMaps(input) {
|
|
|
3714
4165
|
const word = match[0];
|
|
3715
4166
|
const opening = /^['"]?\s*:\s*/.exec(text.slice(match.index + word.length));
|
|
3716
4167
|
const at = opening === null ? -1 : match.index + word.length + opening[0].length;
|
|
3717
|
-
const found = at === -1 ? null :
|
|
4168
|
+
const found = at === -1 ? null : partsOf2(text, at);
|
|
3718
4169
|
const unreadable = `${shown(file)} names ${word} in a way that cannot be read as a list of aliases`;
|
|
3719
4170
|
if (found === null || text[at] !== "{" && !(word === "alias" && text[at] === "[")) {
|
|
3720
4171
|
reasons.push(unreadable);
|
|
@@ -3744,7 +4195,7 @@ function unexplainedMaps(input) {
|
|
|
3744
4195
|
}
|
|
3745
4196
|
const prefix = mapperPrefix(entry.key);
|
|
3746
4197
|
const target = value === null ? null : /^<rootDir>\/(.*)\$1$/.exec(value);
|
|
3747
|
-
const to = target === null ? null : under(
|
|
4198
|
+
const to = target === null ? null : under(path7.dirname(file), target[1]);
|
|
3748
4199
|
if (prefix === null || to === null) {
|
|
3749
4200
|
reasons.push(missing(file, entry.key, word));
|
|
3750
4201
|
} else if (!named(prefix, to)) {
|
|
@@ -3755,19 +4206,19 @@ function unexplainedMaps(input) {
|
|
|
3755
4206
|
}
|
|
3756
4207
|
}
|
|
3757
4208
|
}
|
|
3758
|
-
const tsconfigs = input.compiler.filter(({ file }) => /^[jt]sconfig/.test(
|
|
4209
|
+
const tsconfigs = input.compiler.filter(({ file }) => /^[jt]sconfig/.test(path7.basename(file)));
|
|
3759
4210
|
const withBase = tsconfigs.filter(({ text }) => text !== null && /"baseUrl"\s*:/.test(text));
|
|
3760
4211
|
for (const { file, text } of input.compiler) {
|
|
3761
4212
|
if (text === null) {
|
|
3762
4213
|
continue;
|
|
3763
4214
|
}
|
|
3764
4215
|
const plain2 = withoutComments(text);
|
|
3765
|
-
const name =
|
|
4216
|
+
const name = path7.basename(file);
|
|
3766
4217
|
const swc = name === ".swcrc";
|
|
3767
4218
|
if (!/^[jt]sconfig/.test(name) && !swc) {
|
|
3768
4219
|
const babel = /"babel"\s*:\s*/.exec(plain2);
|
|
3769
4220
|
const own2 = name !== "package.json" ? plain2 : babel === null ? "" : (() => {
|
|
3770
|
-
const found2 =
|
|
4221
|
+
const found2 = partsOf2(plain2, babel.index + babel[0].length);
|
|
3771
4222
|
return found2 === null ? plain2 : found2.parts.join(",");
|
|
3772
4223
|
})();
|
|
3773
4224
|
const maps = /module-resolver|\balias\b/.exec(own2);
|
|
@@ -3781,7 +4232,7 @@ function unexplainedMaps(input) {
|
|
|
3781
4232
|
continue;
|
|
3782
4233
|
}
|
|
3783
4234
|
const at = paths.index + paths[0].length;
|
|
3784
|
-
const found = plain2[at] === "{" ?
|
|
4235
|
+
const found = plain2[at] === "{" ? partsOf2(plain2, at) : null;
|
|
3785
4236
|
if (found === null) {
|
|
3786
4237
|
reasons.push(`${shown(file)} names paths in a way that cannot be read as a list of aliases`);
|
|
3787
4238
|
continue;
|
|
@@ -3791,7 +4242,7 @@ function unexplainedMaps(input) {
|
|
|
3791
4242
|
reasons.push(`${shown(file)} names paths, and baseUrl is set in another file`);
|
|
3792
4243
|
continue;
|
|
3793
4244
|
}
|
|
3794
|
-
const base =
|
|
4245
|
+
const base = path7.resolve(path7.dirname(file), own?.[1] ?? ".");
|
|
3795
4246
|
for (const part of found.parts) {
|
|
3796
4247
|
const entry = property(part);
|
|
3797
4248
|
if (entry === null) {
|
|
@@ -3800,10 +4251,10 @@ function unexplainedMaps(input) {
|
|
|
3800
4251
|
);
|
|
3801
4252
|
continue;
|
|
3802
4253
|
}
|
|
3803
|
-
const list = entry.value.startsWith("[") ?
|
|
4254
|
+
const list = entry.value.startsWith("[") ? partsOf2(entry.value, 0) : null;
|
|
3804
4255
|
const target = list?.parts.length === 1 ? stringValue(list.parts[0]) : null;
|
|
3805
4256
|
const prefix = entry.key.endsWith("*") ? entry.key.slice(0, -1) : null;
|
|
3806
|
-
if (prefix === null && target !== null && !target.includes("*") &&
|
|
4257
|
+
if (prefix === null && target !== null && !target.includes("*") && path7.resolve(base, target).split(path7.sep).includes("node_modules")) {
|
|
3807
4258
|
continue;
|
|
3808
4259
|
}
|
|
3809
4260
|
const directory = target?.endsWith("*") ? target.slice(0, -1) : null;
|
|
@@ -3821,26 +4272,69 @@ function unexplainedMaps(input) {
|
|
|
3821
4272
|
}
|
|
3822
4273
|
|
|
3823
4274
|
// src/commands/mutation.ts
|
|
3824
|
-
import { spawnSync as
|
|
4275
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4276
|
+
import fs10 from "fs";
|
|
4277
|
+
import path10 from "path";
|
|
4278
|
+
import { pathToFileURL } from "url";
|
|
4279
|
+
|
|
4280
|
+
// src/main-worktree.ts
|
|
4281
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3825
4282
|
import fs8 from "fs";
|
|
3826
4283
|
import path8 from "path";
|
|
3827
|
-
|
|
4284
|
+
function git2(dir, args) {
|
|
4285
|
+
const result = spawnSync3("git", args, {
|
|
4286
|
+
cwd: dir,
|
|
4287
|
+
env: gitEnvironment(process.env),
|
|
4288
|
+
encoding: "utf8"
|
|
4289
|
+
});
|
|
4290
|
+
return result.error || result.status !== 0 ? null : result.stdout.trim().split("\n");
|
|
4291
|
+
}
|
|
4292
|
+
function linkedWorktreeOf(dir) {
|
|
4293
|
+
const found = git2(dir, [
|
|
4294
|
+
"rev-parse",
|
|
4295
|
+
"--path-format=absolute",
|
|
4296
|
+
"--show-toplevel",
|
|
4297
|
+
"--git-dir",
|
|
4298
|
+
"--git-common-dir"
|
|
4299
|
+
]);
|
|
4300
|
+
if (found === null || found.length !== 3) {
|
|
4301
|
+
return null;
|
|
4302
|
+
}
|
|
4303
|
+
const [top, gitDir, common] = found;
|
|
4304
|
+
if (gitDir === common || path8.basename(common) !== ".git") {
|
|
4305
|
+
return null;
|
|
4306
|
+
}
|
|
4307
|
+
try {
|
|
4308
|
+
const main2 = fs8.realpathSync(path8.dirname(common));
|
|
4309
|
+
const real = fs8.realpathSync(top);
|
|
4310
|
+
return main2 === real ? null : { top: real, main: main2 };
|
|
4311
|
+
} catch {
|
|
4312
|
+
return null;
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
function inMainWorktree(linked, file) {
|
|
4316
|
+
const relative = path8.relative(linked.top, file);
|
|
4317
|
+
if (relative === "" || relative.startsWith("..") || path8.isAbsolute(relative)) {
|
|
4318
|
+
return null;
|
|
4319
|
+
}
|
|
4320
|
+
return path8.join(linked.main, relative);
|
|
4321
|
+
}
|
|
3828
4322
|
|
|
3829
4323
|
// src/commands/mutation-result.ts
|
|
3830
|
-
import
|
|
3831
|
-
import
|
|
4324
|
+
import fs9 from "fs";
|
|
4325
|
+
import path9 from "path";
|
|
3832
4326
|
function reportPathOf(manifest, override) {
|
|
3833
|
-
return
|
|
4327
|
+
return path9.resolve(manifest.dir, override ?? manifest.settings.strykerReportFile);
|
|
3834
4328
|
}
|
|
3835
|
-
function runMutationResult(dir, reportOverride, io2, only = [], seen = () => void 0) {
|
|
4329
|
+
function runMutationResult(dir, reportOverride, io2, only = [], seen = () => void 0, retried = null) {
|
|
3836
4330
|
const manifest = readManifest(dir);
|
|
3837
4331
|
const gates = only.length === 0 ? manifest.gates : manifest.gates.filter((gate) => only.includes(gate.path));
|
|
3838
4332
|
const caveat = only.length === 0 ? "" : gateRunCaveat(gates.length, manifest.gates.length);
|
|
3839
4333
|
const reportPath = reportPathOf(manifest, reportOverride);
|
|
3840
|
-
const shown =
|
|
4334
|
+
const shown = path9.relative(manifest.dir, reportPath) || reportPath;
|
|
3841
4335
|
let report;
|
|
3842
4336
|
try {
|
|
3843
|
-
report = JSON.parse(
|
|
4337
|
+
report = JSON.parse(fs9.readFileSync(reportPath, "utf8"));
|
|
3844
4338
|
} catch (error) {
|
|
3845
4339
|
io2.err(
|
|
3846
4340
|
`test-gates mutation: cannot read the Stryker JSON report ${shown} (${error.message}). Did Stryker finish, with the "json" reporter writing to this path?`
|
|
@@ -3857,7 +4351,7 @@ function runMutationResult(dir, reportOverride, io2, only = [], seen = () => voi
|
|
|
3857
4351
|
maxTimeouts: manifest.settings.maxTimeouts,
|
|
3858
4352
|
readSource: (gatePath) => {
|
|
3859
4353
|
try {
|
|
3860
|
-
return
|
|
4354
|
+
return fs9.readFileSync(path9.join(manifest.dir, gatePath), "utf8");
|
|
3861
4355
|
} catch {
|
|
3862
4356
|
return void 0;
|
|
3863
4357
|
}
|
|
@@ -3905,8 +4399,34 @@ function runMutationResult(dir, reportOverride, io2, only = [], seen = () => voi
|
|
|
3905
4399
|
io2.err(` - ${formatSurvivor(mutant)}`);
|
|
3906
4400
|
}
|
|
3907
4401
|
io2.err("");
|
|
3908
|
-
|
|
3909
|
-
|
|
4402
|
+
if (retried === null || retried === "failed") {
|
|
4403
|
+
io2.err("A timeout counts as detected, but on a loaded machine a mutant that would survive");
|
|
4404
|
+
if (retried === null) {
|
|
4405
|
+
io2.err(
|
|
4406
|
+
"can time out instead. `test-gates mutation` runs such timeouts again one at a time"
|
|
4407
|
+
);
|
|
4408
|
+
io2.err("(--concurrency 1) before it fails; these were not run again. Only for a");
|
|
4409
|
+
} else {
|
|
4410
|
+
io2.err(
|
|
4411
|
+
"can time out instead. They were run again one at a time (--concurrency 1), but that"
|
|
4412
|
+
);
|
|
4413
|
+
io2.err("run gave no result (see above), so these are those of the first run. Only for a");
|
|
4414
|
+
}
|
|
4415
|
+
} else if (retried.missing === 0) {
|
|
4416
|
+
io2.err("They timed out again when they were run one at a time (--concurrency 1), so they");
|
|
4417
|
+
io2.err("most likely never finish. Look at each mutant first. Only for a");
|
|
4418
|
+
} else {
|
|
4419
|
+
const again = retried.statuses["Timeout"] ?? 0;
|
|
4420
|
+
if (again > 0) {
|
|
4421
|
+
io2.err(
|
|
4422
|
+
`${again} of them timed out again when they were run one at a time (--concurrency 1).`
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
io2.err(
|
|
4426
|
+
`${retried.missing} of them were not in the report of the run again (--concurrency 1),`
|
|
4427
|
+
);
|
|
4428
|
+
io2.err("so they keep the timeout of the first run. Look at each mutant first. Only for a");
|
|
4429
|
+
}
|
|
3910
4430
|
io2.err("mutant that can never finish (a loop that no longer ends), add it to");
|
|
3911
4431
|
io2.err('"expectedTimeouts" of the gate in test-gates.json and fill in "reason"');
|
|
3912
4432
|
io2.err("(settings.mutation.maxTimeouts raises the limit for the whole project):");
|
|
@@ -3925,16 +4445,16 @@ function runMutationResult(dir, reportOverride, io2, only = [], seen = () => voi
|
|
|
3925
4445
|
// src/commands/mutation.ts
|
|
3926
4446
|
var CONFIG_MARK = "\0test-gates-stryker-config\0";
|
|
3927
4447
|
function loadStrykerConfig(manifest, config) {
|
|
3928
|
-
const file =
|
|
4448
|
+
const file = path10.resolve(manifest.dir, config);
|
|
3929
4449
|
if (file.endsWith(".json")) {
|
|
3930
4450
|
try {
|
|
3931
|
-
return JSON.parse(
|
|
4451
|
+
return JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
3932
4452
|
} catch {
|
|
3933
4453
|
return void 0;
|
|
3934
4454
|
}
|
|
3935
4455
|
}
|
|
3936
|
-
const script = `const loaded = await import(${JSON.stringify(pathToFileURL(file).href)});const config = loaded.default?.default ?? loaded.default ?? loaded;process.stdout.write(${JSON.stringify(CONFIG_MARK)} + JSON.stringify({incremental: config.incremental, force: config.force,incrementalFile: config.incrementalFile, tempDirName: config.tempDirName,testRunner: config.testRunner, vitestConfig: config.vitest?.configFile,jestConfig: config.jest?.configFile, checkers: config.checkers,htmlReporter: config.htmlReporter, jsonReporter: config.jsonReporter,ignorePatterns: config.ignorePatterns,}) + ${JSON.stringify(CONFIG_MARK)});`;
|
|
3937
|
-
const result =
|
|
4456
|
+
const script = `const loaded = await import(${JSON.stringify(pathToFileURL(file).href)});const config = loaded.default?.default ?? loaded.default ?? loaded;process.stdout.write(${JSON.stringify(CONFIG_MARK)} + JSON.stringify({incremental: config.incremental, force: config.force,incrementalFile: config.incrementalFile, tempDirName: config.tempDirName,testRunner: config.testRunner, vitestConfig: config.vitest?.configFile,jestConfig: config.jest?.configFile, checkers: config.checkers,htmlReporter: config.htmlReporter, jsonReporter: config.jsonReporter,ignorePatterns: config.ignorePatterns, cleanTempDir: config.cleanTempDir,}) + ${JSON.stringify(CONFIG_MARK)});`;
|
|
4457
|
+
const result = spawnSync4(process.execPath, ["--input-type=module", "--eval", script], {
|
|
3938
4458
|
cwd: manifest.dir,
|
|
3939
4459
|
encoding: "utf8",
|
|
3940
4460
|
timeout: 6e4
|
|
@@ -3947,7 +4467,7 @@ function loadStrykerConfig(manifest, config) {
|
|
|
3947
4467
|
}
|
|
3948
4468
|
function modifiedAt(file) {
|
|
3949
4469
|
try {
|
|
3950
|
-
const stat =
|
|
4470
|
+
const stat = fs10.statSync(file, { bigint: true });
|
|
3951
4471
|
return `${stat.mtimeNs} ${stat.ino}`;
|
|
3952
4472
|
} catch {
|
|
3953
4473
|
return null;
|
|
@@ -3956,6 +4476,7 @@ function modifiedAt(file) {
|
|
|
3956
4476
|
function guardRecordPath(incrementalFile) {
|
|
3957
4477
|
return `${incrementalFile}.test-gates.json`;
|
|
3958
4478
|
}
|
|
4479
|
+
var forcedLine = (reason) => `test-gates mutation: running every mutant again (--force), the results of the last run are not reused: ${reason}`;
|
|
3959
4480
|
function coveringSpecs(report, gates) {
|
|
3960
4481
|
const fileOf = /* @__PURE__ */ new Map();
|
|
3961
4482
|
for (const [file, entry] of Object.entries(report.testFiles ?? {})) {
|
|
@@ -3982,10 +4503,20 @@ function coveringSpecs(report, gates) {
|
|
|
3982
4503
|
}
|
|
3983
4504
|
return covering;
|
|
3984
4505
|
}
|
|
3985
|
-
function
|
|
4506
|
+
function readSeed(incrementalFile, recordFile) {
|
|
4507
|
+
try {
|
|
4508
|
+
const incremental = fs10.readFileSync(incrementalFile);
|
|
4509
|
+
const record = JSON.parse(fs10.readFileSync(recordFile, "utf8"));
|
|
4510
|
+
const named = record?.incremental;
|
|
4511
|
+
return named === hashOf(incremental) ? { incremental, record } : null;
|
|
4512
|
+
} catch {
|
|
4513
|
+
return null;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
function decideReuse(manifest, config, strykerArgs, part, affected, found = () => void 0) {
|
|
3986
4517
|
const loaded = loadStrykerConfig(manifest, config);
|
|
3987
4518
|
const options = incrementalOptions(loaded, strykerArgs);
|
|
3988
|
-
const base =
|
|
4519
|
+
const base = fs10.realpathSync(manifest.dir);
|
|
3989
4520
|
const checkersArg = strykerArgs.some(
|
|
3990
4521
|
(arg) => arg === "--checkers" || arg.startsWith("--checkers=")
|
|
3991
4522
|
);
|
|
@@ -4009,35 +4540,42 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4009
4540
|
last: void 0,
|
|
4010
4541
|
ci: affected ? affectedRefused(process.env) : null,
|
|
4011
4542
|
unchanged: null,
|
|
4012
|
-
configs
|
|
4543
|
+
configs,
|
|
4544
|
+
temp: {
|
|
4545
|
+
dir: loaded === void 0 ? null : path10.resolve(base, options.tempDirName),
|
|
4546
|
+
keeps: loaded?.cleanTempDir === false
|
|
4547
|
+
},
|
|
4548
|
+
mainWorktree: null,
|
|
4549
|
+
seed: null
|
|
4013
4550
|
};
|
|
4551
|
+
found(decision.temp);
|
|
4014
4552
|
if (loaded !== void 0 && !options.incremental) {
|
|
4015
4553
|
return decision;
|
|
4016
4554
|
}
|
|
4017
4555
|
decision.incremental = true;
|
|
4018
|
-
const incrementalFile =
|
|
4556
|
+
const incrementalFile = path10.resolve(base, options.incrementalFile);
|
|
4019
4557
|
const recordFile = guardRecordPath(incrementalFile);
|
|
4020
4558
|
decision.incrementalFile = incrementalFile;
|
|
4021
4559
|
decision.recordFile = recordFile;
|
|
4022
4560
|
let snapshot = null;
|
|
4023
4561
|
let root = base;
|
|
4024
4562
|
let failure = "the Stryker config could not be loaded to see where the incremental file is";
|
|
4025
|
-
const reports = reportFiles(loaded).map((file) =>
|
|
4563
|
+
const reports = reportFiles(loaded).map((file) => path10.resolve(base, file));
|
|
4026
4564
|
const ignores = strykerIgnores(loaded);
|
|
4027
4565
|
const read2 = (file) => {
|
|
4028
|
-
const relative =
|
|
4029
|
-
const inside = !relative.startsWith("..") && !
|
|
4030
|
-
return !reports.includes(file) && !(inside && ignores(relative.split(
|
|
4566
|
+
const relative = path10.relative(base, file);
|
|
4567
|
+
const inside = !relative.startsWith("..") && !path10.isAbsolute(relative);
|
|
4568
|
+
return !reports.includes(file) && !(inside && ignores(relative.split(path10.sep).join("/")));
|
|
4031
4569
|
};
|
|
4032
4570
|
if (loaded !== void 0) {
|
|
4033
4571
|
const scope = (listed2) => {
|
|
4034
4572
|
try {
|
|
4035
|
-
const
|
|
4036
|
-
if (!("files" in
|
|
4037
|
-
decision.scope =
|
|
4573
|
+
const found2 = loadedScope(manifest, configs(), listed2);
|
|
4574
|
+
if (!("files" in found2)) {
|
|
4575
|
+
decision.scope = found2;
|
|
4038
4576
|
return null;
|
|
4039
4577
|
}
|
|
4040
|
-
const files = new Set([...
|
|
4578
|
+
const files = new Set([...found2.files].filter(read2));
|
|
4041
4579
|
decision.scope = { files: files.size };
|
|
4042
4580
|
return files;
|
|
4043
4581
|
} catch (error) {
|
|
@@ -4053,8 +4591,8 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4053
4591
|
written: [
|
|
4054
4592
|
incrementalFile,
|
|
4055
4593
|
recordFile,
|
|
4056
|
-
|
|
4057
|
-
|
|
4594
|
+
path10.resolve(base, options.tempDirName),
|
|
4595
|
+
path10.resolve(base, manifest.settings.strykerReportFile),
|
|
4058
4596
|
...reports
|
|
4059
4597
|
],
|
|
4060
4598
|
args: comparableArgs(strykerArgs)
|
|
@@ -4067,17 +4605,32 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4067
4605
|
decision.snapshot = snapshot;
|
|
4068
4606
|
decision.failure = snapshot === null ? failure : null;
|
|
4069
4607
|
decision.root = root;
|
|
4070
|
-
const
|
|
4608
|
+
const tempDir = path10.basename(options.tempDirName);
|
|
4609
|
+
if (!part && !options.force && snapshot?.mode === "git" && !path10.relative(root, base).split(path10.sep).includes(tempDir) && !fs10.existsSync(incrementalFile) && !fs10.existsSync(recordFile)) {
|
|
4610
|
+
const linked = linkedWorktreeOf(base);
|
|
4611
|
+
const mainIncremental = linked === null ? null : inMainWorktree(linked, incrementalFile);
|
|
4612
|
+
const mainRecord = linked === null ? null : inMainWorktree(linked, recordFile);
|
|
4613
|
+
if (linked !== null && mainIncremental !== null && mainRecord !== null) {
|
|
4614
|
+
decision.mainWorktree = linked.main;
|
|
4615
|
+
const seed = readSeed(mainIncremental, mainRecord);
|
|
4616
|
+
if (seed !== null) {
|
|
4617
|
+
decision.seed = seed;
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
const incremental = decision.seed === null ? hashFile(incrementalFile) : hashOf(decision.seed.incremental);
|
|
4071
4622
|
decision.incrementalHash = incremental;
|
|
4072
4623
|
if (part) {
|
|
4073
4624
|
decision.force = incremental !== null && !options.force ? "with --gate nothing is taken over from an earlier run" : null;
|
|
4074
4625
|
return decision;
|
|
4075
4626
|
}
|
|
4076
|
-
let stored;
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4627
|
+
let stored = decision.seed?.record;
|
|
4628
|
+
if (decision.seed === null) {
|
|
4629
|
+
try {
|
|
4630
|
+
stored = JSON.parse(fs10.readFileSync(recordFile, "utf8"));
|
|
4631
|
+
} catch {
|
|
4632
|
+
stored = void 0;
|
|
4633
|
+
}
|
|
4081
4634
|
}
|
|
4082
4635
|
const ci = decision.ci;
|
|
4083
4636
|
let force = null;
|
|
@@ -4091,6 +4644,9 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4091
4644
|
if (force === null) {
|
|
4092
4645
|
const read3 = readRecord({ record: stored, incremental, snapshot });
|
|
4093
4646
|
last = "record" in read3 ? read3.record.affected : void 0;
|
|
4647
|
+
if (decision.seed !== null) {
|
|
4648
|
+
force = blindInMainRecord(manifest, { root, snapshot, configs, last });
|
|
4649
|
+
}
|
|
4094
4650
|
}
|
|
4095
4651
|
} else if (ci !== null) {
|
|
4096
4652
|
force = ci;
|
|
@@ -4117,8 +4673,10 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4117
4673
|
config: inputs.config,
|
|
4118
4674
|
gates: manifest.gates.map((gate) => ({
|
|
4119
4675
|
path: gate.path,
|
|
4120
|
-
files: inputs.gates[gate.path]?.files ?? []
|
|
4121
|
-
|
|
4676
|
+
files: inputs.gates[gate.path]?.files ?? [],
|
|
4677
|
+
blind: inputs.gates[gate.path]?.blind
|
|
4678
|
+
})),
|
|
4679
|
+
seeded: decision.seed !== null
|
|
4122
4680
|
});
|
|
4123
4681
|
} catch (error) {
|
|
4124
4682
|
if (!(error instanceof AffectedError)) {
|
|
@@ -4127,7 +4685,7 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4127
4685
|
plan = { full: `what a gate depends on cannot be told: ${error.message}` };
|
|
4128
4686
|
}
|
|
4129
4687
|
if ("full" in plan) {
|
|
4130
|
-
if (refuseReuse({ record: stored, incremental, snapshot }) === null) {
|
|
4688
|
+
if (decision.seed === null && refuseReuse({ record: stored, incremental, snapshot }) === null) {
|
|
4131
4689
|
decision.unchanged = plan.full;
|
|
4132
4690
|
plan = null;
|
|
4133
4691
|
} else {
|
|
@@ -4144,9 +4702,45 @@ function decideReuse(manifest, config, strykerArgs, part, affected) {
|
|
|
4144
4702
|
decision.last = last;
|
|
4145
4703
|
return decision;
|
|
4146
4704
|
}
|
|
4147
|
-
function
|
|
4148
|
-
const
|
|
4149
|
-
const
|
|
4705
|
+
function blindInMainRecord(manifest, input) {
|
|
4706
|
+
const { last } = input;
|
|
4707
|
+
const unknown = last === void 0 ? "each gate" : manifest.gates.find((gate) => !Object.hasOwn(last.gates, gate.path))?.path;
|
|
4708
|
+
if (last === void 0 || unknown !== void 0) {
|
|
4709
|
+
return `the record of the main worktree does not say what ${unknown} depends on, so it may read files git does not list, which differ between worktrees`;
|
|
4710
|
+
}
|
|
4711
|
+
try {
|
|
4712
|
+
const inputs = affectedInputs(manifest, {
|
|
4713
|
+
root: input.root,
|
|
4714
|
+
files: input.snapshot.files,
|
|
4715
|
+
configs: input.configs(),
|
|
4716
|
+
tests: last.tests,
|
|
4717
|
+
covering: Object.fromEntries(
|
|
4718
|
+
Object.entries(last.gates).map(([gate, entry]) => [gate, entry.specs])
|
|
4719
|
+
)
|
|
4720
|
+
});
|
|
4721
|
+
for (const gate of manifest.gates) {
|
|
4722
|
+
const blind = inputs.gates[gate.path]?.blind ?? last.gates[gate.path]?.blind;
|
|
4723
|
+
if (blind !== void 0) {
|
|
4724
|
+
return `${gate.path} may read files git does not list, which differ between worktrees (${blind})`;
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
return null;
|
|
4728
|
+
} catch (error) {
|
|
4729
|
+
if (!(error instanceof AffectedError)) {
|
|
4730
|
+
throw error;
|
|
4731
|
+
}
|
|
4732
|
+
return `what a gate depends on cannot be told, so it may read files git does not list, which differ between worktrees: ${error.message}`;
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
function guardIncremental(manifest, config, strykerArgs, part, affected, found) {
|
|
4736
|
+
const decision = decideReuse(manifest, config, strykerArgs, part, affected, found);
|
|
4737
|
+
const nothing = {
|
|
4738
|
+
force: null,
|
|
4739
|
+
mutate: null,
|
|
4740
|
+
said: [],
|
|
4741
|
+
temp: decision.temp,
|
|
4742
|
+
record: () => void 0
|
|
4743
|
+
};
|
|
4150
4744
|
if (!decision.incremental) {
|
|
4151
4745
|
return affected ? {
|
|
4152
4746
|
...nothing,
|
|
@@ -4161,10 +4755,14 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
4161
4755
|
const incremental = decision.incrementalHash;
|
|
4162
4756
|
const options = { force: decision.forced };
|
|
4163
4757
|
if (part) {
|
|
4164
|
-
|
|
4758
|
+
fs10.rmSync(recordFile, { force: true });
|
|
4165
4759
|
return { ...nothing, force };
|
|
4166
4760
|
}
|
|
4167
|
-
|
|
4761
|
+
if (decision.seed !== null) {
|
|
4762
|
+
fs10.mkdirSync(path10.dirname(incrementalFile), { recursive: true });
|
|
4763
|
+
fs10.writeFileSync(incrementalFile, decision.seed.incremental);
|
|
4764
|
+
}
|
|
4765
|
+
fs10.rmSync(recordFile, { force: true });
|
|
4168
4766
|
const before = modifiedAt(incrementalFile);
|
|
4169
4767
|
const rerun = plan !== null && "rerun" in plan ? plan.rerun.map((entry) => entry.gate) : null;
|
|
4170
4768
|
const everyGate = force ?? (rerun !== null && rerun.length > 0 && rerun.length === manifest.gates.length ? "every gate is affected" : null);
|
|
@@ -4179,7 +4777,7 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
4179
4777
|
rerun: timedOut,
|
|
4180
4778
|
expected
|
|
4181
4779
|
} = timeoutRanges(
|
|
4182
|
-
JSON.parse(
|
|
4780
|
+
JSON.parse(fs10.readFileSync(incrementalFile, "utf8")),
|
|
4183
4781
|
kept
|
|
4184
4782
|
));
|
|
4185
4783
|
} catch {
|
|
@@ -4210,11 +4808,20 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
4210
4808
|
said.push(`test-gates mutation: ${describeExpected(expected)}`);
|
|
4211
4809
|
}
|
|
4212
4810
|
}
|
|
4811
|
+
if (decision.seed !== null) {
|
|
4812
|
+
if (said.length === 0 && everyGate !== null) {
|
|
4813
|
+
said.push(forcedLine(everyGate));
|
|
4814
|
+
}
|
|
4815
|
+
said.unshift(
|
|
4816
|
+
`test-gates mutation: this worktree has no record of its own, so it starts from the one of the main worktree (${decision.mainWorktree})`
|
|
4817
|
+
);
|
|
4818
|
+
}
|
|
4213
4819
|
return {
|
|
4214
4820
|
// Every gate is run again: the same as a run of every mutant.
|
|
4215
4821
|
force: everyGate,
|
|
4216
4822
|
mutate: subset.length + ranges.length > 0 ? [...subset, ...ranges] : null,
|
|
4217
4823
|
said,
|
|
4824
|
+
temp: decision.temp,
|
|
4218
4825
|
record: (judged) => {
|
|
4219
4826
|
const written = hashFile(incrementalFile);
|
|
4220
4827
|
if (snapshot === null || written === null || modifiedAt(incrementalFile) === before) {
|
|
@@ -4225,7 +4832,7 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
4225
4832
|
record.affected = affectedRecord(judged, ran, last);
|
|
4226
4833
|
}
|
|
4227
4834
|
try {
|
|
4228
|
-
|
|
4835
|
+
fs10.writeFileSync(recordFile, `${JSON.stringify(record)}
|
|
4229
4836
|
`);
|
|
4230
4837
|
} catch {
|
|
4231
4838
|
}
|
|
@@ -4256,7 +4863,11 @@ function guardIncremental(manifest, config, strykerArgs, part, affected) {
|
|
|
4256
4863
|
for (const gate of Object.keys(covering)) {
|
|
4257
4864
|
const entry = inputs.gates[gate];
|
|
4258
4865
|
if (entry !== void 0) {
|
|
4259
|
-
recorded[gate] = {
|
|
4866
|
+
recorded[gate] = {
|
|
4867
|
+
specs: entry.specs,
|
|
4868
|
+
files: entry.files,
|
|
4869
|
+
...entry.blind === void 0 ? {} : { blind: entry.blind }
|
|
4870
|
+
};
|
|
4260
4871
|
}
|
|
4261
4872
|
}
|
|
4262
4873
|
return { tests: Object.keys(judged.report.testFiles ?? {}).sort(), gates: recorded };
|
|
@@ -4307,53 +4918,120 @@ function runMutation(dir, strykerArgs, io2, only = [], affected = false) {
|
|
|
4307
4918
|
);
|
|
4308
4919
|
}
|
|
4309
4920
|
const reportFile = reportPathOf(manifest, null);
|
|
4310
|
-
|
|
4311
|
-
|
|
4921
|
+
fs10.rmSync(reportFile, { force: true });
|
|
4922
|
+
let removed = [];
|
|
4923
|
+
const guard = guardIncremental(manifest, config, strykerArgs, part, affected, (temp) => {
|
|
4924
|
+
removed = removeStaleSandboxes(manifest.dir, temp);
|
|
4925
|
+
});
|
|
4312
4926
|
if (guard.said.length > 0) {
|
|
4313
4927
|
guard.said.forEach((line) => io2.out(line));
|
|
4314
4928
|
} else if (guard.force !== null) {
|
|
4315
|
-
io2.out(
|
|
4316
|
-
`test-gates mutation: running every mutant again (--force), the results of the last run are not reused: ${guard.force}`
|
|
4317
|
-
);
|
|
4929
|
+
io2.out(forcedLine(guard.force));
|
|
4318
4930
|
}
|
|
4931
|
+
removed.forEach((line) => io2.out(line));
|
|
4319
4932
|
const guardArgs = guard.force === null && guard.mutate === null ? [] : ["--force"];
|
|
4320
4933
|
const partArgs = part ? ["--mutate", chosen.selected.join(",")] : guard.mutate !== null ? ["--mutate", guard.mutate.join(",")] : [];
|
|
4321
|
-
const
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4934
|
+
const unmark = markRun(manifest.dir, guard.temp);
|
|
4935
|
+
let retried;
|
|
4936
|
+
try {
|
|
4937
|
+
const result = spawnSync4(stryker, ["run", config, ...strykerArgs, ...partArgs, ...guardArgs], {
|
|
4938
|
+
cwd: manifest.dir,
|
|
4939
|
+
stdio: "inherit",
|
|
4940
|
+
shell: process.platform === "win32"
|
|
4941
|
+
});
|
|
4942
|
+
if (result.error) {
|
|
4943
|
+
io2.err(`test-gates mutation: cannot start Stryker (${result.error.message})`);
|
|
4944
|
+
return 1;
|
|
4945
|
+
}
|
|
4946
|
+
if (result.status !== 0) {
|
|
4947
|
+
io2.err(
|
|
4948
|
+
`test-gates mutation: Stryker failed (${result.signal ? `signal ${result.signal}` : `exit ${result.status}`}). The result was not judged`
|
|
4949
|
+
);
|
|
4950
|
+
return 1;
|
|
4951
|
+
}
|
|
4952
|
+
retried = retryTimeouts(
|
|
4953
|
+
stryker,
|
|
4954
|
+
[config, ...strykerArgs],
|
|
4955
|
+
manifest,
|
|
4956
|
+
reportFile,
|
|
4957
|
+
part ? manifest.gates.filter((gate) => chosen.selected.includes(gate.path)) : manifest.gates,
|
|
4958
|
+
io2
|
|
4333
4959
|
);
|
|
4334
|
-
|
|
4960
|
+
} finally {
|
|
4961
|
+
unmark();
|
|
4335
4962
|
}
|
|
4336
4963
|
let judged = null;
|
|
4337
|
-
const status = runMutationResult(
|
|
4338
|
-
|
|
4339
|
-
|
|
4964
|
+
const status = runMutationResult(
|
|
4965
|
+
dir,
|
|
4966
|
+
null,
|
|
4967
|
+
io2,
|
|
4968
|
+
part ? chosen.selected : [],
|
|
4969
|
+
(seen) => {
|
|
4970
|
+
judged = seen;
|
|
4971
|
+
},
|
|
4972
|
+
retried
|
|
4973
|
+
);
|
|
4340
4974
|
guard.record(judged);
|
|
4341
4975
|
if (part) {
|
|
4342
|
-
|
|
4976
|
+
fs10.rmSync(reportFile, { force: true });
|
|
4343
4977
|
}
|
|
4344
4978
|
return status;
|
|
4345
4979
|
}
|
|
4980
|
+
function retryTimeouts(stryker, args, manifest, reportFile, gates, io2) {
|
|
4981
|
+
let first;
|
|
4982
|
+
try {
|
|
4983
|
+
first = JSON.parse(fs10.readFileSync(reportFile, "utf8"));
|
|
4984
|
+
} catch {
|
|
4985
|
+
return null;
|
|
4986
|
+
}
|
|
4987
|
+
if (typeof first !== "object" || first === null) {
|
|
4988
|
+
return null;
|
|
4989
|
+
}
|
|
4990
|
+
const { ranges, rerun } = timeoutRanges(first, gates);
|
|
4991
|
+
if (ranges.length === 0 || rerun <= manifest.settings.maxTimeouts) {
|
|
4992
|
+
return null;
|
|
4993
|
+
}
|
|
4994
|
+
io2.out(
|
|
4995
|
+
`test-gates mutation: ${rerun} mutant(s) timed out outside expectedTimeouts, running them again one at a time (--concurrency 1)`
|
|
4996
|
+
);
|
|
4997
|
+
fs10.rmSync(reportFile, { force: true });
|
|
4998
|
+
const result = spawnSync4(
|
|
4999
|
+
stryker,
|
|
5000
|
+
["run", ...args, "--mutate", ranges.join(","), "--concurrency", "1", "--force"],
|
|
5001
|
+
{ cwd: manifest.dir, stdio: "inherit", shell: process.platform === "win32" }
|
|
5002
|
+
);
|
|
5003
|
+
let retry = null;
|
|
5004
|
+
if (!result.error && result.status === 0) {
|
|
5005
|
+
try {
|
|
5006
|
+
retry = JSON.parse(fs10.readFileSync(reportFile, "utf8"));
|
|
5007
|
+
} catch {
|
|
5008
|
+
retry = null;
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
if (retry === null || typeof retry !== "object") {
|
|
5012
|
+
const why = result.error ? result.error.message : result.signal ? `signal ${result.signal}` : result.status === 0 ? "no report" : `exit ${result.status}`;
|
|
5013
|
+
io2.err(
|
|
5014
|
+
`test-gates mutation: running the timed-out mutants again failed (${why}). The first run is judged`
|
|
5015
|
+
);
|
|
5016
|
+
fs10.writeFileSync(reportFile, JSON.stringify(first));
|
|
5017
|
+
return "failed";
|
|
5018
|
+
}
|
|
5019
|
+
const merged = mergeRetry(first, retry, gates);
|
|
5020
|
+
fs10.writeFileSync(reportFile, JSON.stringify(merged.report));
|
|
5021
|
+
io2.out(formatRetry(merged.outcome));
|
|
5022
|
+
return merged.outcome;
|
|
5023
|
+
}
|
|
4346
5024
|
|
|
4347
5025
|
// src/commands/doctor.ts
|
|
4348
5026
|
function read(file) {
|
|
4349
5027
|
try {
|
|
4350
|
-
return
|
|
5028
|
+
return fs11.readFileSync(file, "utf8");
|
|
4351
5029
|
} catch {
|
|
4352
5030
|
return null;
|
|
4353
5031
|
}
|
|
4354
5032
|
}
|
|
4355
|
-
function
|
|
4356
|
-
const result =
|
|
5033
|
+
function git3(dir, args) {
|
|
5034
|
+
const result = spawnSync5("git", args, {
|
|
4357
5035
|
cwd: dir,
|
|
4358
5036
|
env: gitEnvironment(process.env),
|
|
4359
5037
|
encoding: "utf8"
|
|
@@ -4361,20 +5039,20 @@ function git2(dir, args) {
|
|
|
4361
5039
|
return result.status === 0 ? result.stdout.trim() : null;
|
|
4362
5040
|
}
|
|
4363
5041
|
function hooksOf(dir) {
|
|
4364
|
-
const root =
|
|
4365
|
-
const hooksDir =
|
|
5042
|
+
const root = git3(dir, ["rev-parse", "--show-toplevel"]) ?? dir;
|
|
5043
|
+
const hooksDir = git3(dir, ["rev-parse", "--path-format=absolute", "--git-path", "hooks"]);
|
|
4366
5044
|
const candidates = [
|
|
4367
|
-
...["pre-push", "pre-commit"].map((name) =>
|
|
4368
|
-
...hooksDir === null ? [] : ["pre-push", "pre-commit"].map((name) =>
|
|
4369
|
-
...["lefthook.yml", ".lefthook.yml"].map((name) =>
|
|
5045
|
+
...["pre-push", "pre-commit"].map((name) => path11.join(root, ".husky", name)),
|
|
5046
|
+
...hooksDir === null ? [] : ["pre-push", "pre-commit"].map((name) => path11.join(hooksDir, name)),
|
|
5047
|
+
...["lefthook.yml", ".lefthook.yml"].map((name) => path11.join(root, name))
|
|
4370
5048
|
];
|
|
4371
5049
|
let script = "";
|
|
4372
5050
|
try {
|
|
4373
|
-
const manifest = JSON.parse(read(
|
|
5051
|
+
const manifest = JSON.parse(read(path11.join(dir, "package.json")) ?? "{}");
|
|
4374
5052
|
script = manifest.scripts?.["test:gates:mutation"] ?? "";
|
|
4375
5053
|
} catch {
|
|
4376
5054
|
}
|
|
4377
|
-
const subproject =
|
|
5055
|
+
const subproject = path11.relative(fs11.realpathSync(root), dir).split(path11.sep).join("/");
|
|
4378
5056
|
const seen = /* @__PURE__ */ new Set();
|
|
4379
5057
|
const uses = [];
|
|
4380
5058
|
for (const file of candidates) {
|
|
@@ -4382,12 +5060,12 @@ function hooksOf(dir) {
|
|
|
4382
5060
|
if (text === null) {
|
|
4383
5061
|
continue;
|
|
4384
5062
|
}
|
|
4385
|
-
const real =
|
|
5063
|
+
const real = fs11.realpathSync(file);
|
|
4386
5064
|
if (seen.has(real)) {
|
|
4387
5065
|
continue;
|
|
4388
5066
|
}
|
|
4389
5067
|
seen.add(real);
|
|
4390
|
-
const shown =
|
|
5068
|
+
const shown = path11.relative(root, file).split(path11.sep).join("/");
|
|
4391
5069
|
uses.push(...hookUses({ file: shown, text, subproject, script }));
|
|
4392
5070
|
}
|
|
4393
5071
|
return uses;
|
|
@@ -4404,8 +5082,8 @@ function runDoctor(dir, io2) {
|
|
|
4404
5082
|
const affected = decideReuse(manifest, config, [], false, true);
|
|
4405
5083
|
io2.out(`test-gates doctor: ${manifest.dir} (${config})`);
|
|
4406
5084
|
if (plain2.snapshot !== null) {
|
|
4407
|
-
const subproject =
|
|
4408
|
-
const prefix = subproject === "" ? "" : `${subproject.split(
|
|
5085
|
+
const subproject = path11.relative(plain2.root, fs11.realpathSync(manifest.dir));
|
|
5086
|
+
const prefix = subproject === "" ? "" : `${subproject.split(path11.sep).join("/")}/`;
|
|
4409
5087
|
const keys = Object.keys(plain2.snapshot.files);
|
|
4410
5088
|
const outside = keys.filter((key) => !key.startsWith(prefix)).length;
|
|
4411
5089
|
const followed = plain2.scope === null ? "" : "files" in plain2.scope ? `, paths followed in the ${plain2.scope.files} files the run loads` : ", paths followed in every file";
|
|
@@ -4416,6 +5094,15 @@ function runDoctor(dir, io2) {
|
|
|
4416
5094
|
io2.out(
|
|
4417
5095
|
reuse === null ? " next run: reuses the results of the last run" : ` next run: every mutant, ${reuse}`
|
|
4418
5096
|
);
|
|
5097
|
+
if (plain2.seed !== null) {
|
|
5098
|
+
io2.out(
|
|
5099
|
+
` this worktree has no record of its own: the next run starts from the one of the main worktree (${plain2.mainWorktree})`
|
|
5100
|
+
);
|
|
5101
|
+
} else if (plain2.mainWorktree !== null) {
|
|
5102
|
+
io2.out(
|
|
5103
|
+
` this worktree has no record of its own, and the main worktree (${plain2.mainWorktree}) has none this worktree can start from`
|
|
5104
|
+
);
|
|
5105
|
+
}
|
|
4419
5106
|
if (affected.ci !== null) {
|
|
4420
5107
|
io2.out(` next run with --affected: every mutant, ${affected.ci}`);
|
|
4421
5108
|
} else if (affected.plan !== null && "rerun" in affected.plan) {
|
|
@@ -4432,15 +5119,20 @@ function runDoctor(dir, io2) {
|
|
|
4432
5119
|
}
|
|
4433
5120
|
}
|
|
4434
5121
|
let blind = null;
|
|
5122
|
+
let blindGates = [];
|
|
4435
5123
|
if (plain2.snapshot !== null) {
|
|
4436
5124
|
try {
|
|
4437
|
-
affectedInputs(manifest, {
|
|
5125
|
+
const inputs = affectedInputs(manifest, {
|
|
4438
5126
|
root: plain2.root,
|
|
4439
5127
|
files: plain2.snapshot.files,
|
|
4440
5128
|
configs: plain2.configs(),
|
|
4441
5129
|
tests: [],
|
|
4442
5130
|
covering: {}
|
|
4443
5131
|
});
|
|
5132
|
+
blindGates = manifest.gates.flatMap((gate) => {
|
|
5133
|
+
const reason = inputs.gates[gate.path]?.blind;
|
|
5134
|
+
return reason === void 0 ? [] : [{ gate: gate.path, reason }];
|
|
5135
|
+
});
|
|
4444
5136
|
} catch (error) {
|
|
4445
5137
|
if (!(error instanceof AffectedError)) {
|
|
4446
5138
|
throw error;
|
|
@@ -4448,6 +5140,8 @@ function runDoctor(dir, io2) {
|
|
|
4448
5140
|
blind = error.message;
|
|
4449
5141
|
}
|
|
4450
5142
|
}
|
|
5143
|
+
const left = sandboxPlan(manifest.dir, plain2.temp);
|
|
5144
|
+
const count = "kept" in left.plan ? left.plan.kept.length : left.plan.remove.length;
|
|
4451
5145
|
const { lines, status } = formatFindings(
|
|
4452
5146
|
diagnose({
|
|
4453
5147
|
incremental: plain2.incremental,
|
|
@@ -4455,8 +5149,16 @@ function runDoctor(dir, io2) {
|
|
|
4455
5149
|
failure: plain2.failure,
|
|
4456
5150
|
widened: plain2.widened,
|
|
4457
5151
|
scope: plain2.scope,
|
|
4458
|
-
hooks: hooksOf(
|
|
4459
|
-
blind
|
|
5152
|
+
hooks: hooksOf(fs11.realpathSync(manifest.dir)),
|
|
5153
|
+
blind,
|
|
5154
|
+
blindGates,
|
|
5155
|
+
gates: manifest.gates.length,
|
|
5156
|
+
sandboxes: count === 0 ? null : {
|
|
5157
|
+
tempDir: left.shown,
|
|
5158
|
+
count,
|
|
5159
|
+
kept: "kept" in left.plan ? left.plan.why : null,
|
|
5160
|
+
running: "kept" in left.plan ? left.plan.running : null
|
|
5161
|
+
}
|
|
4460
5162
|
})
|
|
4461
5163
|
);
|
|
4462
5164
|
lines.forEach((line) => io2.out(line));
|
|
@@ -4464,8 +5166,8 @@ function runDoctor(dir, io2) {
|
|
|
4464
5166
|
}
|
|
4465
5167
|
|
|
4466
5168
|
// src/commands/lcov.ts
|
|
4467
|
-
import
|
|
4468
|
-
import
|
|
5169
|
+
import fs12 from "fs";
|
|
5170
|
+
import path12 from "path";
|
|
4469
5171
|
|
|
4470
5172
|
// src/core/lcov.ts
|
|
4471
5173
|
function normalizePath2(file, rootDir) {
|
|
@@ -4492,9 +5194,9 @@ function parseLcov(text, rootDir) {
|
|
|
4492
5194
|
const line = rawLine.trim();
|
|
4493
5195
|
if (line.startsWith("SF:")) {
|
|
4494
5196
|
closeRecord();
|
|
4495
|
-
const
|
|
4496
|
-
const file = files.get(
|
|
4497
|
-
files.set(
|
|
5197
|
+
const path15 = normalizePath2(line.slice(3), rootDir);
|
|
5198
|
+
const file = files.get(path15) ?? { lines: /* @__PURE__ */ new Map(), found: 0, hit: 0 };
|
|
5199
|
+
files.set(path15, file);
|
|
4498
5200
|
record = { file, hasDa: false, found: 0, hit: 0 };
|
|
4499
5201
|
} else if (line === "end_of_record") {
|
|
4500
5202
|
closeRecord();
|
|
@@ -4578,10 +5280,10 @@ function runLcov(dir, fileOption, io2) {
|
|
|
4578
5280
|
"no lcov file given. Pass --file <lcov.info> or set settings.lcov.file"
|
|
4579
5281
|
);
|
|
4580
5282
|
}
|
|
4581
|
-
const lcovPath =
|
|
5283
|
+
const lcovPath = path12.resolve(manifest.dir, file);
|
|
4582
5284
|
let text;
|
|
4583
5285
|
try {
|
|
4584
|
-
text =
|
|
5286
|
+
text = fs12.readFileSync(lcovPath, "utf8");
|
|
4585
5287
|
} catch (error) {
|
|
4586
5288
|
io2.err(
|
|
4587
5289
|
`test-gates lcov: cannot read ${file} (${error.message}). Run the tests with coverage first`
|
|
@@ -4624,10 +5326,10 @@ function runLcov(dir, fileOption, io2) {
|
|
|
4624
5326
|
}
|
|
4625
5327
|
|
|
4626
5328
|
// src/commands/selfcheck.ts
|
|
4627
|
-
import { spawn, spawnSync as
|
|
4628
|
-
import
|
|
5329
|
+
import { spawn, spawnSync as spawnSync6 } from "child_process";
|
|
5330
|
+
import fs14 from "fs";
|
|
4629
5331
|
import os2 from "os";
|
|
4630
|
-
import
|
|
5332
|
+
import path14 from "path";
|
|
4631
5333
|
|
|
4632
5334
|
// src/core/jobs.ts
|
|
4633
5335
|
var SWITCHES = {
|
|
@@ -4667,6 +5369,34 @@ function knownCommand(program, args) {
|
|
|
4667
5369
|
function jobFlags(runner, dir) {
|
|
4668
5370
|
return runner === "jest" ? [`--coverageDirectory=${dir}/coverage`, `--cacheDirectory=${dir}/cache`] : [`--coverage.reportsDirectory=${dir}/coverage`, "--no-cache"];
|
|
4669
5371
|
}
|
|
5372
|
+
var LIMITS = {
|
|
5373
|
+
jest: ["--runInBand", "-i", "--maxWorkers", "-w"],
|
|
5374
|
+
vitest: ["--no-file-parallelism", "--fileParallelism", "--maxWorkers", "--minWorkers"]
|
|
5375
|
+
};
|
|
5376
|
+
var CONFIG_LIMITS = [
|
|
5377
|
+
"maxWorkers",
|
|
5378
|
+
"minWorkers",
|
|
5379
|
+
"maxThreads",
|
|
5380
|
+
"minThreads",
|
|
5381
|
+
"maxForks",
|
|
5382
|
+
"minForks",
|
|
5383
|
+
"fileParallelism",
|
|
5384
|
+
"singleThread",
|
|
5385
|
+
"singleFork"
|
|
5386
|
+
];
|
|
5387
|
+
function workerFlags(input) {
|
|
5388
|
+
if (input.lines <= 1) {
|
|
5389
|
+
return [];
|
|
5390
|
+
}
|
|
5391
|
+
const given = input.args.some(
|
|
5392
|
+
(arg) => LIMITS[input.runner].includes(arg.split("=")[0])
|
|
5393
|
+
);
|
|
5394
|
+
const configured = CONFIG_LIMITS.some((word) => input.configSource?.includes(word) === true);
|
|
5395
|
+
if (given || configured) {
|
|
5396
|
+
return [];
|
|
5397
|
+
}
|
|
5398
|
+
return [`--maxWorkers=${Math.max(1, Math.floor(input.cores / input.lines))}`];
|
|
5399
|
+
}
|
|
4670
5400
|
var REACHES = [
|
|
4671
5401
|
"process.",
|
|
4672
5402
|
"process?.",
|
|
@@ -4698,10 +5428,22 @@ var REACHES = [
|
|
|
4698
5428
|
];
|
|
4699
5429
|
var COMPUTED_LOAD = /(?:require|import)\((?!['"])|\brequire(?!\(|\.resolve\b|[\w$])|\bimport\.meta\.glob\b/;
|
|
4700
5430
|
var COMPUTED = "loads a module whose name is computed";
|
|
5431
|
+
var STRING = /(^|[:,([={?+&|;])(\s*)(['"])((?:\\.|(?!\3)[^\\\n])*)\3(?=\s*(?:[,}\]);:+?&|]|$))/gm;
|
|
5432
|
+
function withoutStrings(source) {
|
|
5433
|
+
return source.replace(
|
|
5434
|
+
STRING,
|
|
5435
|
+
(all, before, space, quote, inside) => (
|
|
5436
|
+
// In a template (`{"home": "${home()}"}`) the quotes are text, and what is
|
|
5437
|
+
// between them may be code.
|
|
5438
|
+
inside.includes("${") ? all : `${before}${space}${quote}${" ".repeat(inside.length)}${quote}`
|
|
5439
|
+
)
|
|
5440
|
+
);
|
|
5441
|
+
}
|
|
4701
5442
|
var RUNNER_PACKAGES = ["vitest", "@jest/globals"];
|
|
4702
5443
|
function reachesOutside(file, trusted) {
|
|
4703
5444
|
const dense = file.source.replace(/\s/g, "");
|
|
4704
|
-
const
|
|
5445
|
+
const code = withoutStrings(file.source).replace(/\s/g, "");
|
|
5446
|
+
const mark = REACHES.find((text) => code.includes(text));
|
|
4705
5447
|
if (mark !== void 0) {
|
|
4706
5448
|
return `uses ${mark.replace(/[.?[(:]+$/, "")}`;
|
|
4707
5449
|
}
|
|
@@ -4844,8 +5586,8 @@ function cleanHold(input) {
|
|
|
4844
5586
|
|
|
4845
5587
|
// src/touch.ts
|
|
4846
5588
|
import { createHash as createHash2 } from "crypto";
|
|
4847
|
-
import
|
|
4848
|
-
import
|
|
5589
|
+
import fs13 from "fs";
|
|
5590
|
+
import path13 from "path";
|
|
4849
5591
|
import { fileURLToPath } from "url";
|
|
4850
5592
|
var TOUCH_ENV = "TEST_GATES_TOUCH_DIR";
|
|
4851
5593
|
function isObject(value) {
|
|
@@ -4879,7 +5621,7 @@ function isCounters(value) {
|
|
|
4879
5621
|
function readNotes(directory) {
|
|
4880
5622
|
const read2 = (file) => {
|
|
4881
5623
|
try {
|
|
4882
|
-
return JSON.parse(
|
|
5624
|
+
return JSON.parse(fs13.readFileSync(path13.join(directory, file), "utf8"));
|
|
4883
5625
|
} catch {
|
|
4884
5626
|
return null;
|
|
4885
5627
|
}
|
|
@@ -4887,8 +5629,8 @@ function readNotes(directory) {
|
|
|
4887
5629
|
const run = read2("run.json");
|
|
4888
5630
|
let notes = [];
|
|
4889
5631
|
try {
|
|
4890
|
-
for (const name of
|
|
4891
|
-
const note = read2(
|
|
5632
|
+
for (const name of fs13.readdirSync(path13.join(directory, "specs"))) {
|
|
5633
|
+
const note = read2(path13.join("specs", name));
|
|
4892
5634
|
if (!isObject(note) || typeof note.spec !== "string" || !isObject(note.files) || !Object.values(note.files).every(isCounters)) {
|
|
4893
5635
|
throw new Error("not a note");
|
|
4894
5636
|
}
|
|
@@ -4900,7 +5642,7 @@ function readNotes(directory) {
|
|
|
4900
5642
|
return {
|
|
4901
5643
|
run: isObject(run) && typeof run.runner === "string" && typeof run.version === "string" && typeof run.provider === "string" && typeof run.pool === "string" && typeof run.isolate === "boolean" && typeof run.projects === "number" && (run.problem === null || typeof run.problem === "string") ? run : null,
|
|
4902
5644
|
notes,
|
|
4903
|
-
totals: countersOfAll(read2(
|
|
5645
|
+
totals: countersOfAll(read2(path13.join("coverage", "coverage-final.json")))
|
|
4904
5646
|
};
|
|
4905
5647
|
}
|
|
4906
5648
|
|
|
@@ -4915,7 +5657,7 @@ function resolveGateCommand(manifest) {
|
|
|
4915
5657
|
if (manifest.settings.gateCommand !== null) {
|
|
4916
5658
|
return manifest.settings.gateCommand;
|
|
4917
5659
|
}
|
|
4918
|
-
const exists = (name) =>
|
|
5660
|
+
const exists = (name) => fs14.existsSync(path14.join(manifest.dir, name));
|
|
4919
5661
|
const jest = JEST_CONFIGS.find(exists);
|
|
4920
5662
|
const vitest = VITEST_CONFIGS.find(exists);
|
|
4921
5663
|
if (jest && vitest) {
|
|
@@ -4936,7 +5678,7 @@ function resolveGateCommand(manifest) {
|
|
|
4936
5678
|
var TOUCH_PREFIX = ".test-gates-touch-";
|
|
4937
5679
|
function realPath(file) {
|
|
4938
5680
|
try {
|
|
4939
|
-
return
|
|
5681
|
+
return fs14.realpathSync(file);
|
|
4940
5682
|
} catch {
|
|
4941
5683
|
return file;
|
|
4942
5684
|
}
|
|
@@ -4944,13 +5686,16 @@ function realPath(file) {
|
|
|
4944
5686
|
function shareOneRun(manifest, targets, runGate) {
|
|
4945
5687
|
let touchDir;
|
|
4946
5688
|
try {
|
|
4947
|
-
for (const name of
|
|
4948
|
-
const left =
|
|
4949
|
-
|
|
4950
|
-
|
|
5689
|
+
for (const name of fs14.readdirSync(manifest.dir)) {
|
|
5690
|
+
const left = path14.join(manifest.dir, name);
|
|
5691
|
+
try {
|
|
5692
|
+
if (name.startsWith(TOUCH_PREFIX) && Date.now() - fs14.statSync(left).mtimeMs > 36e5) {
|
|
5693
|
+
fs14.rmSync(left, { recursive: true, force: true });
|
|
5694
|
+
}
|
|
5695
|
+
} catch {
|
|
4951
5696
|
}
|
|
4952
5697
|
}
|
|
4953
|
-
touchDir =
|
|
5698
|
+
touchDir = fs14.mkdtempSync(path14.join(manifest.dir, TOUCH_PREFIX));
|
|
4954
5699
|
} catch {
|
|
4955
5700
|
return { reason: "the subproject cannot be written to" };
|
|
4956
5701
|
}
|
|
@@ -4961,14 +5706,14 @@ function shareOneRun(manifest, targets, runGate) {
|
|
|
4961
5706
|
return { reason: "the gate run with every spec does not pass" };
|
|
4962
5707
|
}
|
|
4963
5708
|
const base = realPath(manifest.dir);
|
|
4964
|
-
const absolute = (file) => realPath(
|
|
5709
|
+
const absolute = (file) => realPath(path14.resolve(base, file));
|
|
4965
5710
|
const rekeyed = (files) => Object.fromEntries(Object.entries(files).map(([file, value]) => [absolute(file), value]));
|
|
4966
5711
|
const left = readNotes(touchDir);
|
|
4967
5712
|
plan = planControls({
|
|
4968
5713
|
all: manifest.gates.map((gate) => ({
|
|
4969
5714
|
path: gate.path,
|
|
4970
|
-
file:
|
|
4971
|
-
specFile:
|
|
5715
|
+
file: path14.join(base, gate.path),
|
|
5716
|
+
specFile: path14.join(base, gate.spec ?? "")
|
|
4972
5717
|
})),
|
|
4973
5718
|
targets: targets.map((gate) => gate.path),
|
|
4974
5719
|
run: left.run,
|
|
@@ -4977,7 +5722,7 @@ function shareOneRun(manifest, targets, runGate) {
|
|
|
4977
5722
|
failurePattern: manifest.settings.selfcheckFailurePattern
|
|
4978
5723
|
});
|
|
4979
5724
|
} finally {
|
|
4980
|
-
|
|
5725
|
+
fs14.rmSync(touchDir, { recursive: true, force: true });
|
|
4981
5726
|
}
|
|
4982
5727
|
if (plan.reason !== null) {
|
|
4983
5728
|
return { reason: plan.reason };
|
|
@@ -4987,15 +5732,15 @@ function shareOneRun(manifest, targets, runGate) {
|
|
|
4987
5732
|
return { gates, result: runGate({ excludeAll: gates.map((gate) => gate.spec) }) };
|
|
4988
5733
|
}
|
|
4989
5734
|
function installedVersion2(start, name) {
|
|
4990
|
-
let current =
|
|
5735
|
+
let current = path14.resolve(start);
|
|
4991
5736
|
for (; ; ) {
|
|
4992
5737
|
try {
|
|
4993
5738
|
const manifest = JSON.parse(
|
|
4994
|
-
|
|
5739
|
+
fs14.readFileSync(path14.join(current, "node_modules", name, "package.json"), "utf8")
|
|
4995
5740
|
);
|
|
4996
5741
|
return typeof manifest.version === "string" ? manifest.version : null;
|
|
4997
5742
|
} catch {
|
|
4998
|
-
const parent =
|
|
5743
|
+
const parent = path14.dirname(current);
|
|
4999
5744
|
if (parent === current) {
|
|
5000
5745
|
return null;
|
|
5001
5746
|
}
|
|
@@ -5024,24 +5769,39 @@ async function runAtOnce(manifest, gates, jobs, runner, executable, args) {
|
|
|
5024
5769
|
const results = /* @__PURE__ */ new Map();
|
|
5025
5770
|
let root;
|
|
5026
5771
|
try {
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5772
|
+
root = fs14.realpathSync(fs14.mkdtempSync(path14.join(os2.tmpdir(), JOBS_PREFIX)));
|
|
5773
|
+
} catch {
|
|
5774
|
+
return results;
|
|
5775
|
+
}
|
|
5776
|
+
try {
|
|
5777
|
+
for (const name of fs14.readdirSync(os2.tmpdir())) {
|
|
5778
|
+
const left = path14.join(os2.tmpdir(), name);
|
|
5779
|
+
try {
|
|
5780
|
+
if (name.startsWith(JOBS_PREFIX) && Date.now() - fs14.statSync(left).mtimeMs > 36e5) {
|
|
5781
|
+
fs14.rmSync(left, { recursive: true, force: true });
|
|
5782
|
+
}
|
|
5783
|
+
} catch {
|
|
5031
5784
|
}
|
|
5032
5785
|
}
|
|
5033
|
-
root = fs12.realpathSync(fs12.mkdtempSync(path12.join(os2.tmpdir(), JOBS_PREFIX)));
|
|
5034
5786
|
} catch {
|
|
5035
|
-
return results;
|
|
5036
5787
|
}
|
|
5037
5788
|
const queue = [...gates];
|
|
5789
|
+
const lines = Math.min(jobs, gates.length);
|
|
5790
|
+
const workers = workerFlags({
|
|
5791
|
+
runner,
|
|
5792
|
+
args,
|
|
5793
|
+
configSource: gateConfigSource(manifest, args),
|
|
5794
|
+
lines,
|
|
5795
|
+
// (os.availableParallelism is in Node 18.14 and later.)
|
|
5796
|
+
cores: os2.availableParallelism?.() ?? os2.cpus().length
|
|
5797
|
+
});
|
|
5038
5798
|
const line = async (dir) => {
|
|
5039
5799
|
for (let gate = queue.shift(); gate !== void 0; gate = queue.shift()) {
|
|
5040
5800
|
const spec = gate.spec;
|
|
5041
5801
|
results.set(
|
|
5042
5802
|
gate,
|
|
5043
5803
|
await new Promise((resolve) => {
|
|
5044
|
-
const child = spawn(executable, [...args, ...jobFlags(runner, dir)], {
|
|
5804
|
+
const child = spawn(executable, [...args, ...jobFlags(runner, dir), ...workers], {
|
|
5045
5805
|
cwd: manifest.dir,
|
|
5046
5806
|
env: { ...process.env, [EXCLUDE_ENV]: spec, [EXCLUDE_ALL_ENV]: "", [TOUCH_ENV]: "" },
|
|
5047
5807
|
shell: process.platform === "win32"
|
|
@@ -5060,13 +5820,10 @@ async function runAtOnce(manifest, gates, jobs, runner, executable, args) {
|
|
|
5060
5820
|
};
|
|
5061
5821
|
try {
|
|
5062
5822
|
await Promise.all(
|
|
5063
|
-
Array.from(
|
|
5064
|
-
{ length: Math.min(jobs, gates.length) },
|
|
5065
|
-
(_unused, index) => line(path12.join(root, `line-${index}`))
|
|
5066
|
-
)
|
|
5823
|
+
Array.from({ length: lines }, (_unused, index) => line(path14.join(root, `line-${index}`)))
|
|
5067
5824
|
);
|
|
5068
5825
|
} finally {
|
|
5069
|
-
|
|
5826
|
+
fs14.rmSync(root, { recursive: true, force: true });
|
|
5070
5827
|
}
|
|
5071
5828
|
return results;
|
|
5072
5829
|
}
|
|
@@ -5095,7 +5852,7 @@ async function runSelfcheck(dir, selection, commandOverride, io2) {
|
|
|
5095
5852
|
const command = commandOverride.length > 0 ? commandOverride : resolveGateCommand(manifest);
|
|
5096
5853
|
const [program, ...args] = command;
|
|
5097
5854
|
const executable = findLocalBin(manifest.dir, program) ?? program;
|
|
5098
|
-
const runGate = (env) =>
|
|
5855
|
+
const runGate = (env) => spawnSync6(executable, args, {
|
|
5099
5856
|
cwd: manifest.dir,
|
|
5100
5857
|
env: {
|
|
5101
5858
|
...process.env,
|
|
@@ -5173,6 +5930,11 @@ async function runSelfcheck(dir, selection, commandOverride, io2) {
|
|
|
5173
5930
|
...manifest.gates.flatMap((gate) => [gate.path, gate.spec])
|
|
5174
5931
|
].sort((left, right) => right.length - left.length);
|
|
5175
5932
|
const results = await runAtOnce(manifest, atOnce, jobs, known.runner, executable, args);
|
|
5933
|
+
if (results.size === 0) {
|
|
5934
|
+
io2.out(
|
|
5935
|
+
`test-gates selfcheck: one negative control at a time (no directory for the runs next to others could be made in ${os2.tmpdir()})`
|
|
5936
|
+
);
|
|
5937
|
+
}
|
|
5176
5938
|
for (const [gate, result] of results) {
|
|
5177
5939
|
const output = `${result.stdout}${result.stderr}`;
|
|
5178
5940
|
const clean = cleanHold({
|
|
@@ -5190,7 +5952,7 @@ async function runSelfcheck(dir, selection, commandOverride, io2) {
|
|
|
5190
5952
|
}
|
|
5191
5953
|
}
|
|
5192
5954
|
const again = atOnce.length - heldAtOnce.size;
|
|
5193
|
-
if (again > 0) {
|
|
5955
|
+
if (again > 0 && results.size > 0) {
|
|
5194
5956
|
io2.out(
|
|
5195
5957
|
`test-gates selfcheck: ${again} of ${atOnce.length} negative control(s) did not hold cleanly next to others and run again on their own`
|
|
5196
5958
|
);
|