tamperward 2.23.10 → 2.23.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +383 -169
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4890,13 +4890,7 @@ var init_hook_wiring = __esm({
|
|
|
4890
4890
|
}
|
|
4891
4891
|
});
|
|
4892
4892
|
|
|
4893
|
-
// src/detectors/
|
|
4894
|
-
function isActiveWorkflow(path) {
|
|
4895
|
-
return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
|
|
4896
|
-
}
|
|
4897
|
-
function commandCore(line) {
|
|
4898
|
-
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
|
|
4899
|
-
}
|
|
4893
|
+
// src/detectors/gh-expression.ts
|
|
4900
4894
|
function foldExpressions(s) {
|
|
4901
4895
|
return s.replace(/\$\{\{([^}]*)\}\}/g, (_m, e) => {
|
|
4902
4896
|
const v = foldConst(e.trim());
|
|
@@ -4904,90 +4898,6 @@ function foldExpressions(s) {
|
|
|
4904
4898
|
return /\b(?:matrix|strategy|inputs)\./.test(e) ? MATRIX_TOKEN : EXPR_TOKEN;
|
|
4905
4899
|
});
|
|
4906
4900
|
}
|
|
4907
|
-
function canonical(core) {
|
|
4908
|
-
let s = foldExpressions(core.replace(/^(['"])(.*)\1$/, "$2").trim()).replace(MATRIX_FLAG, " ").replace(/\s+/g, " ").trim();
|
|
4909
|
-
s = s.replace(/^(?:cd|pushd)\s+\S+\s*&&\s*/, "");
|
|
4910
|
-
s = s.replace(/^(?:npx|pnpm\s+(?:exec|dlx)|yarn\s+(?:exec|dlx)|bunx|bun\s+x)\s+/, "exec ");
|
|
4911
|
-
s = s.replace(/^(?:npm|pnpm|yarn|bun)\s+(?:run(?:-script)?\s+)?/, "exec ");
|
|
4912
|
-
s = s.replace(/^exec\s+(?:t|tst)(?=\s|$)/, "exec test");
|
|
4913
|
-
s = s.replace(/^exec\s+/, "");
|
|
4914
|
-
return s.replace(/\s+--\s*$/, "").trim();
|
|
4915
|
-
}
|
|
4916
|
-
function runnerNarrowed(cmd, args) {
|
|
4917
|
-
const t = RUNNER_NARROWINGS.find((x) => x.runner.test(cmd));
|
|
4918
|
-
return t !== void 0 && t.flags.test(" " + args.trim());
|
|
4919
|
-
}
|
|
4920
|
-
function coverageFloorLowered(removed, added) {
|
|
4921
|
-
const was = removed.match(COVERAGE_FLOOR);
|
|
4922
|
-
const now = added.match(COVERAGE_FLOOR);
|
|
4923
|
-
return was !== null && now !== null && Number(now[1]) < Number(was[1]);
|
|
4924
|
-
}
|
|
4925
|
-
function inCommandPosition(core) {
|
|
4926
|
-
return canonical(core).split(/\s*(?:&&|\|\||;|\|)\s*/).some((seg) => CHECK_HEAD.test(canonical(seg.replace(COMMAND_PREFIX, ""))));
|
|
4927
|
-
}
|
|
4928
|
-
function narrowedByPositional(args) {
|
|
4929
|
-
const toks = args.trim().split(/\s+/).filter(Boolean);
|
|
4930
|
-
for (let i = 0; i < toks.length; i++) {
|
|
4931
|
-
const t = toks[i];
|
|
4932
|
-
if (t === "--" || t.startsWith("-")) continue;
|
|
4933
|
-
const prev = toks[i - 1];
|
|
4934
|
-
if (prev && (prev.startsWith("-") && prev !== "--" && !prev.includes("=") || /[<>]/.test(prev))) continue;
|
|
4935
|
-
if (/[<>|;&]/.test(t)) continue;
|
|
4936
|
-
if (PATH_SHAPED.test(t)) return true;
|
|
4937
|
-
}
|
|
4938
|
-
return false;
|
|
4939
|
-
}
|
|
4940
|
-
function checkKind(core) {
|
|
4941
|
-
if (!invokesCheck(core)) return null;
|
|
4942
|
-
const s = canonical(core);
|
|
4943
|
-
if (/\btamperward\b/.test(s)) return "gate";
|
|
4944
|
-
if (/\b(?:lint|eslint|biome|oxlint|golangci-lint|ruff)\b/i.test(s)) return "lint";
|
|
4945
|
-
if (/\b(?:typecheck|type-check|tsc|tsgo|mypy)\b|\bdeno\s+check\b/i.test(s)) return "types";
|
|
4946
|
-
return "test";
|
|
4947
|
-
}
|
|
4948
|
-
function survives(removedCore, isUses, afterCores, addedCores) {
|
|
4949
|
-
if (isUses) {
|
|
4950
|
-
const ref = usesRef(removedCore);
|
|
4951
|
-
return afterCores.some((a) => usesRef(a) === ref) ? { state: "kept" } : { state: "gone" };
|
|
4952
|
-
}
|
|
4953
|
-
const r = canonical(removedCore);
|
|
4954
|
-
const kind = checkKind(removedCore);
|
|
4955
|
-
const narrowed = (args) => kind === "test" && narrowedByPositional(args);
|
|
4956
|
-
let neutralised;
|
|
4957
|
-
for (const raw of afterCores) {
|
|
4958
|
-
const a = canonical(raw);
|
|
4959
|
-
if (a === r) return { state: "kept" };
|
|
4960
|
-
if (a.startsWith(r + " ")) {
|
|
4961
|
-
const rest = a.slice(r.length);
|
|
4962
|
-
if (NEUTRALISING_SUFFIX.test(rest) || narrowed(rest) || runnerNarrowed(a, rest)) neutralised ??= raw;
|
|
4963
|
-
else return { state: "kept" };
|
|
4964
|
-
} else if (r.startsWith(a + " ") && invokesCheck(a)) {
|
|
4965
|
-
return { state: "kept" };
|
|
4966
|
-
} else {
|
|
4967
|
-
const w = a.match(TIMEOUT_WRAP);
|
|
4968
|
-
if (w && (canonical(w[1]) === r || canonical(w[1]).startsWith(r + " "))) neutralised ??= raw;
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
let respelled = false;
|
|
4972
|
-
for (const raw of addedCores) {
|
|
4973
|
-
if (!kind || checkKind(raw) !== kind) continue;
|
|
4974
|
-
const a = canonical(raw);
|
|
4975
|
-
if (a.match(TIMEOUT_WRAP)) {
|
|
4976
|
-
neutralised ??= raw;
|
|
4977
|
-
continue;
|
|
4978
|
-
}
|
|
4979
|
-
if (!inCommandPosition(raw)) continue;
|
|
4980
|
-
if (coverageFloorLowered(r, a)) {
|
|
4981
|
-
neutralised ??= raw;
|
|
4982
|
-
continue;
|
|
4983
|
-
}
|
|
4984
|
-
const args = a.slice(a.indexOf(" ") + 1 || a.length);
|
|
4985
|
-
if (a.includes(" ") && (NEUTRALISING_SUFFIX.test(" " + args) || narrowed(args) || runnerNarrowed(a, args))) neutralised ??= raw;
|
|
4986
|
-
else respelled = true;
|
|
4987
|
-
}
|
|
4988
|
-
if (respelled) return { state: "kept" };
|
|
4989
|
-
return neutralised ? { state: "neutralised", by: neutralised } : { state: "gone" };
|
|
4990
|
-
}
|
|
4991
4901
|
function lex(src) {
|
|
4992
4902
|
const out3 = [];
|
|
4993
4903
|
let i = 0;
|
|
@@ -5177,6 +5087,329 @@ function callConst(name, args) {
|
|
|
5177
5087
|
return void 0;
|
|
5178
5088
|
}
|
|
5179
5089
|
}
|
|
5090
|
+
var EXPR_TOKEN, MATRIX_TOKEN, TRUTHY, truthy;
|
|
5091
|
+
var init_gh_expression = __esm({
|
|
5092
|
+
"src/detectors/gh-expression.ts"() {
|
|
5093
|
+
"use strict";
|
|
5094
|
+
EXPR_TOKEN = "__expr__";
|
|
5095
|
+
MATRIX_TOKEN = "__matrix__";
|
|
5096
|
+
TRUTHY = Symbol("truthy");
|
|
5097
|
+
truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
|
|
5098
|
+
}
|
|
5099
|
+
});
|
|
5100
|
+
|
|
5101
|
+
// src/detectors/invocation.ts
|
|
5102
|
+
function canonical(core) {
|
|
5103
|
+
let s = foldExpressions(core.replace(/^(['"])(.*)\1$/, "$2").trim()).replace(MATRIX_FLAG, " ").replace(/\s+/g, " ").trim();
|
|
5104
|
+
s = s.replace(/^(?:cd|pushd)\s+\S+\s*&&\s*/, "");
|
|
5105
|
+
s = s.replace(/^(?:npx|pnpm\s+(?:exec|dlx)|yarn\s+(?:exec|dlx)|bunx|bun\s+x)\s+/, "exec ");
|
|
5106
|
+
s = s.replace(/^(?:npm|pnpm|yarn|bun)\s+(?:run(?:-script)?\s+)?/, "exec ");
|
|
5107
|
+
s = s.replace(/^exec\s+(?:t|tst)(?=\s|$)/, "exec test");
|
|
5108
|
+
s = s.replace(/^exec\s+/, "");
|
|
5109
|
+
return s.replace(/\s+--\s*$/, "").trim();
|
|
5110
|
+
}
|
|
5111
|
+
function runnerNarrowed(cmd, args) {
|
|
5112
|
+
return runnerNarrowing(cmd, args) !== null;
|
|
5113
|
+
}
|
|
5114
|
+
function runnerNarrowing(cmd, args) {
|
|
5115
|
+
const t = RUNNER_NARROWINGS.find((x) => x.runner.test(cmd));
|
|
5116
|
+
const m = t ? (" " + args.trim()).match(t.flags) : null;
|
|
5117
|
+
return m ? m[0].trim().replace(/[=\s].*$/, "") : null;
|
|
5118
|
+
}
|
|
5119
|
+
function coverageFloorLowered(removed, added) {
|
|
5120
|
+
const was = removed.match(COVERAGE_FLOOR);
|
|
5121
|
+
const now = added.match(COVERAGE_FLOOR);
|
|
5122
|
+
return was !== null && now !== null && Number(now[1]) < Number(was[1]);
|
|
5123
|
+
}
|
|
5124
|
+
function inCommandPosition(core) {
|
|
5125
|
+
return canonical(core).split(/\s*(?:&&|\|\||;|\|)\s*/).some((seg) => CHECK_HEAD.test(canonical(seg.replace(COMMAND_PREFIX, ""))));
|
|
5126
|
+
}
|
|
5127
|
+
function narrowedByPositional(args) {
|
|
5128
|
+
return positionalOf(args) !== null;
|
|
5129
|
+
}
|
|
5130
|
+
function positionalOf(args) {
|
|
5131
|
+
const toks = args.trim().split(/\s+/).filter(Boolean);
|
|
5132
|
+
for (let i = 0; i < toks.length; i++) {
|
|
5133
|
+
const t = toks[i];
|
|
5134
|
+
if (t === "--" || t.startsWith("-")) continue;
|
|
5135
|
+
const prev = toks[i - 1];
|
|
5136
|
+
if (prev && (prev.startsWith("-") && prev !== "--" && !prev.includes("=") || /[<>]/.test(prev))) continue;
|
|
5137
|
+
if (/[<>|;&]/.test(t)) continue;
|
|
5138
|
+
if (PATH_SHAPED.test(t)) return t;
|
|
5139
|
+
}
|
|
5140
|
+
return null;
|
|
5141
|
+
}
|
|
5142
|
+
function checkKind(core) {
|
|
5143
|
+
if (!invokesCheck(core)) return null;
|
|
5144
|
+
const s = canonical(core);
|
|
5145
|
+
if (/\btamperward\b/.test(s)) return "gate";
|
|
5146
|
+
if (/\b(?:lint|eslint|biome|oxlint|golangci-lint|ruff)\b/i.test(s)) return "lint";
|
|
5147
|
+
if (/\b(?:typecheck|type-check|tsc|tsgo|mypy)\b|\bdeno\s+check\b/i.test(s)) return "types";
|
|
5148
|
+
return "test";
|
|
5149
|
+
}
|
|
5150
|
+
function survives(removedCore, isUses, afterCores, addedCores) {
|
|
5151
|
+
if (isUses) {
|
|
5152
|
+
const ref = usesRef(removedCore);
|
|
5153
|
+
return afterCores.some((a) => usesRef(a) === ref) ? { state: "kept" } : { state: "gone" };
|
|
5154
|
+
}
|
|
5155
|
+
const r = canonical(removedCore);
|
|
5156
|
+
const kind = checkKind(removedCore);
|
|
5157
|
+
const narrowed = (args) => kind === "test" && narrowedByPositional(args);
|
|
5158
|
+
let neutralised;
|
|
5159
|
+
for (const raw of afterCores) {
|
|
5160
|
+
const a = canonical(raw);
|
|
5161
|
+
if (a === r) return { state: "kept" };
|
|
5162
|
+
if (a.startsWith(r + " ")) {
|
|
5163
|
+
const rest = a.slice(r.length);
|
|
5164
|
+
if (NEUTRALISING_SUFFIX.test(rest) || narrowed(rest) || runnerNarrowed(a, rest)) neutralised ??= raw;
|
|
5165
|
+
else return { state: "kept" };
|
|
5166
|
+
} else if (r.startsWith(a + " ") && invokesCheck(a)) {
|
|
5167
|
+
return { state: "kept" };
|
|
5168
|
+
} else {
|
|
5169
|
+
const w = a.match(TIMEOUT_WRAP);
|
|
5170
|
+
if (w && (canonical(w[1]) === r || canonical(w[1]).startsWith(r + " "))) neutralised ??= raw;
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
let respelled = false;
|
|
5174
|
+
for (const raw of addedCores) {
|
|
5175
|
+
if (!kind || checkKind(raw) !== kind) continue;
|
|
5176
|
+
const a = canonical(raw);
|
|
5177
|
+
if (a.match(TIMEOUT_WRAP)) {
|
|
5178
|
+
neutralised ??= raw;
|
|
5179
|
+
continue;
|
|
5180
|
+
}
|
|
5181
|
+
if (!inCommandPosition(raw)) continue;
|
|
5182
|
+
if (coverageFloorLowered(r, a)) {
|
|
5183
|
+
neutralised ??= raw;
|
|
5184
|
+
continue;
|
|
5185
|
+
}
|
|
5186
|
+
const args = a.slice(a.indexOf(" ") + 1 || a.length);
|
|
5187
|
+
if (a.includes(" ") && (NEUTRALISING_SUFFIX.test(" " + args) || narrowed(args) || runnerNarrowed(a, args))) neutralised ??= raw;
|
|
5188
|
+
else respelled = true;
|
|
5189
|
+
}
|
|
5190
|
+
if (respelled) return { state: "kept" };
|
|
5191
|
+
return neutralised ? { state: "neutralised", by: neutralised } : { state: "gone" };
|
|
5192
|
+
}
|
|
5193
|
+
function unwrap(seg) {
|
|
5194
|
+
const s = seg.trim().replace(PYTHON_M, "").replace(NODE_BIN, "$1").replace(BIN_OF, "$1$2");
|
|
5195
|
+
if (invokesCheck(s) || PRINTER.test(s) || /^["']/.test(s)) return s;
|
|
5196
|
+
const toks = s.split(/\s+/);
|
|
5197
|
+
for (let i = 1; i < toks.length && i <= 16; i++) {
|
|
5198
|
+
const rest = toks.slice(i).join(" ");
|
|
5199
|
+
if (invokesCheck(rest)) return rest;
|
|
5200
|
+
}
|
|
5201
|
+
return s;
|
|
5202
|
+
}
|
|
5203
|
+
function scriptKind3(inner) {
|
|
5204
|
+
const kind = checkKind(inner);
|
|
5205
|
+
if (!kind) return null;
|
|
5206
|
+
const head = canonical(inner).match(/^(tests?|lint|type-?check|coverage)(?=[\s:]|$)/i)?.[1];
|
|
5207
|
+
if (!head) return kind;
|
|
5208
|
+
return /^type/i.test(head) ? "types" : /^lint/i.test(head) ? "lint" : "test";
|
|
5209
|
+
}
|
|
5210
|
+
function runnerOfSegment(canon) {
|
|
5211
|
+
const m = canon.match(RUNNER_NAME);
|
|
5212
|
+
if (!m) return null;
|
|
5213
|
+
const n = m[1];
|
|
5214
|
+
return n === "jest" || n === "vitest" || n === "mocha" || n === "pytest" || n === "go" || n === "cargo" ? n : "node";
|
|
5215
|
+
}
|
|
5216
|
+
function statements(raw) {
|
|
5217
|
+
const out3 = [];
|
|
5218
|
+
let single = false;
|
|
5219
|
+
let double = false;
|
|
5220
|
+
let last = 0;
|
|
5221
|
+
for (let i = 0; i < raw.length; i++) {
|
|
5222
|
+
const ch = raw[i];
|
|
5223
|
+
if (ch === "\\" && !single) {
|
|
5224
|
+
i++;
|
|
5225
|
+
continue;
|
|
5226
|
+
}
|
|
5227
|
+
if (ch === "'" && !double) {
|
|
5228
|
+
single = !single;
|
|
5229
|
+
continue;
|
|
5230
|
+
}
|
|
5231
|
+
if (ch === '"' && !single) {
|
|
5232
|
+
double = !double;
|
|
5233
|
+
continue;
|
|
5234
|
+
}
|
|
5235
|
+
if (single || double) continue;
|
|
5236
|
+
const prev = raw[i - 1];
|
|
5237
|
+
const next = raw[i + 1];
|
|
5238
|
+
let op = null;
|
|
5239
|
+
if (ch === "&" && next === "&") op = "&&";
|
|
5240
|
+
else if (ch === "|" && next === "|") op = "||";
|
|
5241
|
+
else if (ch === "|" && next === "&") op = "|&";
|
|
5242
|
+
else if (ch === ";" || ch === "\n") op = ";";
|
|
5243
|
+
else if (ch === "|" && prev !== ">") op = "|";
|
|
5244
|
+
else if (ch === "&" && prev !== ">" && next !== ">") op = "&";
|
|
5245
|
+
if (!op) continue;
|
|
5246
|
+
out3.push({ seg: raw.slice(last, i).trim(), op });
|
|
5247
|
+
i += op.length - 1;
|
|
5248
|
+
last = i + 1;
|
|
5249
|
+
}
|
|
5250
|
+
out3.push({ seg: raw.slice(last).trim(), op: null });
|
|
5251
|
+
return out3.filter((s) => s.seg !== "");
|
|
5252
|
+
}
|
|
5253
|
+
function descriptors(cmd, runner, opts) {
|
|
5254
|
+
const out3 = [];
|
|
5255
|
+
const stmts = statements(cmd);
|
|
5256
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
5257
|
+
const { seg, op } = stmts[i];
|
|
5258
|
+
const wrapped = seg.match(TIMEOUT_WRAP);
|
|
5259
|
+
const inner = unwrap(wrapped ? wrapped[1] : seg);
|
|
5260
|
+
if (!invokesCheck(inner)) continue;
|
|
5261
|
+
const kind = scriptKind3(inner);
|
|
5262
|
+
const canon = canonical(inner);
|
|
5263
|
+
const rn = runner ?? runnerOfSegment(canon);
|
|
5264
|
+
const args = canon.includes(" ") ? " " + canon.slice(canon.indexOf(" ") + 1) : " ";
|
|
5265
|
+
if (wrapped) out3.push({ key: "timeout", what: "a timeout wrapper", direction: "added" });
|
|
5266
|
+
const following = stmts[i + 1]?.seg ?? "";
|
|
5267
|
+
if (op === "||" || op === "|" || op === "&" || op === ";" && !invokesCheck(unwrap(following))) {
|
|
5268
|
+
out3.push({ key: `op:${op}`, what: `${op} ${following.split(/\s+/).slice(0, 2).join(" ")}`.trim(), direction: "added" });
|
|
5269
|
+
}
|
|
5270
|
+
for (const re of MASKING_FLAGS) {
|
|
5271
|
+
const f = spelled(re, args);
|
|
5272
|
+
if (f) out3.push({ key: `flag:${f}`, what: f, direction: "added" });
|
|
5273
|
+
}
|
|
5274
|
+
for (const re of GATE_FLAGS) {
|
|
5275
|
+
const f = spelled(re, " " + seg);
|
|
5276
|
+
if (f) out3.push({ key: `gate:${f}`, what: f, direction: "removed" });
|
|
5277
|
+
}
|
|
5278
|
+
if (kind !== "test" || opts.sliceByDesign || opts.extended?.has(kind)) continue;
|
|
5279
|
+
const flags = [SUITE_NARROWING_FLAGS, ...NARROWING_BY_RUNNER["*"], ...rn ? NARROWING_BY_RUNNER[rn] : []];
|
|
5280
|
+
for (const re of flags) {
|
|
5281
|
+
const f = spelled(re, args);
|
|
5282
|
+
if (f && !opts.excuseFlag?.(f, canon)) out3.push({ key: `flag:${f}`, what: f, direction: "added" });
|
|
5283
|
+
}
|
|
5284
|
+
if (rn === "cargo" && CARGO_SKIP.test(args)) out3.push({ key: "flag:-- --skip", what: "-- --skip", direction: "added" });
|
|
5285
|
+
const shared = runnerNarrowing(canon, args);
|
|
5286
|
+
if (shared && !opts.excuseFlag?.(shared, canon)) out3.push({ key: `flag:${shared}`, what: shared, direction: "added" });
|
|
5287
|
+
if (opts.fresh) continue;
|
|
5288
|
+
const cfg = args.match(CONFIG_FLAG);
|
|
5289
|
+
if (cfg && !opts.configReviewed?.(cfg[3])) out3.push({ key: `config:${cfg[3]}`, what: `${cfg[1]} ${cfg[3]}`, direction: "added" });
|
|
5290
|
+
const pos = positionalOf(args);
|
|
5291
|
+
if (pos) out3.push({ key: "positional", what: `a path positional (${pos})`, direction: "added" });
|
|
5292
|
+
}
|
|
5293
|
+
return out3;
|
|
5294
|
+
}
|
|
5295
|
+
function checkStatements(cmd) {
|
|
5296
|
+
const out3 = [];
|
|
5297
|
+
for (const seg of segments(cmd)) {
|
|
5298
|
+
const w = seg.match(TIMEOUT_WRAP);
|
|
5299
|
+
const inner = unwrap(w ? w[1] : seg);
|
|
5300
|
+
const kind = scriptKind3(inner);
|
|
5301
|
+
if (kind) out3.push({ kind, canon: canonical(inner) });
|
|
5302
|
+
}
|
|
5303
|
+
return out3;
|
|
5304
|
+
}
|
|
5305
|
+
function checkKinds(cmd) {
|
|
5306
|
+
return new Set(checkStatements(cmd).map((s) => s.kind));
|
|
5307
|
+
}
|
|
5308
|
+
function failsLoudly(cmd) {
|
|
5309
|
+
const last = segments(cmd).pop();
|
|
5310
|
+
return last !== void 0 && FAILS_LOUDLY.test(last.trim());
|
|
5311
|
+
}
|
|
5312
|
+
function invocationWeakening(before, after, runner, opts = {}) {
|
|
5313
|
+
const was = checkKinds(before);
|
|
5314
|
+
const now = checkKinds(after);
|
|
5315
|
+
if (now.size === 0 && failsLoudly(after)) return { state: "kept" };
|
|
5316
|
+
for (const k of was) {
|
|
5317
|
+
if (now.has(k) || opts.relocated?.(k)) continue;
|
|
5318
|
+
const instead = [...now].filter((x) => !was.has(x));
|
|
5319
|
+
return { state: "removed", kind: k, what: instead.length ? `runs a ${instead.join("/")} check instead` : now.size ? "the check is gone" : "no check is run" };
|
|
5320
|
+
}
|
|
5321
|
+
const survived = new Set(checkStatements(before).map((s) => s.canon));
|
|
5322
|
+
const extended = new Set(checkStatements(after).filter((s) => survived.has(s.canon)).map((s) => s.kind));
|
|
5323
|
+
const read = { ...opts, fresh: was.size === 0, extended };
|
|
5324
|
+
const beforeDs = descriptors(before, runner, read);
|
|
5325
|
+
const afterDs = descriptors(after, runner, read);
|
|
5326
|
+
const seenBefore = new Set(beforeDs.map((d) => d.key));
|
|
5327
|
+
const added = afterDs.find((d) => d.direction === "added" && !seenBefore.has(d.key));
|
|
5328
|
+
if (added) return { state: "neutralised", what: added.what, direction: "added" };
|
|
5329
|
+
if (coverageFloorLowered(before, after)) return { state: "neutralised", what: "a lowered --cov-fail-under", direction: "added" };
|
|
5330
|
+
const seenAfter = new Set(afterDs.map((d) => d.key));
|
|
5331
|
+
const dropped = beforeDs.find((d) => d.direction === "removed" && !seenAfter.has(d.key));
|
|
5332
|
+
if (dropped) return { state: "neutralised", what: dropped.what, direction: "removed" };
|
|
5333
|
+
return { state: "kept" };
|
|
5334
|
+
}
|
|
5335
|
+
var INVOKES_TOOL, INVOKES_SCRIPT, invokesCheck, MATRIX_FLAG, usesRef, SUITE_NARROWING_FLAGS, NEUTRALISING_SUFFIX, TIMEOUT_WRAP, RUNNER_NARROWINGS, COVERAGE_FLOOR, COMMAND_PREFIX, CHECK_HEAD, PATH_SHAPED, NARROWING_BY_RUNNER, CARGO_SKIP, MASKING_FLAGS, GATE_FLAGS, CONFIG_FLAG, PRINTER, PYTHON_M, NODE_BIN, BIN_OF, RUNNER_NAME, spelled, FAILS_LOUDLY;
|
|
5336
|
+
var init_invocation = __esm({
|
|
5337
|
+
"src/detectors/invocation.ts"() {
|
|
5338
|
+
"use strict";
|
|
5339
|
+
init_gh_expression();
|
|
5340
|
+
init_command();
|
|
5341
|
+
INVOKES_TOOL = /(?:^\s*|[;&|`]\s*|\$\(\s*|\b(?:npx|yarn|pnpm|bunx?)\s+)(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward|mocha|ava|oxlint|tsgo|mypy|golangci-lint|node\s+--test|biome\s+(?:ci|check|lint)|deno\s+(?:test|lint|check)|ruff\s+check)\b/;
|
|
5342
|
+
INVOKES_SCRIPT = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|tests|lint|typecheck|type-check|coverage)\b|\b(?:make|cargo|go)\s+test\b|\bgradle\w*\s+(?:test|check)\b/;
|
|
5343
|
+
invokesCheck = (line) => INVOKES_TOOL.test(line) || INVOKES_SCRIPT.test(line);
|
|
5344
|
+
MATRIX_FLAG = new RegExp(`(?:^|\\s)-{1,2}[\\w-]+(?:=|\\s+)${MATRIX_TOKEN}(?:/\\S*)?(?=\\s|$)`, "g");
|
|
5345
|
+
usesRef = (core) => core.replace(/@.*$/, "");
|
|
5346
|
+
SUITE_NARROWING_FLAGS = /--testPathIgnorePatterns\b|--testPathPattern\b|--testNamePattern\b|(?:^|\s)-t\s|--onlyChanged\b|--changed\b|--findRelatedTests\b|--exclude\b|--dir\b|--project\b/;
|
|
5347
|
+
NEUTRALISING_SUFFIX = new RegExp(
|
|
5348
|
+
`\\|\\||;|(?<!&)&(?!&)|\\|(?!\\|)|--passWithNoTests\\b|--coverage=false\\b|--coverageThreshold\\b|${SUITE_NARROWING_FLAGS.source}`
|
|
5349
|
+
);
|
|
5350
|
+
TIMEOUT_WRAP = /^timeout\s+(?:-{1,2}\S+\s+)*\d\S*\s+(.+)$/;
|
|
5351
|
+
RUNNER_NARROWINGS = [
|
|
5352
|
+
{
|
|
5353
|
+
// a package-manager script: `test --prefix dir`, `-w pkg`, `--workspace=pkg`,
|
|
5354
|
+
// `--if-present` (no script, no failure), pnpm's `--filter` / `-F`, npm's `-C`
|
|
5355
|
+
runner: /^(?:test|tests|coverage)(?::\S+)?(?=\s|$)/,
|
|
5356
|
+
flags: /\s--if-present(?=\s|$)|\s--prefix(?:=|\s)|\s-C(?:=|\s)|\s-w(?:=|\s)|\s--workspace(?:=|\s)|\s--filter(?:=|\s)|\s-F(?:=|\s)/
|
|
5357
|
+
},
|
|
5358
|
+
{
|
|
5359
|
+
runner: /^(?:jest|vitest)\b/,
|
|
5360
|
+
flags: /\s--shard(?:=|\s)|\s--root(?:Dir)?(?:=|\s)|\s--testMatch(?:=|\s)|\s--testRegex(?:=|\s)|\s--modulePathIgnorePatterns(?:=|\s)|\s--selectProjects(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)/
|
|
5361
|
+
},
|
|
5362
|
+
{ runner: /^playwright\b/, flags: /\s--shard(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--grep-invert(?:=|\s)/ },
|
|
5363
|
+
{ runner: /^mocha\b/, flags: /\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--fgrep(?:=|\s)|\s-f(?:=|\s)/ },
|
|
5364
|
+
{ runner: /^ava\b/, flags: /\s--match(?:=|\s)|\s-m(?:=|\s)/ },
|
|
5365
|
+
{ runner: /^node --test\b/, flags: /\s--test-name-pattern(?:=|\s)|\s--test-skip-pattern(?:=|\s)|\s--test-only(?=\s|$)/ },
|
|
5366
|
+
{
|
|
5367
|
+
// `-p no:<plugin>` disables collection when the plugin is the collector
|
|
5368
|
+
// (`python`, `doctest`, `cov`); the cache, warning and output plugins select nothing
|
|
5369
|
+
runner: /^pytest\b/,
|
|
5370
|
+
flags: /\s-k(?:=|\s)|\s-m(?:=|\s)|\s--deselect(?:=|\s)|\s--ignore(?:-glob)?(?:=|\s)|\s-p\s*no:(?!(?:cacheprovider|warnings|faulthandler|randomly|logging|legacypath|pastebin|stepwise|tmpdir|junitxml|terminal|terminalreporter)(?=\s|$))|\s--collect-only(?=\s|$)|\s--co(?=\s|$)/
|
|
5371
|
+
},
|
|
5372
|
+
{ runner: /^cargo test\b/, flags: /\s--\s(?:\S+\s+)*--skip(?:=|\s)|\s--exclude(?:=|\s)/ },
|
|
5373
|
+
{ runner: /^go test\b/, flags: /\s-run(?:=|\s)|\s-skip(?:=|\s)/ }
|
|
5374
|
+
];
|
|
5375
|
+
COVERAGE_FLOOR = /--cov-fail-under(?:=|\s+)(\d+(?:\.\d+)?)/;
|
|
5376
|
+
COMMAND_PREFIX = /^(?:(?:[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S*)|sudo|env|nice|time|exec|command|nohup|xvfb-run|dbus-launch|cross-env|dotenv|poetry\s+run|pipenv\s+run|uv\s+run|hatch\s+run|pdm\s+run|bundle\s+exec)\s+(?:-{1,2}\S+\s+)*)*/;
|
|
5377
|
+
CHECK_HEAD = /^(?:(?:test|tests|lint|typecheck|type-check|coverage)(?::\S+)?(?=\s|$)|(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward|mocha|ava|oxlint|tsgo|mypy|golangci-lint)\b|node --test\b|biome (?:ci|check|lint)\b|deno (?:test|lint|check)\b|ruff check\b|(?:make|cargo|go) test\b|gradle\w* (?:test|check)\b)/;
|
|
5378
|
+
PATH_SHAPED = /\/|\.(?:test|spec)\./;
|
|
5379
|
+
NARROWING_BY_RUNNER = {
|
|
5380
|
+
"*": [/--shard\b/],
|
|
5381
|
+
jest: [/--root\b/, /--rootDir\b/, /--testMatch\b/, /--testRegex\b/, /--modulePathIgnorePatterns\b/, /--selectProjects\b/, /--ignoreProjects\b/, /--filter\b/],
|
|
5382
|
+
vitest: [/--grep\b/],
|
|
5383
|
+
mocha: [/--grep\b/, /(?:^|\s)-g(?:\s|=)/, /--ignore\b/, /(?:^|\s)-f(?:\s|=)/, /--fgrep\b/],
|
|
5384
|
+
pytest: [/(?:^|\s)-k(?:\s|=)/, /(?:^|\s)-m(?:\s|=)/, /--deselect\b/, /--ignore\b/, /--ignore-glob\b/, /--last-failed\b/, /(?:^|\s)--lf(?:\s|$)/, /--collect-only\b/, /(?:^|\s)--co(?:\s|$)/],
|
|
5385
|
+
go: [/(?:^|\s)-run(?:\s|=)/, /(?:^|\s)-skip(?:\s|=)/],
|
|
5386
|
+
cargo: [],
|
|
5387
|
+
node: [/--test-name-pattern\b/, /--test-skip-pattern\b/]
|
|
5388
|
+
};
|
|
5389
|
+
CARGO_SKIP = /\s--\s+(?:\S+\s+)*--skip\b/;
|
|
5390
|
+
MASKING_FLAGS = [/--passWithNoTests\b/, /--coverage=false\b/, /--coverageThreshold\b/, /--if-present\b/];
|
|
5391
|
+
GATE_FLAGS = [/--check-coverage\b/];
|
|
5392
|
+
CONFIG_FLAG = /(?:^|\s)(--config|-c)(?:=|\s+)(['"]?)([^\s'"]+)\2(?=\s|$)/;
|
|
5393
|
+
PRINTER = /^(?:echo|printf|true|false|:|cat|exit|return|sleep|test|\[|node|git|curl|wget)(?=\s|$)/;
|
|
5394
|
+
PYTHON_M = /^python\d?(?:\.\d+)?\s+-m\s+(?=pytest\b)/;
|
|
5395
|
+
NODE_BIN = /^node(?:\s+-\S+)*\s+\S*node_modules\/(?:\.bin\/)?(jest|vitest|mocha|ava)\b\S*/;
|
|
5396
|
+
BIN_OF = /\$\((?:yarn|npm|pnpm)\s+bin\s+(\w+)\)|\$\((?:yarn|npm|pnpm)\s+bin\)\/(\w+)/g;
|
|
5397
|
+
RUNNER_NAME = /^(jest|vitest|mocha|pytest|go|cargo|node)\b/;
|
|
5398
|
+
spelled = (re, s) => {
|
|
5399
|
+
const m = s.match(re);
|
|
5400
|
+
return m ? m[0].trim().replace(/[=\s].*$/, "") : null;
|
|
5401
|
+
};
|
|
5402
|
+
FAILS_LOUDLY = /^(?:exit\s+(?:[1-9]\d*)|false)\s*$/;
|
|
5403
|
+
}
|
|
5404
|
+
});
|
|
5405
|
+
|
|
5406
|
+
// src/detectors/ci-tampering.ts
|
|
5407
|
+
function isActiveWorkflow(path) {
|
|
5408
|
+
return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
|
|
5409
|
+
}
|
|
5410
|
+
function commandCore(line) {
|
|
5411
|
+
return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
|
|
5412
|
+
}
|
|
5180
5413
|
function expressionOf(raw) {
|
|
5181
5414
|
const v = uncommented(raw).replace(/^(['"])(.*)\1$/, "$2").trim();
|
|
5182
5415
|
const m = v.match(/^\$\{\{\s*(.+?)\s*\}\}$/);
|
|
@@ -5425,7 +5658,7 @@ function ancestorOf(lines, i) {
|
|
|
5425
5658
|
while (h >= 0 && (lines[h].trim() === "" || /^\s*#/.test(lines[h]) || indentOf(lines[h]) >= ind)) h--;
|
|
5426
5659
|
return h >= 0 ? h : null;
|
|
5427
5660
|
}
|
|
5428
|
-
var RULE6, USES, CHECK, YAML_KEY,
|
|
5661
|
+
var RULE6, USES, CHECK, YAML_KEY, invokesCheck2, uncommented, SHELL_ERREXIT, indentOf, STEP_START, REPORTER_ACTION, usesCheck, stepHasCheck, jobHasCheck, NEUTRALISERS, LIST_KEYS, DEFAULT_BRANCH, ALL, CODE_EVENTS, PR_CODE_TYPES, pathsMatchSource, ignoresEverySource, CODE_PROBES, REUSED_WORKFLOW, MANIFEST, WORKDIR_LINE, REF_LINE, CHECKOUT, ciTampering;
|
|
5429
5662
|
var init_ci_tampering = __esm({
|
|
5430
5663
|
"src/detectors/ci-tampering.ts"() {
|
|
5431
5664
|
"use strict";
|
|
@@ -5435,60 +5668,22 @@ var init_ci_tampering = __esm({
|
|
|
5435
5668
|
init_files();
|
|
5436
5669
|
init_hook_wiring();
|
|
5437
5670
|
init_repo();
|
|
5671
|
+
init_gh_expression();
|
|
5672
|
+
init_invocation();
|
|
5673
|
+
init_invocation();
|
|
5438
5674
|
RULE6 = "ci-tampering";
|
|
5439
5675
|
USES = /^\s*-\s*uses:/;
|
|
5440
5676
|
CHECK = /\b(test|tests|lint|typecheck|type-check|tsc|eslint|jest|vitest|playwright|coverage|tamperward)\b/i;
|
|
5441
5677
|
YAML_KEY = /^\s*-?\s*[A-Za-z_][\w-]*:\s*(?:$|\S)/;
|
|
5442
|
-
|
|
5443
|
-
INVOKES_SCRIPT = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|tests|lint|typecheck|type-check|coverage)\b|\b(?:make|cargo|go)\s+test\b|\bgradle\w*\s+(?:test|check)\b/;
|
|
5444
|
-
invokesCheck = (line) => INVOKES_TOOL.test(line) || INVOKES_SCRIPT.test(line) || INVOKES_TOOL.test(commandCore(line));
|
|
5445
|
-
EXPR_TOKEN = "__expr__";
|
|
5446
|
-
MATRIX_TOKEN = "__matrix__";
|
|
5447
|
-
MATRIX_FLAG = new RegExp(`(?:^|\\s)-{1,2}[\\w-]+(?:=|\\s+)${MATRIX_TOKEN}(?:/\\S*)?(?=\\s|$)`, "g");
|
|
5448
|
-
usesRef = (core) => core.replace(/@.*$/, "");
|
|
5449
|
-
SUITE_NARROWING_FLAGS = /--testPathIgnorePatterns\b|--testPathPattern\b|--testNamePattern\b|(?:^|\s)-t\s|--onlyChanged\b|--changed\b|--findRelatedTests\b|--exclude\b|--dir\b|--project\b/;
|
|
5450
|
-
NEUTRALISING_SUFFIX = new RegExp(
|
|
5451
|
-
`\\|\\||;|(?<!&)&(?!&)|\\|(?!\\|)|--passWithNoTests\\b|--coverage=false\\b|--coverageThreshold\\b|${SUITE_NARROWING_FLAGS.source}`
|
|
5452
|
-
);
|
|
5453
|
-
TIMEOUT_WRAP = /^timeout\s+(?:-{1,2}\S+\s+)*\d\S*\s+(.+)$/;
|
|
5454
|
-
RUNNER_NARROWINGS = [
|
|
5455
|
-
{
|
|
5456
|
-
// a package-manager script: `test --prefix dir`, `-w pkg`, `--workspace=pkg`,
|
|
5457
|
-
// `--if-present` (no script, no failure), pnpm's `--filter` / `-F`, npm's `-C`
|
|
5458
|
-
runner: /^(?:test|tests|coverage)(?::\S+)?(?=\s|$)/,
|
|
5459
|
-
flags: /\s--if-present(?=\s|$)|\s--prefix(?:=|\s)|\s-C(?:=|\s)|\s-w(?:=|\s)|\s--workspace(?:=|\s)|\s--filter(?:=|\s)|\s-F(?:=|\s)/
|
|
5460
|
-
},
|
|
5461
|
-
{
|
|
5462
|
-
runner: /^(?:jest|vitest)\b/,
|
|
5463
|
-
flags: /\s--shard(?:=|\s)|\s--root(?:Dir)?(?:=|\s)|\s--testMatch(?:=|\s)|\s--testRegex(?:=|\s)|\s--modulePathIgnorePatterns(?:=|\s)|\s--selectProjects(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)/
|
|
5464
|
-
},
|
|
5465
|
-
{ runner: /^playwright\b/, flags: /\s--shard(?:=|\s)|\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--grep-invert(?:=|\s)/ },
|
|
5466
|
-
{ runner: /^mocha\b/, flags: /\s--grep(?:=|\s)|\s-g(?:=|\s)|\s--fgrep(?:=|\s)|\s-f(?:=|\s)/ },
|
|
5467
|
-
{ runner: /^ava\b/, flags: /\s--match(?:=|\s)|\s-m(?:=|\s)/ },
|
|
5468
|
-
{ runner: /^node --test\b/, flags: /\s--test-name-pattern(?:=|\s)|\s--test-skip-pattern(?:=|\s)|\s--test-only(?=\s|$)/ },
|
|
5469
|
-
{
|
|
5470
|
-
// `-p no:<plugin>` disables collection when the plugin is the collector
|
|
5471
|
-
// (`python`, `doctest`, `cov`); the cache, warning and output plugins select nothing
|
|
5472
|
-
runner: /^pytest\b/,
|
|
5473
|
-
flags: /\s-k(?:=|\s)|\s-m(?:=|\s)|\s--deselect(?:=|\s)|\s--ignore(?:-glob)?(?:=|\s)|\s-p\s*no:(?!(?:cacheprovider|warnings|faulthandler|randomly|logging|legacypath|pastebin|stepwise|tmpdir|junitxml|terminal|terminalreporter)(?=\s|$))|\s--collect-only(?=\s|$)|\s--co(?=\s|$)/
|
|
5474
|
-
},
|
|
5475
|
-
{ runner: /^cargo test\b/, flags: /\s--\s(?:\S+\s+)*--skip(?:=|\s)|\s--exclude(?:=|\s)/ },
|
|
5476
|
-
{ runner: /^go test\b/, flags: /\s-run(?:=|\s)|\s-skip(?:=|\s)/ }
|
|
5477
|
-
];
|
|
5478
|
-
COVERAGE_FLOOR = /--cov-fail-under(?:=|\s+)(\d+(?:\.\d+)?)/;
|
|
5479
|
-
COMMAND_PREFIX = /^(?:(?:[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S*)|sudo|env|nice|time|exec|command|nohup|xvfb-run|dbus-launch|cross-env|dotenv|poetry\s+run|pipenv\s+run|uv\s+run|hatch\s+run|pdm\s+run|bundle\s+exec)\s+(?:-{1,2}\S+\s+)*)*/;
|
|
5480
|
-
CHECK_HEAD = /^(?:(?:test|tests|lint|typecheck|type-check|coverage)(?::\S+)?(?=\s|$)|(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward|mocha|ava|oxlint|tsgo|mypy|golangci-lint)\b|node --test\b|biome (?:ci|check|lint)\b|deno (?:test|lint|check)\b|ruff check\b|(?:make|cargo|go) test\b|gradle\w* (?:test|check)\b)/;
|
|
5481
|
-
PATH_SHAPED = /\/|\.(?:test|spec)\./;
|
|
5678
|
+
invokesCheck2 = (line) => invokesCheck(line) || INVOKES_TOOL.test(commandCore(line));
|
|
5482
5679
|
uncommented = (v) => v.replace(/\s+#.*$/, "").trim();
|
|
5483
|
-
TRUTHY = Symbol("truthy");
|
|
5484
|
-
truthy = (v) => v === TRUTHY ? true : typeof v === "string" ? v !== "" : typeof v === "number" ? v !== 0 : v === true;
|
|
5485
5680
|
SHELL_ERREXIT = /(?:^|\s)-[a-zA-Z]*e[a-zA-Z]*(?:\s|$)|-o\s+errexit\b/;
|
|
5486
5681
|
indentOf = (l) => l.length - l.trimStart().length;
|
|
5487
5682
|
STEP_START = /^\s*-\s+(?:name|uses|run|if|id|with|env|shell|continue-on-error|working-directory|timeout-minutes):/;
|
|
5488
5683
|
REPORTER_ACTION = /^\s*-?\s*uses:\s*\S*?(?:[\w-]*-reporter|[\w-]*-results?[\w-]*|upload-[\w-]*|codecov[\w-]*|coveralls[\w-]*)(?:@|\s|$)/i;
|
|
5489
5684
|
usesCheck = (l) => USES.test(l) && CHECK.test(l) && !REPORTER_ACTION.test(l);
|
|
5490
|
-
stepHasCheck = (lines, range) => range === null || lines.slice(range[0], range[1]).some((l) =>
|
|
5491
|
-
jobHasCheck = (lines, range) => lines.slice(range[0], range[1]).some((l) =>
|
|
5685
|
+
stepHasCheck = (lines, range) => range === null || lines.slice(range[0], range[1]).some((l) => invokesCheck2(l) || usesCheck(l));
|
|
5686
|
+
jobHasCheck = (lines, range) => lines.slice(range[0], range[1]).some((l) => invokesCheck2(l) || usesCheck(l) || /^\s*uses:\s*\S/.test(l));
|
|
5492
5687
|
NEUTRALISERS = [
|
|
5493
5688
|
{ re: /^\s*set\s+\+e\b/, what: "set +e \u2014 a failing check no longer fails the step" },
|
|
5494
5689
|
{ re: /^\s*exit\s+0\s*(?:$|[;#])/, what: "exit 0 \u2014 the step ends green whatever the check did" },
|
|
@@ -5574,7 +5769,7 @@ var init_ci_tampering = __esm({
|
|
|
5574
5769
|
}
|
|
5575
5770
|
if (afterLines) {
|
|
5576
5771
|
for (const b of runBlocks(afterLines)) {
|
|
5577
|
-
if (!b.body.some(
|
|
5772
|
+
if (!b.body.some(invokesCheck2)) continue;
|
|
5578
5773
|
const propagates = b.body.some((l) => /\bexit\s+"?\$/.test(l));
|
|
5579
5774
|
for (let k = 0; k < b.body.length; k++) {
|
|
5580
5775
|
const idx = b.start + 1 + k;
|
|
@@ -5621,7 +5816,7 @@ var init_ci_tampering = __esm({
|
|
|
5621
5816
|
const d = defaultsOf(afterLines, i);
|
|
5622
5817
|
if (d === null) continue;
|
|
5623
5818
|
const job = jobOf(afterLines, d);
|
|
5624
|
-
if (job ? !jobHasCheck(afterLines, job) : !afterLines.some((x) =>
|
|
5819
|
+
if (job ? !jobHasCheck(afterLines, job) : !afterLines.some((x) => invokesCheck2(x) || usesCheck(x))) continue;
|
|
5625
5820
|
}
|
|
5626
5821
|
out3.push(
|
|
5627
5822
|
makeFinding(RULE6, policy, {
|
|
@@ -5678,7 +5873,7 @@ var init_ci_tampering = __esm({
|
|
|
5678
5873
|
for (const l of removedLines(c)) {
|
|
5679
5874
|
if (/^\s*#/.test(l.content)) continue;
|
|
5680
5875
|
const isUsesLine = usesCheck(l.content);
|
|
5681
|
-
const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ? /^\s*-?\s*run:/.test(l.content) : true) &&
|
|
5876
|
+
const isCheckCommand = !isUsesLine && !/^\s*-?\s*uses:/.test(l.content) && (YAML_KEY.test(l.content) ? /^\s*-?\s*run:/.test(l.content) : true) && invokesCheck2(l.content);
|
|
5682
5877
|
if (!isUsesLine && !isCheckCommand) continue;
|
|
5683
5878
|
const s = survives(commandCore(l.content), isUsesLine, afterCores, addedCores);
|
|
5684
5879
|
if (s.state === "kept") continue;
|
|
@@ -7324,33 +7519,43 @@ function excludesOtherRunner(flag, cmd) {
|
|
|
7324
7519
|
const values = [...cmd.matchAll(/--exclude(?:=|\s+)(['"]?)(\S+?)\1(?=\s|$)/g)].map((m) => m[2]);
|
|
7325
7520
|
return values.length > 0 && values.every((v) => OTHER_RUNNER_DIR.test(v.replace(/^\.\//, "").replace(/(?:\/\*+)*\/?$/, "") + "/"));
|
|
7326
7521
|
}
|
|
7327
|
-
function
|
|
7522
|
+
function scriptsOf(src) {
|
|
7523
|
+
try {
|
|
7524
|
+
const pkg = JSON.parse(src ?? "{}");
|
|
7525
|
+
const s = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts : {};
|
|
7526
|
+
return Object.fromEntries(Object.entries(s).filter((e) => typeof e[1] === "string"));
|
|
7527
|
+
} catch {
|
|
7528
|
+
return {};
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
function scriptWeakenings(before, after, path, policy, addedPaths) {
|
|
7328
7532
|
if ((path.split("/").pop() ?? path) !== "package.json") return [];
|
|
7329
|
-
const scriptsOf = (src) => {
|
|
7330
|
-
try {
|
|
7331
|
-
const pkg = JSON.parse(src ?? "{}");
|
|
7332
|
-
const s = isRecord(pkg) && isRecord(pkg.scripts) ? pkg.scripts : {};
|
|
7333
|
-
return Object.fromEntries(Object.entries(s).filter((e) => typeof e[1] === "string"));
|
|
7334
|
-
} catch {
|
|
7335
|
-
return {};
|
|
7336
|
-
}
|
|
7337
|
-
};
|
|
7338
7533
|
const was = scriptsOf(before);
|
|
7339
7534
|
const now = scriptsOf(after);
|
|
7535
|
+
const dir = path.includes("/") ? path.slice(0, path.lastIndexOf("/") + 1) : "";
|
|
7536
|
+
const configReviewed = (file) => {
|
|
7537
|
+
const f = dir + file.replace(/^\.\//, "");
|
|
7538
|
+
return isProtected(f, policy, "config") && !addedPaths.has(f);
|
|
7539
|
+
};
|
|
7540
|
+
const relocated = (kind) => Object.keys(now).some((n) => !(n in was) && checkKinds(now[n]).has(kind));
|
|
7340
7541
|
const out3 = [];
|
|
7341
|
-
for (const
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
const
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
if (
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7542
|
+
for (const name of /* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])) {
|
|
7543
|
+
const whole = WHOLE_SUITE_SCRIPT.test(name);
|
|
7544
|
+
if (!whole && !SLICE_SCRIPT.test(name)) continue;
|
|
7545
|
+
const prev = was[name];
|
|
7546
|
+
const cmd = now[name];
|
|
7547
|
+
if (prev === cmd) continue;
|
|
7548
|
+
if (prev === void 0 && !whole) continue;
|
|
7549
|
+
if (cmd === void 0) {
|
|
7550
|
+
if (name !== "pretest" || prev === void 0) continue;
|
|
7551
|
+
const gone = [...checkKinds(prev)].filter((k) => !relocated(k));
|
|
7552
|
+
if (gone.length === 0) continue;
|
|
7553
|
+
out3.push({ script: name, weakening: { state: "removed", kind: gone[0], what: "the script is gone" }, before: prev, after: "" });
|
|
7554
|
+
continue;
|
|
7353
7555
|
}
|
|
7556
|
+
const w = invocationWeakening(prev ?? "", cmd, void 0, { configReviewed, excuseFlag: excludesOtherRunner, sliceByDesign: !whole, relocated });
|
|
7557
|
+
if (w.state === "kept") continue;
|
|
7558
|
+
out3.push({ script: name, weakening: w, before: prev ?? "", after: cmd });
|
|
7354
7559
|
}
|
|
7355
7560
|
return out3;
|
|
7356
7561
|
}
|
|
@@ -7412,7 +7617,7 @@ function pytestPrecedenceFindings(changes, policy, ctx) {
|
|
|
7412
7617
|
})
|
|
7413
7618
|
);
|
|
7414
7619
|
}
|
|
7415
|
-
var RULE8, JVM_TEST, TEST_DEFS, TABLE_METHOD, isEachOf, isDescribeEach, OTHER_RUNNER_DIR, WHOLE_SUITE_SCRIPT, SED_INPLACE, testDeletion;
|
|
7620
|
+
var RULE8, JVM_TEST, TEST_DEFS, TABLE_METHOD, isEachOf, isDescribeEach, OTHER_RUNNER_DIR, WHOLE_SUITE_SCRIPT, SLICE_SCRIPT, SED_INPLACE, testDeletion;
|
|
7416
7621
|
var init_test_deletion = __esm({
|
|
7417
7622
|
"src/detectors/test-deletion.ts"() {
|
|
7418
7623
|
"use strict";
|
|
@@ -7423,7 +7628,7 @@ var init_test_deletion = __esm({
|
|
|
7423
7628
|
init_files();
|
|
7424
7629
|
init_repo();
|
|
7425
7630
|
init_suite_config();
|
|
7426
|
-
|
|
7631
|
+
init_invocation();
|
|
7427
7632
|
init_narrow();
|
|
7428
7633
|
RULE8 = "test-deletion";
|
|
7429
7634
|
JVM_TEST = /^\s*(?:@\w+(?:\([^)]*\))?\s+)*@(?:Test|ParameterizedTest|RepeatedTest|TestFactory|TestTemplate)\b/;
|
|
@@ -7441,7 +7646,8 @@ var init_test_deletion = __esm({
|
|
|
7441
7646
|
isEachOf = (expr) => ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression) && (expr.expression.text === "it" || expr.expression.text === "test") && TABLE_METHOD.test(expr.name.text);
|
|
7442
7647
|
isDescribeEach = (expr) => ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression) && expr.expression.text === "describe" && TABLE_METHOD.test(expr.name.text);
|
|
7443
7648
|
OTHER_RUNNER_DIR = /(?:^|\/)(?:e2e|cypress|playwright|fixtures|__fixtures__|__mocks__)\//;
|
|
7444
|
-
WHOLE_SUITE_SCRIPT = /^test
|
|
7649
|
+
WHOLE_SUITE_SCRIPT = /^(?:test|test:ci|check|pretest)$/;
|
|
7650
|
+
SLICE_SCRIPT = /^test:(?:unit|integration)$/;
|
|
7445
7651
|
SED_INPLACE = /(?:^|\s)(?:-i\b|--in-place\b|-[a-zA-Z]*i[a-zA-Z]*\b)/;
|
|
7446
7652
|
testDeletion = {
|
|
7447
7653
|
id: RULE8,
|
|
@@ -7450,6 +7656,7 @@ var init_test_deletion = __esm({
|
|
|
7450
7656
|
run(changes, policy, _view, ctx) {
|
|
7451
7657
|
const out3 = [];
|
|
7452
7658
|
const isSpec = (p) => isProtected(p, policy, "tests") && !isProtected(p, policy, "snapshots");
|
|
7659
|
+
const addedPaths = new Set(changes.filter((c) => c.kind === "file" && c.op === "add").map((c) => c.path));
|
|
7453
7660
|
const addedTestLines = /* @__PURE__ */ new Set();
|
|
7454
7661
|
let addedTestBlocks = 0;
|
|
7455
7662
|
for (const c of changes) {
|
|
@@ -7529,13 +7736,20 @@ var init_test_deletion = __esm({
|
|
|
7529
7736
|
})
|
|
7530
7737
|
);
|
|
7531
7738
|
}
|
|
7532
|
-
for (const n of
|
|
7739
|
+
for (const n of scriptWeakenings(c.before, c.after, c.path, policy, addedPaths)) {
|
|
7740
|
+
const w = n.weakening;
|
|
7741
|
+
const evidence = `"${n.script}": ${JSON.stringify(n.before)} \u2192 ${n.after === "" ? "deleted" : JSON.stringify(n.after)}`;
|
|
7533
7742
|
out3.push(
|
|
7534
|
-
makeFinding(RULE8, policy, {
|
|
7743
|
+
w.state === "removed" ? makeFinding(RULE8, policy, {
|
|
7535
7744
|
file: c.path,
|
|
7536
|
-
message: `The test script
|
|
7537
|
-
evidence
|
|
7538
|
-
remediation: "Keep the
|
|
7745
|
+
message: `The test script no longer runs the ${w.kind} check: scripts.${n.script} ${w.what}.`,
|
|
7746
|
+
evidence,
|
|
7747
|
+
remediation: "Keep the script running its check. Replacing the runner in package.json removes every test from every `npm test` while the workflow line stays untouched."
|
|
7748
|
+
}) : makeFinding(RULE8, policy, {
|
|
7749
|
+
file: c.path,
|
|
7750
|
+
message: `The test script now narrows the suite: ${w.what} ${w.direction} ${w.direction === "added" ? "to" : "from"} scripts.${n.script}.`,
|
|
7751
|
+
evidence,
|
|
7752
|
+
remediation: "Keep the test script running the whole suite and letting it decide; a masked status, a cut-short run or a runner flag that selects fewer specs removes the rest from every run."
|
|
7539
7753
|
})
|
|
7540
7754
|
);
|
|
7541
7755
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.11",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|