@scantrix/cli 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 +219 -0
- package/dist/astConfigParser.js +308 -0
- package/dist/astRuleHelpers.js +1451 -0
- package/dist/auditConfig.js +81 -0
- package/dist/ciExtractor.js +327 -0
- package/dist/cli.js +156 -0
- package/dist/configExtractor.js +261 -0
- package/dist/cypressExtractor.js +217 -0
- package/dist/diffTracker.js +310 -0
- package/dist/report.js +1904 -0
- package/dist/sarifFormatter.js +88 -0
- package/dist/scanResult.js +45 -0
- package/dist/scanner.js +3519 -0
- package/dist/scoring.js +206 -0
- package/dist/sinks/index.js +29 -0
- package/dist/sinks/jsonSink.js +28 -0
- package/dist/sinks/types.js +2 -0
- package/docs/high-res-icon.svg +26 -0
- package/docs/scantrix-logo-light.svg +64 -0
- package/docs/scantrix-logo.svg +64 -0
- package/package.json +55 -0
package/dist/scoring.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.severityScore = severityScore;
|
|
4
|
+
exports.computeFinalSeverity = computeFinalSeverity;
|
|
5
|
+
exports.applyEscalation = applyEscalation;
|
|
6
|
+
exports.isTestScoped = isTestScoped;
|
|
7
|
+
exports.riskPointsToHealthScore = riskPointsToHealthScore;
|
|
8
|
+
exports.gradeFromHealthScore = gradeFromHealthScore;
|
|
9
|
+
exports.calculateRiskScore = calculateRiskScore;
|
|
10
|
+
function severityScore(sev) {
|
|
11
|
+
return sev === "high" ? 3 : sev === "medium" ? 2 : 1;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Determine whether a finding's severity should be escalated based on
|
|
15
|
+
* how widespread the pattern is across the test suite.
|
|
16
|
+
*
|
|
17
|
+
* Pure function — no side-effects.
|
|
18
|
+
*/
|
|
19
|
+
function computeFinalSeverity(baseSeverity, occurrences, totalTestFiles) {
|
|
20
|
+
if (baseSeverity === "high") {
|
|
21
|
+
return { finalSeverity: "high", prevalence: totalTestFiles > 0 ? occurrences / totalTestFiles : null, reason: "already high" };
|
|
22
|
+
}
|
|
23
|
+
if (totalTestFiles <= 0) {
|
|
24
|
+
return { finalSeverity: baseSeverity, prevalence: null, reason: "unknown prevalence" };
|
|
25
|
+
}
|
|
26
|
+
const prevalence = occurrences / totalTestFiles;
|
|
27
|
+
if (baseSeverity === "medium") {
|
|
28
|
+
if (prevalence >= 0.25) {
|
|
29
|
+
return { finalSeverity: "high", prevalence, reason: `prevalence ${(prevalence * 100).toFixed(1)}% >= 25% threshold` };
|
|
30
|
+
}
|
|
31
|
+
if (occurrences >= 200) {
|
|
32
|
+
return { finalSeverity: "high", prevalence, reason: `${occurrences} occurrences >= 200 absolute threshold` };
|
|
33
|
+
}
|
|
34
|
+
if (prevalence >= 0.10 && occurrences >= 75) {
|
|
35
|
+
return { finalSeverity: "high", prevalence, reason: `prevalence ${(prevalence * 100).toFixed(1)}% >= 10% and ${occurrences} occurrences >= 75 (compound threshold)` };
|
|
36
|
+
}
|
|
37
|
+
return { finalSeverity: "medium", prevalence, reason: "below escalation thresholds" };
|
|
38
|
+
}
|
|
39
|
+
// baseSeverity === "low"
|
|
40
|
+
if (prevalence >= 0.25) {
|
|
41
|
+
return { finalSeverity: "medium", prevalence, reason: `prevalence ${(prevalence * 100).toFixed(1)}% >= 25% threshold` };
|
|
42
|
+
}
|
|
43
|
+
if (occurrences >= 200) {
|
|
44
|
+
return { finalSeverity: "medium", prevalence, reason: `${occurrences} occurrences >= 200 absolute threshold` };
|
|
45
|
+
}
|
|
46
|
+
return { finalSeverity: "low", prevalence, reason: "below escalation thresholds" };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Post-scan escalation pass: promote finding severity when a pattern is
|
|
50
|
+
* widespread across the test suite ("blast radius").
|
|
51
|
+
*
|
|
52
|
+
* Returns a new array. Non-escalated findings are returned by reference
|
|
53
|
+
* (no copy). Escalated findings get `baseSeverity` and `escalationReason` set.
|
|
54
|
+
* The original array is never mutated.
|
|
55
|
+
*/
|
|
56
|
+
function applyEscalation(findings, effectiveTestFiles) {
|
|
57
|
+
return findings.map((f) => {
|
|
58
|
+
const occurrences = f.totalOccurrences ?? f.evidence.length;
|
|
59
|
+
const result = computeFinalSeverity(f.severity, occurrences, effectiveTestFiles);
|
|
60
|
+
if (result.finalSeverity === f.severity) {
|
|
61
|
+
return f; // no escalation — return by reference
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...f,
|
|
65
|
+
baseSeverity: f.severity,
|
|
66
|
+
severity: result.finalSeverity,
|
|
67
|
+
escalationReason: result.reason,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Finding prefixes that are tied to test files.
|
|
73
|
+
* Used by reports and downstream consumers to classify findings.
|
|
74
|
+
*/
|
|
75
|
+
const TEST_SCOPED_PREFIXES = [
|
|
76
|
+
"PW-FLAKE-",
|
|
77
|
+
"PW-STABILITY-",
|
|
78
|
+
"PW-LOC-",
|
|
79
|
+
"PW-PERF-",
|
|
80
|
+
"PW-DEPREC-",
|
|
81
|
+
"CY-",
|
|
82
|
+
"SE-",
|
|
83
|
+
"DEP-",
|
|
84
|
+
];
|
|
85
|
+
function isTestScoped(findingId) {
|
|
86
|
+
return TEST_SCOPED_PREFIXES.some((p) => findingId.startsWith(p));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Convert raw risk points (unbounded, higher = worse) to a 0–100 health score
|
|
90
|
+
* (higher = better) using exponential decay.
|
|
91
|
+
*
|
|
92
|
+
* Examples:
|
|
93
|
+
* 0 risk points → 100 (perfect)
|
|
94
|
+
* 50 risk points → 78
|
|
95
|
+
* 100 risk points → 61
|
|
96
|
+
* 200 risk points → 37
|
|
97
|
+
* 300 risk points → 22
|
|
98
|
+
* 400 risk points → 14
|
|
99
|
+
*/
|
|
100
|
+
function riskPointsToHealthScore(riskPoints) {
|
|
101
|
+
return Math.round(100 * Math.exp(-riskPoints / 200));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Map a 0–100 health score to a letter grade and human-readable level.
|
|
105
|
+
* Higher score = better grade.
|
|
106
|
+
*
|
|
107
|
+
* Default thresholds: A (≥80), B (≥60), C (≥40), D (≥30), F (<30)
|
|
108
|
+
*/
|
|
109
|
+
function gradeFromHealthScore(score, thresholds) {
|
|
110
|
+
const t = thresholds ?? { gradeA: 80, gradeB: 60, gradeC: 40 };
|
|
111
|
+
if (score >= t.gradeA)
|
|
112
|
+
return { grade: "A", level: "Excellent", description: "Well-structured framework with strong practices" };
|
|
113
|
+
if (score >= t.gradeB)
|
|
114
|
+
return { grade: "B", level: "Good", description: "Solid foundation with room for improvement" };
|
|
115
|
+
if (score >= t.gradeC)
|
|
116
|
+
return { grade: "C", level: "Fair", description: "Multiple areas need attention" };
|
|
117
|
+
if (score >= 30)
|
|
118
|
+
return { grade: "D", level: "Poor", description: "Significant issues affecting reliability" };
|
|
119
|
+
return { grade: "F", level: "Critical", description: "Widespread issues requiring immediate action" };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Calculate the risk score and grade from a set of findings.
|
|
123
|
+
*
|
|
124
|
+
* This is the **single source of truth** for scoring — used by the CLI report
|
|
125
|
+
* and all result output. Any change to the formula must happen here so all
|
|
126
|
+
* consumers stay in sync.
|
|
127
|
+
*
|
|
128
|
+
* ## Density normalization
|
|
129
|
+
*
|
|
130
|
+
* When `inventory` is provided, the decay constant (lambda) is scaled by suite
|
|
131
|
+
* size so that larger test suites are evaluated more leniently. The rationale:
|
|
132
|
+
* 24 findings scattered across 981 test files is a healthier codebase than 18
|
|
133
|
+
* findings concentrated in 105 test files.
|
|
134
|
+
*
|
|
135
|
+
* The density factor scales the decay constant (λ = 200 × factor), meaning
|
|
136
|
+
* the same raw risk points produce a higher health score for larger suites.
|
|
137
|
+
* Factor is clamped to [1.0, 4.0] — small suites see no penalty, and the
|
|
138
|
+
* maximum benefit caps at 4.0× to prevent extremely large suites from
|
|
139
|
+
* masking real problems.
|
|
140
|
+
*
|
|
141
|
+
* Returns a 0–100 health score (higher = better) in the `riskScore` field.
|
|
142
|
+
*/
|
|
143
|
+
function calculateRiskScore(findings, inventory) {
|
|
144
|
+
// Weight findings by type and severity
|
|
145
|
+
const INSIGHT_MULTIPLIER = 1.5; // Correlated findings are higher signal
|
|
146
|
+
const CI_MULTIPLIER = 1.3; // CI issues affect entire pipeline
|
|
147
|
+
const ARCH_MULTIPLIER = 1.2; // Architecture issues compound over time
|
|
148
|
+
let riskPoints = 0;
|
|
149
|
+
for (const f of findings) {
|
|
150
|
+
const basePoints = severityScore(f.severity);
|
|
151
|
+
// Occurrence multiplier: N^0.6 power curve.
|
|
152
|
+
// Provides meaningful sensitivity across the full range — fixing 58 of 170
|
|
153
|
+
// occurrences produces a visible score improvement, unlike log₂ which
|
|
154
|
+
// compresses too aggressively at high counts.
|
|
155
|
+
// Uses totalOccurrences (true count) when available, since evidence arrays
|
|
156
|
+
// are capped for report brevity (typically 10-25 items).
|
|
157
|
+
//
|
|
158
|
+
// 1 occurrence → 1.00 25 occurrences → 6.90
|
|
159
|
+
// 5 occurrences → 2.63 50 occurrences → 10.46
|
|
160
|
+
// 10 occurrences → 3.98 100 occurrences → 15.85
|
|
161
|
+
const occurrences = f.totalOccurrences ?? f.evidence.length;
|
|
162
|
+
const evidenceMultiplier = Math.pow(Math.max(occurrences, 1), 0.6);
|
|
163
|
+
let multiplier = 1;
|
|
164
|
+
if (f.findingId.startsWith("PW-INSIGHT-"))
|
|
165
|
+
multiplier = INSIGHT_MULTIPLIER;
|
|
166
|
+
else if (f.findingId.startsWith("CI-"))
|
|
167
|
+
multiplier = CI_MULTIPLIER;
|
|
168
|
+
else if (f.findingId.startsWith("ARCH-"))
|
|
169
|
+
multiplier = ARCH_MULTIPLIER;
|
|
170
|
+
riskPoints += basePoints * evidenceMultiplier * multiplier;
|
|
171
|
+
}
|
|
172
|
+
// Density normalization: scale the decay constant by suite size.
|
|
173
|
+
// Larger suites get a higher lambda, so the same risk points yield a better score.
|
|
174
|
+
//
|
|
175
|
+
// densityFactor formula: 1.0 + 0.3 × log₂(effectiveTestFiles / 50)
|
|
176
|
+
// 50 files → 1.0 (neutral — no adjustment)
|
|
177
|
+
// 100 files → 1.3
|
|
178
|
+
// 200 files → 1.6
|
|
179
|
+
// 500 files → ~2.0
|
|
180
|
+
// 1000 files → ~2.3
|
|
181
|
+
// 5000 files → ~2.9
|
|
182
|
+
// < 50 files → 1.0 (floor — never penalize small suites)
|
|
183
|
+
// cap at 4.0 (reached only at ~51,000 files)
|
|
184
|
+
const BASE_LAMBDA = 200;
|
|
185
|
+
let lambda = BASE_LAMBDA;
|
|
186
|
+
let densityInfo;
|
|
187
|
+
if (inventory && findings.length > 0) {
|
|
188
|
+
const effectiveTestFiles = inventory.testFiles +
|
|
189
|
+
(inventory.cypressTestFiles ?? 0) +
|
|
190
|
+
(inventory.seleniumTestFiles ?? 0);
|
|
191
|
+
if (effectiveTestFiles > 0) {
|
|
192
|
+
const findingDensity = findings.length / effectiveTestFiles;
|
|
193
|
+
const rawFactor = 1.0 + 0.3 * Math.log2(Math.max(effectiveTestFiles / 50, 1));
|
|
194
|
+
const densityFactor = Math.min(4.0, Math.max(1.0, rawFactor));
|
|
195
|
+
lambda = BASE_LAMBDA * densityFactor;
|
|
196
|
+
densityInfo = {
|
|
197
|
+
effectiveTestFiles,
|
|
198
|
+
findingDensity: Math.round(findingDensity * 1000) / 1000,
|
|
199
|
+
densityFactor: Math.round(densityFactor * 100) / 100,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const healthScore = Math.round(100 * Math.exp(-riskPoints / lambda));
|
|
204
|
+
const gradeInfo = gradeFromHealthScore(healthScore);
|
|
205
|
+
return { ...gradeInfo, riskScore: healthScore, densityInfo };
|
|
206
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonSink = void 0;
|
|
4
|
+
exports.resolveSinkConfig = resolveSinkConfig;
|
|
5
|
+
exports.buildSinks = buildSinks;
|
|
6
|
+
const jsonSink_1 = require("./jsonSink");
|
|
7
|
+
/**
|
|
8
|
+
* Resolve sink configuration from environment variables, with optional
|
|
9
|
+
* CLI-flag overrides.
|
|
10
|
+
*/
|
|
11
|
+
function resolveSinkConfig(overrides) {
|
|
12
|
+
return {
|
|
13
|
+
jsonPath: overrides?.jsonPath ?? process.env.SCANTRIX_JSON_PATH ?? undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build the ordered list of sinks from a validated {@link SinkConfig}.
|
|
18
|
+
*
|
|
19
|
+
* JSON sink is created when `jsonPath` is set.
|
|
20
|
+
*/
|
|
21
|
+
function buildSinks(config) {
|
|
22
|
+
const sinks = [];
|
|
23
|
+
if (config.jsonPath) {
|
|
24
|
+
sinks.push(new jsonSink_1.JsonSink(config.jsonPath));
|
|
25
|
+
}
|
|
26
|
+
return sinks;
|
|
27
|
+
}
|
|
28
|
+
var jsonSink_2 = require("./jsonSink");
|
|
29
|
+
Object.defineProperty(exports, "JsonSink", { enumerable: true, get: function () { return jsonSink_2.JsonSink; } });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.JsonSink = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
/**
|
|
10
|
+
* Writes the full {@link ScanResult} as a pretty-printed JSON file.
|
|
11
|
+
*
|
|
12
|
+
* This sink is always safe to use and has no external dependencies
|
|
13
|
+
* beyond Node's built-in `fs` module. Recommended in every
|
|
14
|
+
* environment (local & CI) as a durable artifact / fallback.
|
|
15
|
+
*/
|
|
16
|
+
class JsonSink {
|
|
17
|
+
filePath;
|
|
18
|
+
name = "json";
|
|
19
|
+
constructor(filePath) {
|
|
20
|
+
this.filePath = filePath;
|
|
21
|
+
}
|
|
22
|
+
async write(result) {
|
|
23
|
+
const abs = path_1.default.resolve(this.filePath);
|
|
24
|
+
await promises_1.default.mkdir(path_1.default.dirname(abs), { recursive: true });
|
|
25
|
+
await promises_1.default.writeFile(abs, JSON.stringify(result, null, 2), "utf8");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.JsonSink = JsonSink;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 9.03 Build 0) -->
|
|
3
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
4
|
+
width="512" height="512" viewBox="-1 -7.5 527 527" xml:space="preserve">
|
|
5
|
+
<g id="BoxInterior">
|
|
6
|
+
<g id="Lambda_00000018955945252053725280000009118050138801679498_">
|
|
7
|
+
<polyline id="LeftLeg" fill="#4F7BE3" stroke="#4F7BE3" stroke-width="0.3673" stroke-miterlimit="10" points="241.313,228.973
|
|
8
|
+
62.719,454.627 144.574,454.599 276.345,284.973 "/>
|
|
9
|
+
<polygon fill="#4F7BE3" stroke="#4F7BE3" stroke-width="0.3673" stroke-miterlimit="10" points="200.662,202.76 228.653,253.062
|
|
10
|
+
342.316,453.453 460.935,453.453 460.935,381.12 382.252,381.12 194.649,54.57 62.719,54.412 62.719,112.57 149.396,112.57 "/>
|
|
11
|
+
|
|
12
|
+
<polyline id="LeftLeg_00000170258041919781009300000016929244431908451753_" fill="#4F7BE3" stroke="#4F7BE3" stroke-width="0.3673" stroke-miterlimit="10" points="
|
|
13
|
+
324.334,223.188 459.624,55.345 377.745,55.376 289.658,166.627 "/>
|
|
14
|
+
</g>
|
|
15
|
+
</g>
|
|
16
|
+
<g id="BoxEdges">
|
|
17
|
+
<path id="BottomRt" fill="#808080" d="M494.925,480.929V368.497h29.502l0.19,122.857c0.017,16.859,0.19,19.32-3.021,20.31
|
|
18
|
+
L376.932,512l0.03-29.695l84.485-0.073h33.478L494.925,480.929L494.925,480.929z"/>
|
|
19
|
+
<path id="BottomLeft" fill="#808080" d="M29.698,480.929V368.497H0.197l-0.19,122.857c-0.017,16.859-0.19,19.32,3.021,20.31
|
|
20
|
+
L147.691,512l-0.03-29.695l-84.485-0.073H29.698V480.929z"/>
|
|
21
|
+
<path id="TopRt" fill="#808080" d="M494.925,31.071v112.433h29.502l0.19-122.857c0.017-16.859,0.19-19.32-3.021-20.309L376.932,0
|
|
22
|
+
l0.03,29.695l84.485,0.073h33.478L494.925,31.071L494.925,31.071z"/>
|
|
23
|
+
<path id="TopLeft" fill="#808080" d="M29.698,31.071v112.433H0.197L0.007,20.646c-0.018-16.859-0.19-19.32,3.02-20.309L147.691,0
|
|
24
|
+
l-0.03,29.695l-84.485,0.073H29.698V31.071z"/>
|
|
25
|
+
</g>
|
|
26
|
+
</svg>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 5942.2 1528">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 1) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0, .st1 {
|
|
7
|
+
fill: #808080;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1, .st2, .st3, .st4 {
|
|
11
|
+
stroke: #4f7be3;
|
|
12
|
+
stroke-miterlimit: 10;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.st2 {
|
|
16
|
+
fill: #808080;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.st3 {
|
|
20
|
+
stroke-width: .57px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.st3, .st5, .st4 {
|
|
24
|
+
fill: #4f7be3;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.st4 {
|
|
28
|
+
stroke-width: 5.57px;
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
31
|
+
</defs>
|
|
32
|
+
<g id="ScantrixText">
|
|
33
|
+
<g id="scantrix-text">
|
|
34
|
+
<path id="S" class="st1" d="M1723.07,1059.71v-86.2h384.81c37.73,0,55.33-18.94,55.33-53.52v-73.29c0-34.58-17.61-53.52-55.33-53.52h-260.16c-100.66,0-139.03-56.88-139.03-140.15v-33.8c0-83.26,38.38-140.15,139.03-140.15h386.81v85.21h-385.02c-39.77,0-57.39,19.1-57.39,53.98v35.71c0,34.88,17.62,53.98,55.39,53.98h260.23c100.56,0,138.91,56.84,138.91,140.04v71.67c0,83.2-38.34,140.04-138.91,140.04h-384.66Z"/>
|
|
35
|
+
<path id="C" class="st1" d="M2419.1,1059.72c-88.5,0-122.24-56.91-122.24-140.21v-300.21c0-83.3,33.74-140.21,122.24-140.21h313.62v85.77h-294.62c-33.19,0-48.68,18.97-48.68,53.61v301.86c0,34.64,15.49,53.61,48.68,53.61h294.62v85.77h-313.62Z"/>
|
|
36
|
+
<path id="A" class="st1" d="M3240.7,1059.72l-170.04-467.27-172.93,467.27h-101.24l225.67-580.17h97.52l223.22,580.17h-97.2s-5,0-5,0h0Z"/>
|
|
37
|
+
<path id="N" class="st1" d="M3847.31,1060.17l-353.93-452.32,1,452.32h-89.43l-3.08-581.08h109.07l318.62,396.68-1-396.68h90.34v581.08h-64.6s-7,0-7,0h0Z"/>
|
|
38
|
+
<path id="T" class="st1" d="M4201.58,1060.84v-495.39h-219.3v-86.37h535.36v86.37h-213.62v495.39h-102.44Z"/>
|
|
39
|
+
<path id="R" class="st1" d="M5011.92,1062.63l-101.71-240h-228.71v240h-81.76V476.63h368.47c98.59,0,136.17,57.43,136.17,141.51v42.19c0,75.64-30.21,142.67-107.26,152.64l102.33,249.66h-87.54ZM5022.63,616.92c0-35.08-17.27-54.29-54.28-54.29h-286.85v172h286.85c37.01,0,54.28-39.21,54.28-74.29v-43.43Z"/>
|
|
40
|
+
<path id="I" class="st1" d="M5171.62,1065.54V476.63h96.85l-1.08,588.91h-95.77Z"/>
|
|
41
|
+
<g id="x-symbol">
|
|
42
|
+
<g id="x-lambda">
|
|
43
|
+
<polyline id="x-left-leg" class="st4" points="5596.03 736.18 5329.86 1075.12 5448.78 1075.18 5647.7 827.16"/>
|
|
44
|
+
<polygon class="st3" points="5532.07 696.07 5572.71 772.04 5737.75 1074.7 5909.98 1074.7 5909.98 965.45 5795.74 965.45 5523.34 472.25 5331.77 472.01 5331.77 559.85 5457.63 559.85 5532.07 696.07"/>
|
|
45
|
+
</g>
|
|
46
|
+
<polygon id="x-accent" class="st2" points="5717 736.18 5912.49 471.07 5806.7 471.06 5672.99 657.44 5717 736.18"/>
|
|
47
|
+
</g>
|
|
48
|
+
</g>
|
|
49
|
+
<path id="I1" data-name="I" class="st0" d="M2991.44,806.51l157.55.44,28.07,77.38-213.92-1.36"/>
|
|
50
|
+
</g>
|
|
51
|
+
<g id="BoxInterior">
|
|
52
|
+
<g id="box-lambda">
|
|
53
|
+
<polyline id="box-left-leg" class="st1" points="779.61 686.51 248.16 1260.19 485.62 1260.3 882.79 840.51"/>
|
|
54
|
+
<polygon class="st1" points="651.9 618.63 733.05 747.22 1062.57 1259.48 1406.46 1259.48 1406.46 1074.58 1178.35 1074.58 634.47 239.81 251.99 239.4 251.99 388.08 503.28 388.08 651.9 618.63"/>
|
|
55
|
+
</g>
|
|
56
|
+
<polygon id="box-accent" class="st5" points="1000.53 702.76 1403.17 243.06 1203.13 243.15 910.13 564.66 1000.53 702.76"/>
|
|
57
|
+
</g>
|
|
58
|
+
<g id="BoxEdges">
|
|
59
|
+
<path id="BottomRt" class="st5" d="M1512.87,1380.16v-303.12h79.8l.51,331.22c.05,45.45.51,52.09-8.17,54.76l-391.29.91.08-80.06,228.52-.2h90.55v-3.51Z"/>
|
|
60
|
+
<path id="BottomLeft" class="st5" d="M151.88,1380.16v-303.12h-79.8l-.51,331.22c-.05,45.45-.51,52.09,8.17,54.76l391.29.91-.08-80.06-228.51-.2h-90.55v-3.51Z"/>
|
|
61
|
+
<path id="TopRt" class="st5" d="M1512.87,147.41v303.12h79.8l.51-331.23c.05-45.45.51-52.09-8.17-54.75l-391.29-.91.08,80.06,228.52.2h90.55v3.51Z"/>
|
|
62
|
+
<path id="TopLeft" class="st5" d="M152.88,147.41v303.12h-79.8l-.51-331.23c-.05-45.45-.51-52.09,8.17-54.75l391.29-.91-.08,80.06-228.51.2h-90.55v3.51Z"/>
|
|
63
|
+
</g>
|
|
64
|
+
</svg>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 5942.2 1528">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 1) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0, .st1 {
|
|
7
|
+
fill: #e2e8f0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1, .st2, .st3, .st4 {
|
|
11
|
+
stroke: #4f7be3;
|
|
12
|
+
stroke-miterlimit: 10;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.st2 {
|
|
16
|
+
fill: #d9d9d9;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.st3 {
|
|
20
|
+
stroke-width: .57px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.st3, .st5, .st4 {
|
|
24
|
+
fill: #4f7be3;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.st4 {
|
|
28
|
+
stroke-width: 5.57px;
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
31
|
+
</defs>
|
|
32
|
+
<g id="ScantrixText">
|
|
33
|
+
<g id="scantrix-text">
|
|
34
|
+
<path id="S" class="st1" d="M1723.07,1059.71v-86.2h384.81c37.73,0,55.33-18.94,55.33-53.52v-73.29c0-34.58-17.61-53.52-55.33-53.52h-260.16c-100.66,0-139.03-56.88-139.03-140.15v-33.8c0-83.26,38.38-140.15,139.03-140.15h386.81v85.21h-385.02c-39.77,0-57.39,19.1-57.39,53.98v35.71c0,34.88,17.62,53.98,55.39,53.98h260.23c100.56,0,138.91,56.84,138.91,140.04v71.67c0,83.2-38.34,140.04-138.91,140.04h-384.66Z"/>
|
|
35
|
+
<path id="C" class="st1" d="M2419.1,1059.72c-88.5,0-122.24-56.91-122.24-140.21v-300.21c0-83.3,33.74-140.21,122.24-140.21h313.62v85.77h-294.62c-33.19,0-48.68,18.97-48.68,53.61v301.86c0,34.64,15.49,53.61,48.68,53.61h294.62v85.77h-313.62Z"/>
|
|
36
|
+
<path id="A" class="st1" d="M3240.7,1059.72l-170.04-467.27-172.93,467.27h-101.24l225.67-580.17h97.52l223.22,580.17h-97.2s-5,0-5,0h0Z"/>
|
|
37
|
+
<path id="N" class="st1" d="M3847.31,1060.17l-353.93-452.32,1,452.32h-89.43l-3.08-581.08h109.07l318.62,396.68-1-396.68h90.34v581.08h-64.6s-7,0-7,0h0Z"/>
|
|
38
|
+
<path id="T" class="st1" d="M4201.58,1060.84v-495.39h-219.3v-86.37h535.36v86.37h-213.62v495.39h-102.44Z"/>
|
|
39
|
+
<path id="R" class="st1" d="M5011.92,1062.63l-101.71-240h-228.71v240h-81.76V476.63h368.47c98.59,0,136.17,57.43,136.17,141.51v42.19c0,75.64-30.21,142.67-107.26,152.64l102.33,249.66h-87.54ZM5022.63,616.92c0-35.08-17.27-54.29-54.28-54.29h-286.85v172h286.85c37.01,0,54.28-39.21,54.28-74.29v-43.43Z"/>
|
|
40
|
+
<path id="I" class="st1" d="M5171.62,1065.54V476.63h96.85l-1.08,588.91h-95.77Z"/>
|
|
41
|
+
<g id="x-symbol">
|
|
42
|
+
<g id="x-lambda">
|
|
43
|
+
<polyline id="x-left-leg" class="st4" points="5596.03 736.18 5329.86 1075.12 5448.78 1075.18 5647.7 827.16"/>
|
|
44
|
+
<polygon class="st3" points="5532.07 696.07 5572.71 772.04 5737.75 1074.7 5909.98 1074.7 5909.98 965.45 5795.74 965.45 5523.34 472.25 5331.77 472.01 5331.77 559.85 5457.63 559.85 5532.07 696.07"/>
|
|
45
|
+
</g>
|
|
46
|
+
<polygon id="x-accent" class="st2" points="5717 736.18 5912.49 471.07 5806.7 471.06 5672.99 657.44 5717 736.18"/>
|
|
47
|
+
</g>
|
|
48
|
+
</g>
|
|
49
|
+
<path id="I1" data-name="I" class="st0" d="M2991.44,806.51l157.55.44,28.07,77.38-213.92-1.36"/>
|
|
50
|
+
</g>
|
|
51
|
+
<g id="BoxInterior">
|
|
52
|
+
<g id="box-lambda">
|
|
53
|
+
<polyline id="box-left-leg" class="st1" points="779.61 686.51 248.16 1260.19 485.62 1260.3 882.79 840.51"/>
|
|
54
|
+
<polygon class="st1" points="651.9 618.63 733.05 747.22 1062.57 1259.48 1406.46 1259.48 1406.46 1074.58 1178.35 1074.58 634.47 239.81 251.99 239.4 251.99 388.08 503.28 388.08 651.9 618.63"/>
|
|
55
|
+
</g>
|
|
56
|
+
<polygon id="box-accent" class="st5" points="1000.53 702.76 1403.17 243.06 1203.13 243.15 910.13 564.66 1000.53 702.76"/>
|
|
57
|
+
</g>
|
|
58
|
+
<g id="BoxEdges">
|
|
59
|
+
<path id="BottomRt" class="st5" d="M1512.87,1380.16v-303.12h79.8l.51,331.22c.05,45.45.51,52.09-8.17,54.76l-391.29.91.08-80.06,228.52-.2h90.55v-3.51Z"/>
|
|
60
|
+
<path id="BottomLeft" class="st5" d="M151.88,1380.16v-303.12h-79.8l-.51,331.22c-.05,45.45-.51,52.09,8.17,54.76l391.29.91-.08-80.06-228.51-.2h-90.55v-3.51Z"/>
|
|
61
|
+
<path id="TopRt" class="st5" d="M1512.87,147.41v303.12h79.8l.51-331.23c.05-45.45.51-52.09-8.17-54.75l-391.29-.91.08,80.06,228.52.2h90.55v3.51Z"/>
|
|
62
|
+
<path id="TopLeft" class="st5" d="M152.88,147.41v303.12h-79.8l-.51-331.23c-.05-45.45-.51-52.09,8.17-54.75l391.29-.91-.08,80.06-228.51.2h-90.55v3.51Z"/>
|
|
63
|
+
</g>
|
|
64
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scantrix/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Static analysis and configuration-aware auditing engine for automation repositories",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"scantrix": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/cli.js",
|
|
12
|
+
"audit": "npm run build && node dist/cli.js",
|
|
13
|
+
"scan": "npm run audit",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"generate:favicons": "ts-node scripts/generate-favicons.ts"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"playwright",
|
|
20
|
+
"cypress",
|
|
21
|
+
"selenium",
|
|
22
|
+
"test-automation",
|
|
23
|
+
"static-analysis",
|
|
24
|
+
"audit",
|
|
25
|
+
"ci",
|
|
26
|
+
"quality"
|
|
27
|
+
],
|
|
28
|
+
"author": "Scantrix",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/",
|
|
35
|
+
"docs/*.svg",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"type": "commonjs",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"fast-glob": "^3.3.3",
|
|
42
|
+
"markdown-it": "^14.1.0",
|
|
43
|
+
"minimist": "^1.2.8"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/markdown-it": "^14.1.2",
|
|
47
|
+
"@types/minimist": "^1.2.5",
|
|
48
|
+
"@types/node": "^25.1.0",
|
|
49
|
+
"@types/sharp": "^0.31.1",
|
|
50
|
+
"sharp": "^0.34.5",
|
|
51
|
+
"ts-node": "^10.9.2",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^3.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|