openpitstop 1.0.0
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/LICENSE +21 -0
- package/README.md +512 -0
- package/demo-repo/README.md +24 -0
- package/demo-repo/package-lock.json +4483 -0
- package/demo-repo/package.json +17 -0
- package/demo-repo/src/config.js +5 -0
- package/demo-repo/src/db.js +31 -0
- package/demo-repo/src/server.js +13 -0
- package/demo-repo/src/userController.js +22 -0
- package/demo-repo/src/userRepo.js +18 -0
- package/demo-repo/src/userService.js +59 -0
- package/demo-repo/test/user.test.js +12 -0
- package/demo-repo-fintech/README.md +27 -0
- package/demo-repo-fintech/package-lock.json +1023 -0
- package/demo-repo-fintech/package.json +14 -0
- package/demo-repo-fintech/pitstop-repro-ledger-order-ord-conc-1-charged-2.test.mjs +175 -0
- package/demo-repo-fintech/pitstop-repro-ledger-order-ord-retry-2-charged-2.test.mjs +175 -0
- package/demo-repo-fintech/pitstop-repro-ledger-order-ord-webhook-3-charged-2.test.mjs +175 -0
- package/demo-repo-fintech/src/gateway.js +26 -0
- package/demo-repo-fintech/src/server.js +61 -0
- package/demo-repo-generators/package-lock.json +1508 -0
- package/demo-repo-generators/package.json +17 -0
- package/demo-repo-generators/public/index.html +10 -0
- package/demo-repo-generators/scripts/build.js +17 -0
- package/demo-repo-generators/src/counter.js +16 -0
- package/demo-repo-integrity/README.md +49 -0
- package/demo-repo-integrity/package.json +12 -0
- package/demo-repo-integrity/src/money.js +19 -0
- package/demo-repo-integrity/test/money.test.js +17 -0
- package/dist/analyzers/accessibility.d.ts +18 -0
- package/dist/analyzers/accessibility.js +248 -0
- package/dist/analyzers/accessibility.js.map +1 -0
- package/dist/analyzers/dependencyGraph.d.ts +14 -0
- package/dist/analyzers/dependencyGraph.js +206 -0
- package/dist/analyzers/dependencyGraph.js.map +1 -0
- package/dist/analyzers/devex.d.ts +19 -0
- package/dist/analyzers/devex.js +301 -0
- package/dist/analyzers/devex.js.map +1 -0
- package/dist/analyzers/duplication.d.ts +2 -0
- package/dist/analyzers/duplication.js +47 -0
- package/dist/analyzers/duplication.js.map +1 -0
- package/dist/analyzers/index.d.ts +12 -0
- package/dist/analyzers/index.js +38 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/analyzers/integrity/assertionLiteralTamper.d.ts +2 -0
- package/dist/analyzers/integrity/assertionLiteralTamper.js +164 -0
- package/dist/analyzers/integrity/assertionLiteralTamper.js.map +1 -0
- package/dist/analyzers/integrity/exceptionSwallow.d.ts +2 -0
- package/dist/analyzers/integrity/exceptionSwallow.js +198 -0
- package/dist/analyzers/integrity/exceptionSwallow.js.map +1 -0
- package/dist/analyzers/integrity/exitCheat.d.ts +2 -0
- package/dist/analyzers/integrity/exitCheat.js +31 -0
- package/dist/analyzers/integrity/exitCheat.js.map +1 -0
- package/dist/analyzers/integrity/git.d.ts +10 -0
- package/dist/analyzers/integrity/git.js +181 -0
- package/dist/analyzers/integrity/git.js.map +1 -0
- package/dist/analyzers/integrity/hardcodedMatch.d.ts +2 -0
- package/dist/analyzers/integrity/hardcodedMatch.js +93 -0
- package/dist/analyzers/integrity/hardcodedMatch.js.map +1 -0
- package/dist/analyzers/integrity/helpers.d.ts +21 -0
- package/dist/analyzers/integrity/helpers.js +66 -0
- package/dist/analyzers/integrity/helpers.js.map +1 -0
- package/dist/analyzers/integrity/index.d.ts +10 -0
- package/dist/analyzers/integrity/index.js +27 -0
- package/dist/analyzers/integrity/index.js.map +1 -0
- package/dist/analyzers/integrity/mockOverreach.d.ts +2 -0
- package/dist/analyzers/integrity/mockOverreach.js +72 -0
- package/dist/analyzers/integrity/mockOverreach.js.map +1 -0
- package/dist/analyzers/integrity/suppressionCreep.d.ts +2 -0
- package/dist/analyzers/integrity/suppressionCreep.js +27 -0
- package/dist/analyzers/integrity/suppressionCreep.js.map +1 -0
- package/dist/analyzers/integrity/testTamper.d.ts +2 -0
- package/dist/analyzers/integrity/testTamper.js +153 -0
- package/dist/analyzers/integrity/testTamper.js.map +1 -0
- package/dist/analyzers/integrity/types.d.ts +63 -0
- package/dist/analyzers/integrity/types.js +13 -0
- package/dist/analyzers/integrity/types.js.map +1 -0
- package/dist/analyzers/ledger/attacks.d.ts +17 -0
- package/dist/analyzers/ledger/attacks.js +135 -0
- package/dist/analyzers/ledger/attacks.js.map +1 -0
- package/dist/analyzers/ledger/discover.d.ts +15 -0
- package/dist/analyzers/ledger/discover.js +122 -0
- package/dist/analyzers/ledger/discover.js.map +1 -0
- package/dist/analyzers/ledger/evidence.d.ts +22 -0
- package/dist/analyzers/ledger/evidence.js +127 -0
- package/dist/analyzers/ledger/evidence.js.map +1 -0
- package/dist/analyzers/ledger/harness.d.ts +4 -0
- package/dist/analyzers/ledger/harness.js +270 -0
- package/dist/analyzers/ledger/harness.js.map +1 -0
- package/dist/analyzers/ledger/index.d.ts +14 -0
- package/dist/analyzers/ledger/index.js +116 -0
- package/dist/analyzers/ledger/index.js.map +1 -0
- package/dist/analyzers/ledger/types.d.ts +31 -0
- package/dist/analyzers/ledger/types.js +2 -0
- package/dist/analyzers/ledger/types.js.map +1 -0
- package/dist/analyzers/perf.d.ts +2 -0
- package/dist/analyzers/perf.js +106 -0
- package/dist/analyzers/perf.js.map +1 -0
- package/dist/analyzers/reliability.d.ts +22 -0
- package/dist/analyzers/reliability.js +339 -0
- package/dist/analyzers/reliability.js.map +1 -0
- package/dist/analyzers/routes.d.ts +17 -0
- package/dist/analyzers/routes.js +115 -0
- package/dist/analyzers/routes.js.map +1 -0
- package/dist/analyzers/security.d.ts +2 -0
- package/dist/analyzers/security.js +302 -0
- package/dist/analyzers/security.js.map +1 -0
- package/dist/analyzers/suiteRunner.d.ts +64 -0
- package/dist/analyzers/suiteRunner.js +381 -0
- package/dist/analyzers/suiteRunner.js.map +1 -0
- package/dist/analyzers/tests.d.ts +2 -0
- package/dist/analyzers/tests.js +215 -0
- package/dist/analyzers/tests.js.map +1 -0
- package/dist/analyzers/types.d.ts +203 -0
- package/dist/analyzers/types.js +2 -0
- package/dist/analyzers/types.js.map +1 -0
- package/dist/analyzers/util.d.ts +35 -0
- package/dist/analyzers/util.js +209 -0
- package/dist/analyzers/util.js.map +1 -0
- package/dist/ciCheckA.d.ts +2 -0
- package/dist/ciCheckA.js +5 -0
- package/dist/ciCheckA.js.map +1 -0
- package/dist/ciCheckB.d.ts +2 -0
- package/dist/ciCheckB.js +6 -0
- package/dist/ciCheckB.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +65 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/budget.d.ts +33 -0
- package/dist/commands/budget.js +108 -0
- package/dist/commands/budget.js.map +1 -0
- package/dist/commands/ci.d.ts +14 -0
- package/dist/commands/ci.js +157 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/demo.d.ts +2 -0
- package/dist/commands/demo.js +163 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/digest.d.ts +8 -0
- package/dist/commands/digest.js +189 -0
- package/dist/commands/digest.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +98 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/drive.d.ts +3 -0
- package/dist/commands/drive.js +170 -0
- package/dist/commands/drive.js.map +1 -0
- package/dist/commands/gate.d.ts +2 -0
- package/dist/commands/gate.js +136 -0
- package/dist/commands/gate.js.map +1 -0
- package/dist/commands/honesty.d.ts +2 -0
- package/dist/commands/honesty.js +182 -0
- package/dist/commands/honesty.js.map +1 -0
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +244 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +81 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/integrity.d.ts +2 -0
- package/dist/commands/integrity.js +74 -0
- package/dist/commands/integrity.js.map +1 -0
- package/dist/commands/memory.d.ts +6 -0
- package/dist/commands/memory.js +90 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/pen.d.ts +17 -0
- package/dist/commands/pen.js +158 -0
- package/dist/commands/pen.js.map +1 -0
- package/dist/commands/prompt.d.ts +2 -0
- package/dist/commands/prompt.js +37 -0
- package/dist/commands/prompt.js.map +1 -0
- package/dist/commands/readyCheck.d.ts +21 -0
- package/dist/commands/readyCheck.js +68 -0
- package/dist/commands/readyCheck.js.map +1 -0
- package/dist/commands/report.d.ts +2 -0
- package/dist/commands/report.js +48 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/repro.d.ts +3 -0
- package/dist/commands/repro.js +61 -0
- package/dist/commands/repro.js.map +1 -0
- package/dist/commands/scan.d.ts +27 -0
- package/dist/commands/scan.js +348 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +163 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/trends.d.ts +2 -0
- package/dist/commands/trends.js +103 -0
- package/dist/commands/trends.js.map +1 -0
- package/dist/commands/try.d.ts +12 -0
- package/dist/commands/try.js +208 -0
- package/dist/commands/try.js.map +1 -0
- package/dist/commands/verify.d.ts +44 -0
- package/dist/commands/verify.js +381 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/watch.d.ts +10 -0
- package/dist/commands/watch.js +70 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/evidence.d.ts +37 -0
- package/dist/evidence.js +61 -0
- package/dist/evidence.js.map +1 -0
- package/dist/graph/correlate.d.ts +7 -0
- package/dist/graph/correlate.js +246 -0
- package/dist/graph/correlate.js.map +1 -0
- package/dist/graph/integrity.d.ts +22 -0
- package/dist/graph/integrity.js +39 -0
- package/dist/graph/integrity.js.map +1 -0
- package/dist/installer/targets.d.ts +23 -0
- package/dist/installer/targets.js +162 -0
- package/dist/installer/targets.js.map +1 -0
- package/dist/memory/store.d.ts +33 -0
- package/dist/memory/store.js +131 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/pen/dynamic.d.ts +36 -0
- package/dist/pen/dynamic.js +622 -0
- package/dist/pen/dynamic.js.map +1 -0
- package/dist/pen/fix.d.ts +35 -0
- package/dist/pen/fix.js +239 -0
- package/dist/pen/fix.js.map +1 -0
- package/dist/pen/proof.d.ts +46 -0
- package/dist/pen/proof.js +112 -0
- package/dist/pen/proof.js.map +1 -0
- package/dist/pen/report.d.ts +11 -0
- package/dist/pen/report.js +258 -0
- package/dist/pen/report.js.map +1 -0
- package/dist/pen/static.d.ts +17 -0
- package/dist/pen/static.js +565 -0
- package/dist/pen/static.js.map +1 -0
- package/dist/pen/store.d.ts +11 -0
- package/dist/pen/store.js +36 -0
- package/dist/pen/store.js.map +1 -0
- package/dist/pen/types.d.ts +112 -0
- package/dist/pen/types.js +53 -0
- package/dist/pen/types.js.map +1 -0
- package/dist/report/format.d.ts +126 -0
- package/dist/report/format.js +706 -0
- package/dist/report/format.js.map +1 -0
- package/dist/report/score.d.ts +47 -0
- package/dist/report/score.js +164 -0
- package/dist/report/score.js.map +1 -0
- package/dist/repro/framework.d.ts +26 -0
- package/dist/repro/framework.js +224 -0
- package/dist/repro/framework.js.map +1 -0
- package/dist/repro/generate.d.ts +14 -0
- package/dist/repro/generate.js +669 -0
- package/dist/repro/generate.js.map +1 -0
- package/dist/repro/ids.d.ts +32 -0
- package/dist/repro/ids.js +175 -0
- package/dist/repro/ids.js.map +1 -0
- package/dist/repro/index.d.ts +24 -0
- package/dist/repro/index.js +69 -0
- package/dist/repro/index.js.map +1 -0
- package/dist/repro/pen.d.ts +26 -0
- package/dist/repro/pen.js +321 -0
- package/dist/repro/pen.js.map +1 -0
- package/dist/repro/run.d.ts +18 -0
- package/dist/repro/run.js +36 -0
- package/dist/repro/run.js.map +1 -0
- package/dist/sandbox/proxy.d.ts +41 -0
- package/dist/sandbox/proxy.js +231 -0
- package/dist/sandbox/proxy.js.map +1 -0
- package/dist/sandbox/startCmd.d.ts +24 -0
- package/dist/sandbox/startCmd.js +71 -0
- package/dist/sandbox/startCmd.js.map +1 -0
- package/dist/ui/spinner.d.ts +18 -0
- package/dist/ui/spinner.js +26 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/verify/metrics.d.ts +13 -0
- package/dist/verify/metrics.js +72 -0
- package/dist/verify/metrics.js.map +1 -0
- package/package.json +68 -0
- package/scripts/cheat-demo.cjs +102 -0
- package/templates/ledger/preload.cjs +331 -0
- package/templates/pen/preload.cjs +196 -0
- package/templates/pitstop.prompt.md +371 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { discover } from "./discover.js";
|
|
2
|
+
import { startHarness, newEvidencePath, readAbortReason, } from "./harness.js";
|
|
3
|
+
import { runAttacks } from "./attacks.js";
|
|
4
|
+
import { analyzeEvidence, writeEvidenceFile, } from "./evidence.js";
|
|
5
|
+
/**
|
|
6
|
+
* ledger.ts — orchestrates `pitstop scan --ledger`.
|
|
7
|
+
*
|
|
8
|
+
* This is invasive (it boots the app and fires live traffic at it), so it is
|
|
9
|
+
* ONLY ever invoked from the CLI when the user explicitly passes `--ledger`.
|
|
10
|
+
* Every outbound HTTP call from the app is routed through a nock sandbox before
|
|
11
|
+
* it can leave the process; the request/response pairs the "gateway" receives
|
|
12
|
+
* are recorded so evidence of a double-charge is real, not simulated.
|
|
13
|
+
*/
|
|
14
|
+
export async function runLedgerAnalyzer(repo) {
|
|
15
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
16
|
+
let discovery;
|
|
17
|
+
try {
|
|
18
|
+
discovery = discover(repo);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return {
|
|
22
|
+
status: "error",
|
|
23
|
+
note: `endpoint discovery failed: ${e.message}`,
|
|
24
|
+
endpoints: [],
|
|
25
|
+
evidence: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (discovery.endpoints.length === 0) {
|
|
29
|
+
return {
|
|
30
|
+
status: "ok",
|
|
31
|
+
note: "no money-moving endpoints discovered (route keywords: charge/capture/payment/" +
|
|
32
|
+
"transfer/refund/webhook; payment SDKs: razorpay/stripe/braintree)",
|
|
33
|
+
endpoints: [],
|
|
34
|
+
evidence: [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const harnessResult = await startHarness(repo, discovery.gatewayHosts);
|
|
38
|
+
if (!harnessResult.harness) {
|
|
39
|
+
return {
|
|
40
|
+
status: harnessResult.aborted ? "aborted" : "error",
|
|
41
|
+
note: harnessResult.abortReason ?? "could not arm the ledger harness",
|
|
42
|
+
endpoints: discovery.endpoints,
|
|
43
|
+
evidence: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const harness = harnessResult.harness;
|
|
47
|
+
let evidenceOut = null;
|
|
48
|
+
let abortedReason = null;
|
|
49
|
+
let runError = null;
|
|
50
|
+
try {
|
|
51
|
+
const attacks = await runAttacks({
|
|
52
|
+
baseUrl: harness.baseUrl,
|
|
53
|
+
endpoints: discovery.endpoints,
|
|
54
|
+
});
|
|
55
|
+
evidenceOut = analyzeEvidence({
|
|
56
|
+
repo,
|
|
57
|
+
discovery,
|
|
58
|
+
attacks,
|
|
59
|
+
gatewayLogPath: harness.gatewayLogPath,
|
|
60
|
+
});
|
|
61
|
+
abortedReason = readAbortReason(harness.controlPath);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
runError = e.message;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await harness.close();
|
|
68
|
+
}
|
|
69
|
+
if (runError) {
|
|
70
|
+
return {
|
|
71
|
+
status: "error",
|
|
72
|
+
note: `attack/evidence phase failed: ${runError}`,
|
|
73
|
+
endpoints: discovery.endpoints,
|
|
74
|
+
evidence: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (abortedReason) {
|
|
78
|
+
return {
|
|
79
|
+
status: "aborted",
|
|
80
|
+
note: abortedReason,
|
|
81
|
+
endpoints: discovery.endpoints,
|
|
82
|
+
evidence: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (!evidenceOut) {
|
|
86
|
+
return {
|
|
87
|
+
status: "error",
|
|
88
|
+
note: "no evidence produced",
|
|
89
|
+
endpoints: discovery.endpoints,
|
|
90
|
+
evidence: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const evidenceFile = newEvidencePath(repo, ts);
|
|
94
|
+
writeEvidenceFile(evidenceFile, evidenceOut.evidence, harness.gatewayLogPath);
|
|
95
|
+
for (const e of evidenceOut.evidence)
|
|
96
|
+
e.evidenceFile = evidenceFile;
|
|
97
|
+
const notes = [];
|
|
98
|
+
if (evidenceOut.note)
|
|
99
|
+
notes.push(evidenceOut.note);
|
|
100
|
+
if (discovery.sdkImports.length === 0) {
|
|
101
|
+
notes.push("no payment-SDK import detected — route heuristics only; the mock gateway was " +
|
|
102
|
+
"not pointed at a specific provider");
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
status: "ok",
|
|
106
|
+
note: notes.length ? notes.join(" · ") : undefined,
|
|
107
|
+
endpoints: discovery.endpoints,
|
|
108
|
+
evidence: evidenceOut.evidence,
|
|
109
|
+
gatewayLogPath: harness.gatewayLogPath,
|
|
110
|
+
evidenceFile,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export { discover } from "./discover.js";
|
|
114
|
+
export { runAttacks } from "./attacks.js";
|
|
115
|
+
export { analyzeEvidence } from "./evidence.js";
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyzers/ledger/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LedgerEndpoint, LedgerScenario } from "../types.js";
|
|
2
|
+
export type { LedgerEndpoint, LedgerScenario };
|
|
3
|
+
/** A single scenario run against one endpoint. */
|
|
4
|
+
export interface ScenarioRun {
|
|
5
|
+
scenario: LedgerScenario;
|
|
6
|
+
endpointIndex: number;
|
|
7
|
+
orderId: string;
|
|
8
|
+
idempotencyKey: string | null;
|
|
9
|
+
/** Every identifier that could surface on the wire (order id, idem key, payment id). */
|
|
10
|
+
identifiers: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface AttackReport {
|
|
13
|
+
runs: ScenarioRun[];
|
|
14
|
+
}
|
|
15
|
+
export interface Harness {
|
|
16
|
+
port: number;
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
/** Absolute path of the gateway receipt log (JSONL) written by the preload. */
|
|
19
|
+
gatewayLogPath: string;
|
|
20
|
+
/** Absolute path of the control stream (JSONL) written by the preload. */
|
|
21
|
+
controlPath: string;
|
|
22
|
+
/** Cleanup: kill the app, remove temp files. */
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface HarnessResult {
|
|
26
|
+
harness: Harness | null;
|
|
27
|
+
/** When the harness could not be armed safely. */
|
|
28
|
+
abortReason?: string;
|
|
29
|
+
/** True when the app reported a safety abort (uninterceptable traffic detected). */
|
|
30
|
+
aborted: boolean;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/analyzers/ledger/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { commandExists, detectLanguage, dirSize, safeExecAsync } from "./util.js";
|
|
4
|
+
export async function analyzePerf(repo) {
|
|
5
|
+
const lang = detectLanguage(repo);
|
|
6
|
+
if (lang === "js")
|
|
7
|
+
return jsPerf(repo);
|
|
8
|
+
if (lang === "go")
|
|
9
|
+
return nativePerf(repo, "go", ["build", "./..."], null);
|
|
10
|
+
if (lang === "rust")
|
|
11
|
+
return nativePerf(repo, "cargo", ["build"], null);
|
|
12
|
+
if (lang === "dart")
|
|
13
|
+
return nativePerf(repo, "flutter", ["build", "web"], "build/web");
|
|
14
|
+
if (lang === "dotnet")
|
|
15
|
+
return nativePerf(repo, "dotnet", ["build"], "bin");
|
|
16
|
+
if (lang === "java") {
|
|
17
|
+
if (fs.existsSync(path.join(repo, "pom.xml"))) {
|
|
18
|
+
return nativePerf(repo, "mvn", ["package", "-DskipTests"], "target");
|
|
19
|
+
}
|
|
20
|
+
const gradle = gradleCmd(repo);
|
|
21
|
+
if (gradle)
|
|
22
|
+
return nativePerf(repo, gradle, ["assemble"], "build/libs");
|
|
23
|
+
return { status: "skipped", note: "no java build tool found (mvn or gradle)" };
|
|
24
|
+
}
|
|
25
|
+
return { status: "skipped", note: "no build command for this language" };
|
|
26
|
+
}
|
|
27
|
+
function gradleCmd(repo) {
|
|
28
|
+
const wrapperName = process.platform === "win32" ? "gradlew.bat" : "gradlew";
|
|
29
|
+
const wrapper = path.join(repo, wrapperName);
|
|
30
|
+
if (fs.existsSync(wrapper))
|
|
31
|
+
return wrapper;
|
|
32
|
+
return commandExists("gradle") ? "gradle" : null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generic build-timing for non-JS stacks. `sizeDir` is an output directory
|
|
36
|
+
* relative to the repo (measured when it exists) — some toolchains only expose
|
|
37
|
+
* a meaningful build artifact size after the build.
|
|
38
|
+
*/
|
|
39
|
+
async function nativePerf(repo, cmd, args, sizeDir) {
|
|
40
|
+
if (!commandExists(cmd) && !path.isAbsolute(cmd)) {
|
|
41
|
+
return { status: "skipped", note: `${cmd} not found on PATH` };
|
|
42
|
+
}
|
|
43
|
+
const start = performance.now();
|
|
44
|
+
const build = await safeExecAsync(cmd, args, repo, 360000);
|
|
45
|
+
if (build.code !== 0) {
|
|
46
|
+
return {
|
|
47
|
+
status: "skipped",
|
|
48
|
+
note: `${cmd} ${args.join(" ")} failed (see stderr)`,
|
|
49
|
+
stderr: build.stderr,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const buildTimeMs = Math.round(performance.now() - start);
|
|
53
|
+
let bundleSizeBytes;
|
|
54
|
+
if (sizeDir) {
|
|
55
|
+
const outAbs = path.resolve(repo, sizeDir);
|
|
56
|
+
if (fs.existsSync(outAbs))
|
|
57
|
+
bundleSizeBytes = dirSize(outAbs);
|
|
58
|
+
}
|
|
59
|
+
return { status: "ok", buildTimeMs, bundleSizeBytes };
|
|
60
|
+
}
|
|
61
|
+
async function jsPerf(repo) {
|
|
62
|
+
const pkgPath = path.join(repo, "package.json");
|
|
63
|
+
if (!fs.existsSync(pkgPath)) {
|
|
64
|
+
return { status: "skipped", note: "no package.json" };
|
|
65
|
+
}
|
|
66
|
+
let pkg = {};
|
|
67
|
+
try {
|
|
68
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return { status: "skipped", note: "unreadable package.json" };
|
|
72
|
+
}
|
|
73
|
+
const buildScript = pkg.scripts?.build;
|
|
74
|
+
if (!buildScript) {
|
|
75
|
+
return { status: "skipped", note: "no build script" };
|
|
76
|
+
}
|
|
77
|
+
const start = performance.now();
|
|
78
|
+
const build = await safeExecAsync("npm", ["run", "build"], repo, 240000);
|
|
79
|
+
if (build.code !== 0) {
|
|
80
|
+
return {
|
|
81
|
+
status: "skipped",
|
|
82
|
+
note: "build script present but `npm run build` failed (see stderr)",
|
|
83
|
+
stderr: build.stderr,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const buildTimeMs = Math.round(performance.now() - start);
|
|
87
|
+
let outDir = "dist";
|
|
88
|
+
const tsconfigPath = path.join(repo, "tsconfig.json");
|
|
89
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
90
|
+
try {
|
|
91
|
+
const tc = JSON.parse(fs.readFileSync(tsconfigPath, "utf8"));
|
|
92
|
+
if (typeof tc.compilerOptions?.outDir === "string") {
|
|
93
|
+
outDir = tc.compilerOptions.outDir;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* ignore */
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const outAbs = path.resolve(repo, outDir);
|
|
101
|
+
let bundleSizeBytes;
|
|
102
|
+
if (fs.existsSync(outAbs))
|
|
103
|
+
bundleSizeBytes = dirSize(outAbs);
|
|
104
|
+
return { status: "ok", buildTimeMs, bundleSizeBytes };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=perf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf.js","sourceRoot":"","sources":["../../src/analyzers/perf.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReliabilityResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reliability analysis.
|
|
4
|
+
*
|
|
5
|
+
* HONEST CAVEATS (heuristics, never certainty):
|
|
6
|
+
*
|
|
7
|
+
* - Flaky-test detection works by running the suite `runs` times IN SEQUENCE and
|
|
8
|
+
* flagging any test whose pass/fail changed between runs. Running the suite
|
|
9
|
+
* multiple times can itself perturb results (cache warm-up, shuffle order), and a
|
|
10
|
+
* genuinely flaky test may not flake within the runs executed — absence of a
|
|
11
|
+
* finding is NOT proof the suite is stable. We only run this when the suite is
|
|
12
|
+
* fast (<60s); a slow suite is skipped with a note. With `runs < 2` the flaky
|
|
13
|
+
* detector cannot run (it needs two observed outcomes), so only the race-smell
|
|
14
|
+
* heuristic is reported and the note says so.
|
|
15
|
+
* - The race-condition "smells" (timer-based waits, module-scope mutable state
|
|
16
|
+
* reassigned near async code) are a cheap static grep. Every one of them is
|
|
17
|
+
* labeled "heuristic, needs human review". A smell is a hint to look, not an
|
|
18
|
+
* assertion that a bug exists.
|
|
19
|
+
*/
|
|
20
|
+
export declare function analyzeReliability(repo: string, opts?: {
|
|
21
|
+
runs?: number;
|
|
22
|
+
}): Promise<ReliabilityResult>;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { commandExists, safeExecAsync, walkFiles, lineOf, detectLanguage } from "./util.js";
|
|
4
|
+
import { runNonJsSuite } from "./suiteRunner.js";
|
|
5
|
+
/** Only bother with flakiness detection when the suite is fast enough. */
|
|
6
|
+
const MAX_SUITE_MS = 60_000;
|
|
7
|
+
/**
|
|
8
|
+
* Sequential runs; we deliberately run serially so runs perturb each other as
|
|
9
|
+
* little as possible. 2 runs is the minimum that can detect a changed outcome;
|
|
10
|
+
* pass a higher `runs` for a stronger signal (cost: N× suite time).
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_RUNS = 2;
|
|
13
|
+
const MIN_RUNS_FOR_FLAKY = 2;
|
|
14
|
+
const MAX_FLAKY_REPORTED = 20;
|
|
15
|
+
const JS_EXTS = [".js", ".jsx", ".ts", ".tsx"];
|
|
16
|
+
/**
|
|
17
|
+
* Reliability analysis.
|
|
18
|
+
*
|
|
19
|
+
* HONEST CAVEATS (heuristics, never certainty):
|
|
20
|
+
*
|
|
21
|
+
* - Flaky-test detection works by running the suite `runs` times IN SEQUENCE and
|
|
22
|
+
* flagging any test whose pass/fail changed between runs. Running the suite
|
|
23
|
+
* multiple times can itself perturb results (cache warm-up, shuffle order), and a
|
|
24
|
+
* genuinely flaky test may not flake within the runs executed — absence of a
|
|
25
|
+
* finding is NOT proof the suite is stable. We only run this when the suite is
|
|
26
|
+
* fast (<60s); a slow suite is skipped with a note. With `runs < 2` the flaky
|
|
27
|
+
* detector cannot run (it needs two observed outcomes), so only the race-smell
|
|
28
|
+
* heuristic is reported and the note says so.
|
|
29
|
+
* - The race-condition "smells" (timer-based waits, module-scope mutable state
|
|
30
|
+
* reassigned near async code) are a cheap static grep. Every one of them is
|
|
31
|
+
* labeled "heuristic, needs human review". A smell is a hint to look, not an
|
|
32
|
+
* assertion that a bug exists.
|
|
33
|
+
*/
|
|
34
|
+
export async function analyzeReliability(repo, opts = {}) {
|
|
35
|
+
const runsRequested = Math.max(1, Math.floor(opts.runs ?? DEFAULT_RUNS));
|
|
36
|
+
const skipped = (note) => ({
|
|
37
|
+
status: "skipped",
|
|
38
|
+
note,
|
|
39
|
+
runs: 0,
|
|
40
|
+
durationMs: 0,
|
|
41
|
+
flakyTests: [],
|
|
42
|
+
raceSmells: [],
|
|
43
|
+
});
|
|
44
|
+
// Race-smell grep is independent of the test runner and always attempted.
|
|
45
|
+
const raceSmells = scanRaceSmells(repo);
|
|
46
|
+
const first = await runSuite(repo);
|
|
47
|
+
if (!first) {
|
|
48
|
+
// No runnable suite — but surface races if any were found.
|
|
49
|
+
return raceSmells.length > 0
|
|
50
|
+
? {
|
|
51
|
+
status: "ok",
|
|
52
|
+
note: "no runnable test suite; race-smell heuristic only",
|
|
53
|
+
runs: 0,
|
|
54
|
+
durationMs: 0,
|
|
55
|
+
flakyTests: [],
|
|
56
|
+
raceSmells,
|
|
57
|
+
}
|
|
58
|
+
: skipped("no runnable test suite");
|
|
59
|
+
}
|
|
60
|
+
if (first.durationMs >= MAX_SUITE_MS) {
|
|
61
|
+
return {
|
|
62
|
+
status: raceSmells.length > 0 ? "ok" : "skipped",
|
|
63
|
+
note: `test suite took ${first.durationMs}ms (>= 60s) — too slow to run ${runsRequested}x; only the race-smell heuristic ran`,
|
|
64
|
+
runs: 1,
|
|
65
|
+
durationMs: first.durationMs,
|
|
66
|
+
suiteDurationMs: first.durationMs,
|
|
67
|
+
flakyTests: [],
|
|
68
|
+
raceSmells,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Flaky detection needs at least two observed outcomes per test.
|
|
72
|
+
const runs = [first];
|
|
73
|
+
for (let i = 1; i < runsRequested; i++) {
|
|
74
|
+
const r = await runSuite(repo);
|
|
75
|
+
if (r)
|
|
76
|
+
runs.push(r);
|
|
77
|
+
}
|
|
78
|
+
let note;
|
|
79
|
+
if (runs.length < MIN_RUNS_FOR_FLAKY) {
|
|
80
|
+
note =
|
|
81
|
+
"flaky detection needs >= 2 suite runs; runs=1 means only the race-smell heuristic was checked";
|
|
82
|
+
}
|
|
83
|
+
else if (raceSmells.length > 0 || detectFlaky(runs).length > 0) {
|
|
84
|
+
note = "findings are heuristics — confirm each before acting";
|
|
85
|
+
}
|
|
86
|
+
const flakyTests = detectFlaky(runs);
|
|
87
|
+
const totalDuration = runs.reduce((n, r) => n + r.durationMs, 0);
|
|
88
|
+
return {
|
|
89
|
+
status: "ok",
|
|
90
|
+
note,
|
|
91
|
+
runs: runs.length,
|
|
92
|
+
durationMs: totalDuration,
|
|
93
|
+
suiteDurationMs: first.durationMs,
|
|
94
|
+
flakyTests,
|
|
95
|
+
raceSmells,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function runSuite(repo) {
|
|
99
|
+
const lang = detectLanguage(repo);
|
|
100
|
+
if (lang === "unknown")
|
|
101
|
+
return Promise.resolve(null);
|
|
102
|
+
if (lang === "python")
|
|
103
|
+
return commandExists("pytest") ? runPytest(repo) : Promise.resolve(null);
|
|
104
|
+
if (lang !== "js") {
|
|
105
|
+
// Go/Rust/Flutter/.NET/Java: shared runner, per-test outcomes when the
|
|
106
|
+
// runner exposes them (go -json, cargo json, flutter --machine), whole-suite
|
|
107
|
+
// pseudo-test otherwise (dotnet/maven/gradle text/XML summaries).
|
|
108
|
+
return runNonJsSuite(repo).then((sr) => sr
|
|
109
|
+
? {
|
|
110
|
+
tests: sr.tests,
|
|
111
|
+
passed: sr.passed,
|
|
112
|
+
failed: sr.failed,
|
|
113
|
+
durationMs: sr.durationMs,
|
|
114
|
+
}
|
|
115
|
+
: null);
|
|
116
|
+
}
|
|
117
|
+
if (!fs.existsSync(path.join(repo, "package.json"))) {
|
|
118
|
+
return Promise.resolve(null);
|
|
119
|
+
}
|
|
120
|
+
let pkg = {};
|
|
121
|
+
try {
|
|
122
|
+
pkg = JSON.parse(fs.readFileSync(path.join(repo, "package.json"), "utf8"));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
pkg = {};
|
|
126
|
+
}
|
|
127
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
|
|
128
|
+
const hasBin = (name) => commandExists(name) || fs.existsSync(path.join(repo, "node_modules", ".bin", name));
|
|
129
|
+
if (deps.jest && hasBin("jest"))
|
|
130
|
+
return runJest(repo);
|
|
131
|
+
if (deps.vitest && hasBin("vitest"))
|
|
132
|
+
return runVitest(repo);
|
|
133
|
+
if (commandExists("pytest"))
|
|
134
|
+
return runPytest(repo);
|
|
135
|
+
return Promise.resolve(null);
|
|
136
|
+
}
|
|
137
|
+
async function runJest(repo) {
|
|
138
|
+
let cmd;
|
|
139
|
+
let args;
|
|
140
|
+
const local = path.join(repo, "node_modules", "jest", "bin", "jest.js");
|
|
141
|
+
if (fs.existsSync(local)) {
|
|
142
|
+
cmd = "node";
|
|
143
|
+
args = [local];
|
|
144
|
+
}
|
|
145
|
+
else if (commandExists("jest")) {
|
|
146
|
+
cmd = "jest";
|
|
147
|
+
args = [];
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const start = performance.now();
|
|
153
|
+
const cacheDir = path.join(repo, ".pitstop", "cache", "jest");
|
|
154
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
155
|
+
const r = await safeExecAsync(cmd, [...args, "--json", "--cacheDirectory", cacheDir], repo, 180000);
|
|
156
|
+
const durationMs = Math.round(performance.now() - start);
|
|
157
|
+
// jest exits 0 on pass, 1 on failure — both are valid outputs.
|
|
158
|
+
if (r.code !== 0 && r.code !== 1)
|
|
159
|
+
return null;
|
|
160
|
+
let json;
|
|
161
|
+
try {
|
|
162
|
+
json = JSON.parse(r.stdout);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const tests = [];
|
|
168
|
+
for (const tr of json.testResults ?? []) {
|
|
169
|
+
for (const a of tr.assertionResults ?? []) {
|
|
170
|
+
tests.push({
|
|
171
|
+
name: a.fullName ?? a.title ?? "",
|
|
172
|
+
file: tr.name,
|
|
173
|
+
status: a.status === "passed" ? "passed" : "failed",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
tests,
|
|
179
|
+
passed: json.numPassedTests ?? 0,
|
|
180
|
+
failed: json.numFailedTests ?? 0,
|
|
181
|
+
durationMs,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async function runVitest(repo) {
|
|
185
|
+
let cmd;
|
|
186
|
+
let args;
|
|
187
|
+
const local = path.join(repo, "node_modules", "vitest", "vitest.mjs");
|
|
188
|
+
if (fs.existsSync(local)) {
|
|
189
|
+
cmd = "node";
|
|
190
|
+
args = [local];
|
|
191
|
+
}
|
|
192
|
+
else if (commandExists("vitest")) {
|
|
193
|
+
cmd = "vitest";
|
|
194
|
+
args = [];
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const tmp = path.join(repo, ".pitstop", "vitest-reliability.json");
|
|
200
|
+
fs.mkdirSync(path.dirname(tmp), { recursive: true });
|
|
201
|
+
const start = performance.now();
|
|
202
|
+
await safeExecAsync(cmd, [...args, "run", "--reporter=json", "--outputFile", tmp], repo, 180000);
|
|
203
|
+
const durationMs = Math.round(performance.now() - start);
|
|
204
|
+
let json;
|
|
205
|
+
if (fs.existsSync(tmp)) {
|
|
206
|
+
try {
|
|
207
|
+
json = JSON.parse(fs.readFileSync(tmp, "utf8"));
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
json = undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!json)
|
|
214
|
+
return null;
|
|
215
|
+
const tests = [];
|
|
216
|
+
for (const tr of json.testResults ?? []) {
|
|
217
|
+
for (const a of tr.assertionResults ?? []) {
|
|
218
|
+
tests.push({
|
|
219
|
+
name: a.fullName ?? a.title ?? "",
|
|
220
|
+
file: tr.name,
|
|
221
|
+
status: a.status === "passed" ? "passed" : "failed",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
tests,
|
|
227
|
+
passed: json.numPassedTests ?? 0,
|
|
228
|
+
failed: json.numFailedTests ?? 0,
|
|
229
|
+
durationMs,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* pytest: `-q` only exposes per-suite counts, not per-test names. We model the
|
|
234
|
+
* whole run as one pseudo-test so a changing pass/fail between runs still flags.
|
|
235
|
+
*/
|
|
236
|
+
async function runPytest(repo) {
|
|
237
|
+
const start = performance.now();
|
|
238
|
+
const r = await safeExecAsync("pytest", ["-q", "--tb=no"], repo, 180000);
|
|
239
|
+
const durationMs = Math.round(performance.now() - start);
|
|
240
|
+
const passed = Number((r.stdout.match(/(\d+)\s+passed/) ?? [])[1] ?? 0);
|
|
241
|
+
const failed = Number((r.stdout.match(/(\d+)\s+failed/) ?? [])[1] ?? 0);
|
|
242
|
+
// Distinguish "no tests collected" from a real clean run.
|
|
243
|
+
if (passed === 0 && failed === 0 && !/passed|failed/.test(r.stdout))
|
|
244
|
+
return null;
|
|
245
|
+
const tests = [
|
|
246
|
+
{
|
|
247
|
+
name: failed > 0 ? "(pytest suite: failures)" : "(pytest suite)",
|
|
248
|
+
status: failed > 0 ? "failed" : "passed",
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
return { tests, passed, failed, durationMs };
|
|
252
|
+
}
|
|
253
|
+
function detectFlaky(runs) {
|
|
254
|
+
const byKey = new Map();
|
|
255
|
+
for (const run of runs) {
|
|
256
|
+
for (const t of run.tests) {
|
|
257
|
+
const key = `${t.file ?? ""}::${t.name}`;
|
|
258
|
+
if (!byKey.has(key)) {
|
|
259
|
+
byKey.set(key, { name: t.name, file: t.file, statuses: [] });
|
|
260
|
+
}
|
|
261
|
+
byKey.get(key).statuses.push(t.status);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const flaky = [];
|
|
265
|
+
for (const v of byKey.values()) {
|
|
266
|
+
const distinct = new Set(v.statuses);
|
|
267
|
+
if (v.statuses.length >= 2 && distinct.has("passed") && distinct.has("failed")) {
|
|
268
|
+
flaky.push({ name: v.name, file: v.file, statuses: v.statuses });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
flaky.sort((a, b) => b.statuses.length - a.statuses.length);
|
|
272
|
+
return flaky.slice(0, MAX_FLAKY_REPORTED);
|
|
273
|
+
}
|
|
274
|
+
/* ------------------------------------------------------------------ */
|
|
275
|
+
/* Race-condition smell heuristics (labeled, never certain) */
|
|
276
|
+
/* ------------------------------------------------------------------ */
|
|
277
|
+
const TEST_FILE_RE = /(?:\.(?:test|spec)\.(?:js|jsx|ts|tsx)$)|(?:[\\/](?:__tests__|test|tests|spec)[\\/])/;
|
|
278
|
+
function scanRaceSmells(repo) {
|
|
279
|
+
const issues = [];
|
|
280
|
+
for (const f of walkFiles(repo, JS_EXTS)) {
|
|
281
|
+
let content;
|
|
282
|
+
try {
|
|
283
|
+
content = fs.readFileSync(f, "utf8");
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const rel = path.relative(repo, f);
|
|
289
|
+
if (TEST_FILE_RE.test(rel)) {
|
|
290
|
+
issues.push(...timerSmells(content, f));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
issues.push(...moduleStateSmells(content, f));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return issues;
|
|
297
|
+
}
|
|
298
|
+
function timerSmells(content, file) {
|
|
299
|
+
const out = [];
|
|
300
|
+
const re = /\b(?:setTimeout|setInterval)\s*\(/g;
|
|
301
|
+
let m;
|
|
302
|
+
while ((m = re.exec(content)) !== null) {
|
|
303
|
+
out.push({
|
|
304
|
+
type: "race-condition",
|
|
305
|
+
severity: "warning",
|
|
306
|
+
file,
|
|
307
|
+
line: lineOf(content, m.index),
|
|
308
|
+
description: "test uses a timer-based wait (setTimeout/setInterval) — flaky-risk; prefer event- or poll-until helpers. heuristic, needs human review",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
/** Module-scope let/var at column 0 that is reassigned in a file with async code. */
|
|
314
|
+
function moduleStateSmells(content, file) {
|
|
315
|
+
const out = [];
|
|
316
|
+
const declRe = /^(?:export\s+)?(?:let|var)\s+([A-Za-z_$][\w$]*)/gm;
|
|
317
|
+
let d;
|
|
318
|
+
while ((d = declRe.exec(content)) !== null) {
|
|
319
|
+
const name = d[1];
|
|
320
|
+
// Only flag when the module also has async/await or Promise usage nearby.
|
|
321
|
+
if (!/\basync\b|\bawait\b|\bPromise\b/.test(content))
|
|
322
|
+
continue;
|
|
323
|
+
const reassign = new RegExp(`\\b${name}\\s*=(?!=)`, "g");
|
|
324
|
+
// Skip the declaration itself; look for a second assignment.
|
|
325
|
+
const count = (content.match(reassign) ?? []).length;
|
|
326
|
+
if (count > 1) {
|
|
327
|
+
out.push({
|
|
328
|
+
type: "race-condition",
|
|
329
|
+
severity: "warning",
|
|
330
|
+
file,
|
|
331
|
+
line: lineOf(content, d.index),
|
|
332
|
+
description: `module-level mutable state \`${name}\` is reassigned in a file that also has async code — ` +
|
|
333
|
+
"may race across concurrent tasks. heuristic, needs human review",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=reliability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reliability.js","sourceRoot":"","sources":["../../src/analyzers/reliability.ts"],"names":[],"mappings":"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"}
|