sickbay 1.7.4 → 1.8.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/dist/{DiffApp-RLMGNCUO.js → DiffApp-CG2DRKTU.js} +2 -2
- package/dist/{DoctorApp-YTLQ2DS6.js → DoctorApp-XVZMFDLM.js} +3 -3
- package/dist/{FixApp-AL2OABBZ.js → FixApp-46TGLR3C.js} +3 -3
- package/dist/{StatsApp-IDK6NZ26.js → StatsApp-5YMWUNHQ.js} +3 -3
- package/dist/{TrendApp-XKUCHCA3.js → TrendApp-WMFR4L3A.js} +3 -3
- package/dist/{TuiApp-7QFQPAVR.js → TuiApp-DDC2E3SZ.js} +3 -3
- package/dist/{chunk-DRBWOJSY.js → chunk-EONIXEDB.js} +1 -1
- package/dist/{chunk-QT7EP2BN.js → chunk-LRBDCBJG.js} +1 -1
- package/dist/{chunk-EXMWNJFU.js → chunk-NYE7UH3H.js} +162 -57
- package/dist/config.js +1 -1
- package/dist/{dist-GBGEDULY.js → dist-PJQ2AQQL.js} +3 -1
- package/dist/index.js +21 -21
- package/dist/{init-Y6ZEUZBY.js → init-WBLDUGEB.js} +1 -1
- package/dist/{resolve-package-I6ECLCON.js → resolve-package-7S4ZB3OA.js} +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Header
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EONIXEDB.js";
|
|
4
4
|
|
|
5
5
|
// src/components/DiffApp.tsx
|
|
6
6
|
import React, { useState, useEffect } from "react";
|
|
@@ -119,7 +119,7 @@ function DiffApp({ projectPath, branch, jsonOutput, checks, verbose }) {
|
|
|
119
119
|
useEffect(() => {
|
|
120
120
|
(async () => {
|
|
121
121
|
try {
|
|
122
|
-
const { runSickbay } = await import("./dist-
|
|
122
|
+
const { runSickbay } = await import("./dist-PJQ2AQQL.js");
|
|
123
123
|
const currentReport = await runSickbay({
|
|
124
124
|
projectPath,
|
|
125
125
|
checks,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
shortName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LRBDCBJG.js";
|
|
4
4
|
import {
|
|
5
5
|
Header
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EONIXEDB.js";
|
|
7
7
|
import {
|
|
8
8
|
detectPackageManager,
|
|
9
9
|
detectProject
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NYE7UH3H.js";
|
|
11
11
|
|
|
12
12
|
// src/components/DoctorApp.tsx
|
|
13
13
|
import React, { useState, useEffect } from "react";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
shortName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LRBDCBJG.js";
|
|
4
4
|
import {
|
|
5
5
|
ProgressList
|
|
6
6
|
} from "./chunk-MBVA75EM.js";
|
|
7
7
|
import {
|
|
8
8
|
Header
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EONIXEDB.js";
|
|
10
10
|
import {
|
|
11
11
|
runSickbay
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-NYE7UH3H.js";
|
|
13
13
|
|
|
14
14
|
// src/components/FixApp.tsx
|
|
15
15
|
import React, { useState, useEffect, useCallback } from "react";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
shortName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LRBDCBJG.js";
|
|
4
4
|
import {
|
|
5
5
|
Header
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-EONIXEDB.js";
|
|
7
7
|
import {
|
|
8
8
|
detectProject
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-NYE7UH3H.js";
|
|
10
10
|
|
|
11
11
|
// src/components/StatsApp.tsx
|
|
12
12
|
import React, { useState, useEffect } from "react";
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
} from "./chunk-SHO3ZXTH.js";
|
|
5
5
|
import {
|
|
6
6
|
shortName
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LRBDCBJG.js";
|
|
8
8
|
import {
|
|
9
9
|
Header
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-EONIXEDB.js";
|
|
11
|
+
import "./chunk-NYE7UH3H.js";
|
|
12
12
|
import {
|
|
13
13
|
detectRegressions,
|
|
14
14
|
loadHistory
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
detectMonorepo,
|
|
15
15
|
runSickbay,
|
|
16
16
|
runSickbayMonorepo
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NYE7UH3H.js";
|
|
18
18
|
import {
|
|
19
19
|
loadHistory
|
|
20
20
|
} from "./chunk-3OR2GFVE.js";
|
|
@@ -689,7 +689,7 @@ function TuiApp({
|
|
|
689
689
|
return () => timers.forEach(clearTimeout);
|
|
690
690
|
}, []);
|
|
691
691
|
useEffect7(() => {
|
|
692
|
-
checkForUpdate("1.7.
|
|
692
|
+
checkForUpdate("1.7.5").then((info) => {
|
|
693
693
|
if (info) setUpdateInfo(info);
|
|
694
694
|
});
|
|
695
695
|
}, []);
|
|
@@ -715,7 +715,7 @@ function TuiApp({
|
|
|
715
715
|
} catch {
|
|
716
716
|
}
|
|
717
717
|
try {
|
|
718
|
-
const { getDependencyTree } = await import("./dist-
|
|
718
|
+
const { getDependencyTree } = await import("./dist-PJQ2AQQL.js");
|
|
719
719
|
const { saveDepTree } = await import("./history-XLNVZEDI.js");
|
|
720
720
|
const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);
|
|
721
721
|
saveDepTree(projectPath, tree);
|
|
@@ -11,7 +11,7 @@ var ASCII_ART = `
|
|
|
11
11
|
A vitals health check for your app
|
|
12
12
|
`.trim();
|
|
13
13
|
function Header({ projectName }) {
|
|
14
|
-
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, ASCII_ART), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " v", "1.7.
|
|
14
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, ASCII_ART), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " v", "1.7.5")), projectName && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " Analyzing "), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "white" }, projectName)));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export {
|
|
@@ -87,6 +87,12 @@ function defineConfig(config) {
|
|
|
87
87
|
return config;
|
|
88
88
|
}
|
|
89
89
|
var CONFIG_FILES = ["sickbay.config.ts", "sickbay.config.js", "sickbay.config.mjs"];
|
|
90
|
+
function getCheckConfig(config, checkId) {
|
|
91
|
+
if (!config?.checks) return null;
|
|
92
|
+
const entry = config.checks[checkId];
|
|
93
|
+
if (typeof entry === "object" && entry.enabled !== false) return entry;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
90
96
|
function isCheckDisabled(config, checkId) {
|
|
91
97
|
if (!config?.checks) return false;
|
|
92
98
|
const entry = config.checks[checkId];
|
|
@@ -114,6 +120,29 @@ function resolveConfigMeta(config) {
|
|
|
114
120
|
const hasCustomConfig = disabledChecks.length > 0 || overriddenChecks.length > 0 || config.weights !== void 0 && Object.keys(config.weights).length > 0 || config.exclude !== void 0 && config.exclude.length > 0;
|
|
115
121
|
return { hasCustomConfig, overriddenChecks, disabledChecks };
|
|
116
122
|
}
|
|
123
|
+
var KNOWN_THRESHOLD_KEYS = {
|
|
124
|
+
outdated: ["maxOutdated"],
|
|
125
|
+
complexity: [
|
|
126
|
+
"react-component",
|
|
127
|
+
"custom-hook",
|
|
128
|
+
"node-service",
|
|
129
|
+
"route-file",
|
|
130
|
+
"ts-utility",
|
|
131
|
+
"config",
|
|
132
|
+
"test",
|
|
133
|
+
"general"
|
|
134
|
+
],
|
|
135
|
+
jscpd: ["warnPercent", "criticalPercent"],
|
|
136
|
+
coverage: ["lines", "functions"],
|
|
137
|
+
eslint: ["maxErrors"],
|
|
138
|
+
typescript: ["maxErrors"],
|
|
139
|
+
madge: ["maxCircular"],
|
|
140
|
+
"todo-scanner": ["patterns"],
|
|
141
|
+
"asset-size": ["imageWarn", "imageCritical", "svgWarn", "fontWarn", "totalWarn", "totalCritical"],
|
|
142
|
+
"source-map-explorer": ["warnSize", "failSize"],
|
|
143
|
+
"license-checker": ["blocklist"],
|
|
144
|
+
git: ["staleMonths", "maxRemoteBranches"]
|
|
145
|
+
};
|
|
117
146
|
function validateConfig(config, knownCheckIds) {
|
|
118
147
|
if (config.weights) {
|
|
119
148
|
for (const [category, value] of Object.entries(config.weights)) {
|
|
@@ -123,12 +152,26 @@ function validateConfig(config, knownCheckIds) {
|
|
|
123
152
|
}
|
|
124
153
|
}
|
|
125
154
|
if (config.checks) {
|
|
126
|
-
for (const checkId of Object.
|
|
155
|
+
for (const [checkId, entry] of Object.entries(config.checks)) {
|
|
127
156
|
if (!knownCheckIds.includes(checkId)) {
|
|
128
157
|
process.stderr.write(
|
|
129
158
|
`Warning: Unknown check "${checkId}" in sickbay.config \u2014 it will be ignored.
|
|
130
159
|
`
|
|
131
160
|
);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (typeof entry === "object" && entry.thresholds) {
|
|
164
|
+
const knownKeys = KNOWN_THRESHOLD_KEYS[checkId];
|
|
165
|
+
if (knownKeys) {
|
|
166
|
+
for (const key of Object.keys(entry.thresholds)) {
|
|
167
|
+
if (!knownKeys.includes(key)) {
|
|
168
|
+
process.stderr.write(
|
|
169
|
+
`Warning: Unknown threshold key "${key}" for check "${checkId}" \u2014 it will be ignored.
|
|
170
|
+
`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
132
175
|
}
|
|
133
176
|
}
|
|
134
177
|
}
|
|
@@ -213,6 +256,8 @@ function parseJsonOutput(stdout, fallback = "{}") {
|
|
|
213
256
|
return JSON.parse(fallback);
|
|
214
257
|
}
|
|
215
258
|
var BaseRunner = class {
|
|
259
|
+
/** Human-readable display name. Falls back to `name` (the check ID) if not set. */
|
|
260
|
+
displayName;
|
|
216
261
|
applicableFrameworks;
|
|
217
262
|
applicableRuntimes;
|
|
218
263
|
isApplicableToContext(context) {
|
|
@@ -233,7 +278,7 @@ var BaseRunner = class {
|
|
|
233
278
|
return {
|
|
234
279
|
id: this.name,
|
|
235
280
|
category: this.category,
|
|
236
|
-
name: this.name,
|
|
281
|
+
name: this.displayName ?? this.name,
|
|
237
282
|
score: 100,
|
|
238
283
|
status: "skipped",
|
|
239
284
|
issues: [],
|
|
@@ -981,8 +1026,15 @@ var AssetSizeRunner = class extends BaseRunner {
|
|
|
981
1026
|
name = "asset-size";
|
|
982
1027
|
category = "performance";
|
|
983
1028
|
applicableRuntimes = ["browser"];
|
|
984
|
-
async run(projectPath) {
|
|
1029
|
+
async run(projectPath, options) {
|
|
985
1030
|
const elapsed = timer();
|
|
1031
|
+
const t = options?.checkConfig?.thresholds;
|
|
1032
|
+
const imageWarn = t?.imageWarn ?? IMAGE_WARN;
|
|
1033
|
+
const imageCritical = t?.imageCritical ?? IMAGE_CRITICAL;
|
|
1034
|
+
const svgWarn = t?.svgWarn ?? SVG_WARN;
|
|
1035
|
+
const fontWarn = t?.fontWarn ?? FONT_WARN;
|
|
1036
|
+
const totalWarn = t?.totalWarn ?? TOTAL_WARN;
|
|
1037
|
+
const totalCritical = t?.totalCritical ?? TOTAL_CRITICAL;
|
|
986
1038
|
try {
|
|
987
1039
|
const assets = [];
|
|
988
1040
|
for (const dir of ASSET_DIRS) {
|
|
@@ -997,7 +1049,7 @@ var AssetSizeRunner = class extends BaseRunner {
|
|
|
997
1049
|
const sizeKB = Math.round(asset.size / 1024);
|
|
998
1050
|
const sizeMB = (asset.size / (1024 * 1024)).toFixed(1);
|
|
999
1051
|
if (asset.type === "image") {
|
|
1000
|
-
if (asset.size >
|
|
1052
|
+
if (asset.size > imageCritical) {
|
|
1001
1053
|
issues.push({
|
|
1002
1054
|
severity: "critical",
|
|
1003
1055
|
message: `${asset.path} \u2014 ${sizeMB}MB image (exceeds 2MB)`,
|
|
@@ -1007,7 +1059,7 @@ var AssetSizeRunner = class extends BaseRunner {
|
|
|
1007
1059
|
},
|
|
1008
1060
|
reportedBy: ["asset-size"]
|
|
1009
1061
|
});
|
|
1010
|
-
} else if (asset.size >
|
|
1062
|
+
} else if (asset.size > imageWarn) {
|
|
1011
1063
|
issues.push({
|
|
1012
1064
|
severity: "warning",
|
|
1013
1065
|
message: `${asset.path} \u2014 ${sizeKB}KB image (exceeds 500KB)`,
|
|
@@ -1017,7 +1069,7 @@ var AssetSizeRunner = class extends BaseRunner {
|
|
|
1017
1069
|
});
|
|
1018
1070
|
}
|
|
1019
1071
|
} else if (asset.type === "svg") {
|
|
1020
|
-
if (asset.size >
|
|
1072
|
+
if (asset.size > svgWarn) {
|
|
1021
1073
|
issues.push({
|
|
1022
1074
|
severity: "warning",
|
|
1023
1075
|
message: `${asset.path} \u2014 ${sizeKB}KB SVG (exceeds 100KB, likely unoptimized)`,
|
|
@@ -1029,7 +1081,7 @@ var AssetSizeRunner = class extends BaseRunner {
|
|
|
1029
1081
|
});
|
|
1030
1082
|
}
|
|
1031
1083
|
} else if (asset.type === "font") {
|
|
1032
|
-
if (asset.size >
|
|
1084
|
+
if (asset.size > fontWarn) {
|
|
1033
1085
|
issues.push({
|
|
1034
1086
|
severity: "warning",
|
|
1035
1087
|
message: `${asset.path} \u2014 ${sizeKB}KB font (exceeds 500KB)`,
|
|
@@ -1043,14 +1095,14 @@ var AssetSizeRunner = class extends BaseRunner {
|
|
|
1043
1095
|
}
|
|
1044
1096
|
}
|
|
1045
1097
|
const totalMB = (totalSize / (1024 * 1024)).toFixed(1);
|
|
1046
|
-
if (totalSize >
|
|
1098
|
+
if (totalSize > totalCritical) {
|
|
1047
1099
|
issues.push({
|
|
1048
1100
|
severity: "critical",
|
|
1049
1101
|
message: `Total asset size is ${totalMB}MB \u2014 exceeds 10MB threshold`,
|
|
1050
1102
|
fix: { description: "Review and optimize all static assets to reduce total payload" },
|
|
1051
1103
|
reportedBy: ["asset-size"]
|
|
1052
1104
|
});
|
|
1053
|
-
} else if (totalSize >
|
|
1105
|
+
} else if (totalSize > totalWarn) {
|
|
1054
1106
|
issues.push({
|
|
1055
1107
|
severity: "warning",
|
|
1056
1108
|
message: `Total asset size is ${totalMB}MB \u2014 consider optimizing`,
|
|
@@ -1201,8 +1253,25 @@ var ComplexityRunner = class extends BaseRunner {
|
|
|
1201
1253
|
async isApplicable(projectPath) {
|
|
1202
1254
|
return SOURCE_DIRS.some((dir) => existsSync5(join11(projectPath, dir)));
|
|
1203
1255
|
}
|
|
1204
|
-
async run(projectPath) {
|
|
1256
|
+
async run(projectPath, options) {
|
|
1205
1257
|
const elapsed = timer();
|
|
1258
|
+
const userThresholds = options?.checkConfig?.thresholds;
|
|
1259
|
+
const mergedThresholds = { ...FILE_TYPE_THRESHOLDS };
|
|
1260
|
+
if (userThresholds) {
|
|
1261
|
+
for (const [key, overrides] of Object.entries(userThresholds)) {
|
|
1262
|
+
const ft = key;
|
|
1263
|
+
if (mergedThresholds[ft] && overrides) {
|
|
1264
|
+
mergedThresholds[ft] = {
|
|
1265
|
+
warn: overrides.warn ?? mergedThresholds[ft].warn,
|
|
1266
|
+
critical: overrides.critical ?? mergedThresholds[ft].critical
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const resolveThresholds = (filePath) => {
|
|
1272
|
+
const fileType = classifyFile(filePath);
|
|
1273
|
+
return { ...mergedThresholds[fileType], fileType };
|
|
1274
|
+
};
|
|
1206
1275
|
try {
|
|
1207
1276
|
const files = SOURCE_DIRS.flatMap(
|
|
1208
1277
|
(dir) => existsSync5(join11(projectPath, dir)) ? scanDirectory(join11(projectPath, dir), projectPath) : []
|
|
@@ -1210,7 +1279,7 @@ var ComplexityRunner = class extends BaseRunner {
|
|
|
1210
1279
|
const issues = [];
|
|
1211
1280
|
let oversizedCount = 0;
|
|
1212
1281
|
for (const f of files) {
|
|
1213
|
-
const { warn, critical, fileType } =
|
|
1282
|
+
const { warn, critical, fileType } = resolveThresholds(f.path);
|
|
1214
1283
|
if (f.lines >= warn) {
|
|
1215
1284
|
oversizedCount++;
|
|
1216
1285
|
const label = getFileTypeLabel(fileType);
|
|
@@ -1226,7 +1295,7 @@ var ComplexityRunner = class extends BaseRunner {
|
|
|
1226
1295
|
const avgLines = files.length > 0 ? Math.round(totalLines / files.length) : 0;
|
|
1227
1296
|
const score = Math.max(0, 100 - oversizedCount * 10);
|
|
1228
1297
|
const topFiles = [...files].sort((a, b) => b.lines - a.lines).slice(0, 10).map((f) => {
|
|
1229
|
-
const { warn, critical, fileType } =
|
|
1298
|
+
const { warn, critical, fileType } = resolveThresholds(f.path);
|
|
1230
1299
|
return { ...f, fileType, warn, critical };
|
|
1231
1300
|
});
|
|
1232
1301
|
return {
|
|
@@ -1454,11 +1523,14 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1454
1523
|
}
|
|
1455
1524
|
return true;
|
|
1456
1525
|
}
|
|
1457
|
-
async run(projectPath) {
|
|
1526
|
+
async run(projectPath, options) {
|
|
1458
1527
|
const elapsed = timer();
|
|
1528
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
1529
|
+
const lineTarget = thresholds?.lines ?? 80;
|
|
1530
|
+
const functionTarget = thresholds?.functions ?? 80;
|
|
1459
1531
|
const runner = this.detectTestRunner(projectPath);
|
|
1460
1532
|
if (!runner) {
|
|
1461
|
-
return this.readExistingCoverage(projectPath, elapsed);
|
|
1533
|
+
return this.readExistingCoverage(projectPath, elapsed, lineTarget);
|
|
1462
1534
|
}
|
|
1463
1535
|
try {
|
|
1464
1536
|
const hasCoverage = this.hasCoverageProvider(projectPath, runner);
|
|
@@ -1513,7 +1585,9 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1513
1585
|
coverageData,
|
|
1514
1586
|
runner,
|
|
1515
1587
|
hasCoverage,
|
|
1516
|
-
packageManager
|
|
1588
|
+
packageManager,
|
|
1589
|
+
lineTarget,
|
|
1590
|
+
functionTarget
|
|
1517
1591
|
);
|
|
1518
1592
|
} catch (err) {
|
|
1519
1593
|
return {
|
|
@@ -1530,7 +1604,7 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1530
1604
|
};
|
|
1531
1605
|
}
|
|
1532
1606
|
}
|
|
1533
|
-
buildResult(elapsed, counts, coverage, runner, hasCoverage, packageManager = "npm") {
|
|
1607
|
+
buildResult(elapsed, counts, coverage, runner, hasCoverage, packageManager = "npm", lineTarget = 80, functionTarget = 80) {
|
|
1534
1608
|
const issues = [];
|
|
1535
1609
|
if (counts.failed > 0) {
|
|
1536
1610
|
issues.push({
|
|
@@ -1541,18 +1615,18 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1541
1615
|
});
|
|
1542
1616
|
}
|
|
1543
1617
|
if (coverage) {
|
|
1544
|
-
if (coverage.lines.pct <
|
|
1618
|
+
if (coverage.lines.pct < lineTarget) {
|
|
1545
1619
|
issues.push({
|
|
1546
1620
|
severity: coverage.lines.pct < 50 ? "critical" : "warning",
|
|
1547
|
-
message: `Line coverage: ${coverage.lines.pct.toFixed(1)}% (target:
|
|
1621
|
+
message: `Line coverage: ${coverage.lines.pct.toFixed(1)}% (target: ${lineTarget}%)`,
|
|
1548
1622
|
fix: { description: "Add tests to improve coverage" },
|
|
1549
1623
|
reportedBy: ["coverage"]
|
|
1550
1624
|
});
|
|
1551
1625
|
}
|
|
1552
|
-
if (coverage.functions.pct <
|
|
1626
|
+
if (coverage.functions.pct < functionTarget) {
|
|
1553
1627
|
issues.push({
|
|
1554
1628
|
severity: "warning",
|
|
1555
|
-
message: `Function coverage: ${coverage.functions.pct.toFixed(1)}% (target:
|
|
1629
|
+
message: `Function coverage: ${coverage.functions.pct.toFixed(1)}% (target: ${functionTarget}%)`,
|
|
1556
1630
|
fix: { description: "Add tests for uncovered functions" },
|
|
1557
1631
|
reportedBy: ["coverage"]
|
|
1558
1632
|
});
|
|
@@ -1576,7 +1650,7 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1576
1650
|
const covAvg = (coverage.lines.pct + coverage.statements.pct + coverage.functions.pct + coverage.branches.pct) / 4;
|
|
1577
1651
|
score = Math.round(score * 0.6 + covAvg * 0.4);
|
|
1578
1652
|
}
|
|
1579
|
-
const status = counts.failed > 0 ? "fail" : coverage && coverage.lines.pct < 50 ? "fail" : coverage && coverage.lines.pct <
|
|
1653
|
+
const status = counts.failed > 0 ? "fail" : coverage && coverage.lines.pct < 50 ? "fail" : coverage && coverage.lines.pct < lineTarget ? "warning" : issues.length > 0 ? "warning" : "pass";
|
|
1580
1654
|
return {
|
|
1581
1655
|
id: "coverage",
|
|
1582
1656
|
category: this.category,
|
|
@@ -1601,7 +1675,7 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1601
1675
|
}
|
|
1602
1676
|
};
|
|
1603
1677
|
}
|
|
1604
|
-
readExistingCoverage(projectPath, elapsed) {
|
|
1678
|
+
readExistingCoverage(projectPath, elapsed, lineTarget = 80) {
|
|
1605
1679
|
const coveragePath = COVERAGE_PATHS.map((p) => join13(projectPath, p)).find(existsSync7);
|
|
1606
1680
|
if (!coveragePath) {
|
|
1607
1681
|
return this.skipped("No test runner or coverage report found");
|
|
@@ -1615,10 +1689,10 @@ var CoverageRunner = class extends BaseRunner {
|
|
|
1615
1689
|
const { lines, statements, functions, branches } = candidate;
|
|
1616
1690
|
const avg = (lines.pct + statements.pct + functions.pct + branches.pct) / 4;
|
|
1617
1691
|
const issues = [];
|
|
1618
|
-
if (lines.pct <
|
|
1692
|
+
if (lines.pct < lineTarget) {
|
|
1619
1693
|
issues.push({
|
|
1620
1694
|
severity: lines.pct < 50 ? "critical" : "warning",
|
|
1621
|
-
message: `Line coverage: ${lines.pct.toFixed(1)}% (target:
|
|
1695
|
+
message: `Line coverage: ${lines.pct.toFixed(1)}% (target: ${lineTarget}%)`,
|
|
1622
1696
|
reportedBy: ["coverage"]
|
|
1623
1697
|
});
|
|
1624
1698
|
}
|
|
@@ -1724,8 +1798,10 @@ var ESLintRunner = class extends BaseRunner {
|
|
|
1724
1798
|
async isApplicable(projectPath) {
|
|
1725
1799
|
return existsSync8(join14(projectPath, ".eslintrc.js")) || existsSync8(join14(projectPath, ".eslintrc.cjs")) || existsSync8(join14(projectPath, ".eslintrc.json")) || existsSync8(join14(projectPath, ".eslintrc.yml")) || existsSync8(join14(projectPath, "eslint.config.js")) || existsSync8(join14(projectPath, "eslint.config.mjs")) || existsSync8(join14(projectPath, "eslint.config.cjs"));
|
|
1726
1800
|
}
|
|
1727
|
-
async run(projectPath) {
|
|
1801
|
+
async run(projectPath, options) {
|
|
1728
1802
|
const elapsed = timer();
|
|
1803
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
1804
|
+
const maxErrors = thresholds?.maxErrors ?? 10;
|
|
1729
1805
|
try {
|
|
1730
1806
|
const candidateDirs = ["src", "lib", "app"];
|
|
1731
1807
|
const dirsToScan = candidateDirs.filter((d) => existsSync8(join14(projectPath, d)));
|
|
@@ -1769,7 +1845,7 @@ var ESLintRunner = class extends BaseRunner {
|
|
|
1769
1845
|
category: this.category,
|
|
1770
1846
|
name: "Lint",
|
|
1771
1847
|
score,
|
|
1772
|
-
status: totalErrors >
|
|
1848
|
+
status: totalErrors > maxErrors ? "fail" : totalErrors > 0 ? "warning" : totalWarnings > 0 ? "warning" : "pass",
|
|
1773
1849
|
issues,
|
|
1774
1850
|
toolsUsed: ["eslint"],
|
|
1775
1851
|
duration: elapsed(),
|
|
@@ -1797,8 +1873,11 @@ var GitRunner = class extends BaseRunner {
|
|
|
1797
1873
|
async isApplicable(projectPath) {
|
|
1798
1874
|
return fileExists(projectPath, ".git");
|
|
1799
1875
|
}
|
|
1800
|
-
async run(projectPath) {
|
|
1876
|
+
async run(projectPath, options) {
|
|
1801
1877
|
const elapsed = timer();
|
|
1878
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
1879
|
+
const staleMonths = thresholds?.staleMonths ?? 6;
|
|
1880
|
+
const maxRemoteBranches = thresholds?.maxRemoteBranches ?? 20;
|
|
1802
1881
|
try {
|
|
1803
1882
|
const [lastCommitResult, logCountResult, contributorsResult] = await Promise.allSettled([
|
|
1804
1883
|
execa5("git", ["log", "-1", "--format=%cr"], { cwd: projectPath }),
|
|
@@ -1819,7 +1898,7 @@ var GitRunner = class extends BaseRunner {
|
|
|
1819
1898
|
const commitCount = logCountResult.status === "fulfilled" ? parseInt(logCountResult.value.stdout.trim(), 10) : 0;
|
|
1820
1899
|
const contributorCount = contributorsResult.status === "fulfilled" ? contributorsResult.value.stdout.trim().split("\n").filter(Boolean).length : 0;
|
|
1821
1900
|
const issues = [];
|
|
1822
|
-
const isStale = lastCommit.includes("year") || lastCommit.includes("month") && parseInt(lastCommit) >
|
|
1901
|
+
const isStale = lastCommit.includes("year") || lastCommit.includes("month") && parseInt(lastCommit) > staleMonths;
|
|
1823
1902
|
if (isStale) {
|
|
1824
1903
|
issues.push({
|
|
1825
1904
|
severity: "warning",
|
|
@@ -1827,7 +1906,7 @@ var GitRunner = class extends BaseRunner {
|
|
|
1827
1906
|
reportedBy: ["git"]
|
|
1828
1907
|
});
|
|
1829
1908
|
}
|
|
1830
|
-
if (remoteBranches >
|
|
1909
|
+
if (remoteBranches > maxRemoteBranches) {
|
|
1831
1910
|
issues.push({
|
|
1832
1911
|
severity: "info",
|
|
1833
1912
|
message: `${remoteBranches} remote branches \u2014 consider pruning stale branches`,
|
|
@@ -2025,8 +2104,11 @@ var HeavyDepsRunner = class extends BaseRunner {
|
|
|
2025
2104
|
var JscpdRunner = class extends BaseRunner {
|
|
2026
2105
|
name = "jscpd";
|
|
2027
2106
|
category = "code-quality";
|
|
2028
|
-
async run(projectPath) {
|
|
2107
|
+
async run(projectPath, options) {
|
|
2029
2108
|
const elapsed = timer();
|
|
2109
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
2110
|
+
const warnPercent = thresholds?.warnPercent ?? 5;
|
|
2111
|
+
const criticalPercent = thresholds?.criticalPercent ?? 20;
|
|
2030
2112
|
const available = await isCommandAvailable("jscpd");
|
|
2031
2113
|
if (!available) {
|
|
2032
2114
|
return this.skipped("jscpd not installed \u2014 run: npm i -g jscpd");
|
|
@@ -2050,9 +2132,9 @@ var JscpdRunner = class extends BaseRunner {
|
|
|
2050
2132
|
const percentage = data.statistics?.total.percentage ?? 0;
|
|
2051
2133
|
const clones = data.statistics?.total.clones ?? 0;
|
|
2052
2134
|
const issues = [];
|
|
2053
|
-
if (percentage >
|
|
2135
|
+
if (percentage > warnPercent) {
|
|
2054
2136
|
issues.push({
|
|
2055
|
-
severity: percentage >
|
|
2137
|
+
severity: percentage > criticalPercent ? "critical" : "warning",
|
|
2056
2138
|
message: `${percentage.toFixed(1)}% code duplication detected (${clones} clone${clones !== 1 ? "s" : ""})`,
|
|
2057
2139
|
fix: {
|
|
2058
2140
|
description: "Extract duplicated code into shared utilities or components"
|
|
@@ -2065,7 +2147,7 @@ var JscpdRunner = class extends BaseRunner {
|
|
|
2065
2147
|
category: this.category,
|
|
2066
2148
|
name: "Code Duplication",
|
|
2067
2149
|
score: Math.max(0, 100 - Math.round(percentage * 3)),
|
|
2068
|
-
status: percentage === 0 ? "pass" : percentage >
|
|
2150
|
+
status: percentage === 0 ? "pass" : percentage > criticalPercent ? "fail" : percentage > warnPercent ? "warning" : "pass",
|
|
2069
2151
|
issues,
|
|
2070
2152
|
toolsUsed: ["jscpd"],
|
|
2071
2153
|
duration: elapsed(),
|
|
@@ -2217,8 +2299,10 @@ var PROBLEMATIC_LICENSES = ["GPL-2.0", "GPL-3.0", "AGPL-3.0", "LGPL-2.1", "LGPL-
|
|
|
2217
2299
|
var LicenseCheckerRunner = class extends BaseRunner {
|
|
2218
2300
|
name = "license-checker";
|
|
2219
2301
|
category = "security";
|
|
2220
|
-
async run(projectPath) {
|
|
2302
|
+
async run(projectPath, options) {
|
|
2221
2303
|
const elapsed = timer();
|
|
2304
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
2305
|
+
const blocklist = thresholds?.blocklist ?? PROBLEMATIC_LICENSES;
|
|
2222
2306
|
const available = await isCommandAvailable("license-checker");
|
|
2223
2307
|
if (!available) {
|
|
2224
2308
|
return this.skipped("license-checker not installed \u2014 run: npm i -g license-checker");
|
|
@@ -2234,7 +2318,7 @@ var LicenseCheckerRunner = class extends BaseRunner {
|
|
|
2234
2318
|
const issues = [];
|
|
2235
2319
|
for (const [pkg, info] of Object.entries(licenses)) {
|
|
2236
2320
|
const license = info.licenses;
|
|
2237
|
-
if (
|
|
2321
|
+
if (blocklist.some((l) => license.includes(l))) {
|
|
2238
2322
|
issues.push({
|
|
2239
2323
|
severity: "warning",
|
|
2240
2324
|
message: `${pkg} uses ${license} license \u2014 may be incompatible with commercial use`,
|
|
@@ -2309,8 +2393,10 @@ function findCircularDeps(graph) {
|
|
|
2309
2393
|
var MadgeRunner = class extends BaseRunner {
|
|
2310
2394
|
name = "madge";
|
|
2311
2395
|
category = "code-quality";
|
|
2312
|
-
async run(projectPath) {
|
|
2396
|
+
async run(projectPath, options) {
|
|
2313
2397
|
const elapsed = timer();
|
|
2398
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
2399
|
+
const maxCircular = thresholds?.maxCircular ?? 5;
|
|
2314
2400
|
const available = await isCommandAvailable("madge");
|
|
2315
2401
|
if (!available) {
|
|
2316
2402
|
return this.skipped("madge not installed \u2014 run: npm i -g madge");
|
|
@@ -2345,7 +2431,7 @@ var MadgeRunner = class extends BaseRunner {
|
|
|
2345
2431
|
category: this.category,
|
|
2346
2432
|
name: "Circular Dependencies",
|
|
2347
2433
|
score: circles.length === 0 ? 100 : Math.max(0, 100 - circles.length * 10),
|
|
2348
|
-
status: circles.length === 0 ? "pass" : circles.length >
|
|
2434
|
+
status: circles.length === 0 ? "pass" : circles.length > maxCircular ? "fail" : "warning",
|
|
2349
2435
|
issues,
|
|
2350
2436
|
toolsUsed: ["madge"],
|
|
2351
2437
|
duration: elapsed(),
|
|
@@ -3298,9 +3384,11 @@ var NpmAuditRunner = class extends BaseRunner {
|
|
|
3298
3384
|
var OutdatedRunner = class extends BaseRunner {
|
|
3299
3385
|
name = "outdated";
|
|
3300
3386
|
category = "dependencies";
|
|
3301
|
-
async run(projectPath) {
|
|
3387
|
+
async run(projectPath, options) {
|
|
3302
3388
|
const elapsed = timer();
|
|
3303
3389
|
const pm = detectPackageManager(projectPath);
|
|
3390
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
3391
|
+
const maxOutdated = thresholds?.maxOutdated ?? 15;
|
|
3304
3392
|
if (pm === "yarn" || pm === "bun") {
|
|
3305
3393
|
return this.skipped(`${pm} outdated not supported \u2014 run: ${pm} outdated`);
|
|
3306
3394
|
}
|
|
@@ -3333,7 +3421,7 @@ var OutdatedRunner = class extends BaseRunner {
|
|
|
3333
3421
|
category: this.category,
|
|
3334
3422
|
name: "Outdated Packages",
|
|
3335
3423
|
score: Math.max(0, 100 - count * 3),
|
|
3336
|
-
status: count === 0 ? "pass" : count >
|
|
3424
|
+
status: count === 0 ? "pass" : count > maxOutdated ? "fail" : "warning",
|
|
3337
3425
|
issues,
|
|
3338
3426
|
toolsUsed: [pm],
|
|
3339
3427
|
duration: elapsed(),
|
|
@@ -3740,10 +3828,14 @@ function findEntryChunks(buildPath) {
|
|
|
3740
3828
|
}
|
|
3741
3829
|
var SourceMapExplorerRunner = class extends BaseRunner {
|
|
3742
3830
|
name = "source-map-explorer";
|
|
3831
|
+
displayName = "Bundle Size";
|
|
3743
3832
|
category = "performance";
|
|
3744
3833
|
applicableRuntimes = ["browser"];
|
|
3745
|
-
async run(projectPath) {
|
|
3834
|
+
async run(projectPath, options) {
|
|
3746
3835
|
const elapsed = timer();
|
|
3836
|
+
const t = options?.checkConfig?.thresholds;
|
|
3837
|
+
const warnSize = t?.warnSize ?? SIZE_THRESHOLD_WARN;
|
|
3838
|
+
const failSize = t?.failSize ?? SIZE_THRESHOLD_FAIL;
|
|
3747
3839
|
const buildDir = fileExists(projectPath, "dist") ? "dist" : "build";
|
|
3748
3840
|
const buildPath = join29(projectPath, buildDir);
|
|
3749
3841
|
const mapFiles = await globby("**/*.js.map", {
|
|
@@ -3770,7 +3862,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3770
3862
|
const totalKB = Math.round(totalBytes / 1024);
|
|
3771
3863
|
const largestKB = Math.round(largestBytes / 1024);
|
|
3772
3864
|
const issues = [];
|
|
3773
|
-
if (largestBytes >
|
|
3865
|
+
if (largestBytes > failSize) {
|
|
3774
3866
|
issues.push({
|
|
3775
3867
|
severity: "critical",
|
|
3776
3868
|
message: `Largest bundle is ${largestKB}KB \u2014 exceeds 1MB threshold`,
|
|
@@ -3779,7 +3871,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3779
3871
|
},
|
|
3780
3872
|
reportedBy: ["source-map-explorer"]
|
|
3781
3873
|
});
|
|
3782
|
-
} else if (largestBytes >
|
|
3874
|
+
} else if (largestBytes > warnSize) {
|
|
3783
3875
|
issues.push({
|
|
3784
3876
|
severity: "warning",
|
|
3785
3877
|
message: `Largest bundle is ${largestKB}KB \u2014 consider optimizing`,
|
|
@@ -3789,7 +3881,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3789
3881
|
reportedBy: ["source-map-explorer"]
|
|
3790
3882
|
});
|
|
3791
3883
|
}
|
|
3792
|
-
const score = largestBytes >
|
|
3884
|
+
const score = largestBytes > failSize ? 40 : largestBytes > warnSize ? 70 : 100;
|
|
3793
3885
|
return {
|
|
3794
3886
|
id: "source-map-explorer",
|
|
3795
3887
|
category: this.category,
|
|
@@ -3842,7 +3934,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3842
3934
|
const totalKB = Math.round(totalBytes / 1024);
|
|
3843
3935
|
const initialKB = Math.round(initialBytes / 1024);
|
|
3844
3936
|
const issues = [];
|
|
3845
|
-
if (initialBytes >
|
|
3937
|
+
if (initialBytes > failSize) {
|
|
3846
3938
|
issues.push({
|
|
3847
3939
|
severity: "critical",
|
|
3848
3940
|
message: `Initial bundle is ${initialKB}KB \u2014 exceeds 1MB threshold`,
|
|
@@ -3851,7 +3943,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3851
3943
|
},
|
|
3852
3944
|
reportedBy: ["bundle-size-check"]
|
|
3853
3945
|
});
|
|
3854
|
-
} else if (initialBytes >
|
|
3946
|
+
} else if (initialBytes > warnSize) {
|
|
3855
3947
|
issues.push({
|
|
3856
3948
|
severity: "warning",
|
|
3857
3949
|
message: `Initial bundle is ${initialKB}KB \u2014 consider optimizing`,
|
|
@@ -3861,7 +3953,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3861
3953
|
reportedBy: ["bundle-size-check"]
|
|
3862
3954
|
});
|
|
3863
3955
|
}
|
|
3864
|
-
const score = initialBytes >
|
|
3956
|
+
const score = initialBytes > failSize ? 40 : initialBytes > warnSize ? 70 : 100;
|
|
3865
3957
|
return {
|
|
3866
3958
|
id: "source-map-explorer",
|
|
3867
3959
|
category: this.category,
|
|
@@ -3902,7 +3994,10 @@ var SourceMapExplorerRunner = class extends BaseRunner {
|
|
|
3902
3994
|
}
|
|
3903
3995
|
}
|
|
3904
3996
|
};
|
|
3905
|
-
var
|
|
3997
|
+
var DEFAULT_PATTERNS = ["TODO", "FIXME", "HACK"];
|
|
3998
|
+
function buildPattern(patterns) {
|
|
3999
|
+
return new RegExp("\\b(" + patterns.join("|") + ")\\b[:\\s]*(.*)", "i");
|
|
4000
|
+
}
|
|
3906
4001
|
var STRING_LITERAL_RE = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g;
|
|
3907
4002
|
function stripStringLiterals(line) {
|
|
3908
4003
|
return line.replace(STRING_LITERAL_RE, (m) => " ".repeat(m.length));
|
|
@@ -3914,10 +4009,13 @@ var TodoScannerRunner = class extends BaseRunner {
|
|
|
3914
4009
|
async isApplicable(projectPath) {
|
|
3915
4010
|
return existsSync18(join30(projectPath, "src"));
|
|
3916
4011
|
}
|
|
3917
|
-
async run(projectPath) {
|
|
4012
|
+
async run(projectPath, options) {
|
|
3918
4013
|
const elapsed = timer();
|
|
4014
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
4015
|
+
const patterns = thresholds?.patterns ?? DEFAULT_PATTERNS;
|
|
4016
|
+
const pattern = buildPattern(patterns);
|
|
3919
4017
|
try {
|
|
3920
|
-
const todos = scanDirectory4(join30(projectPath, "src"), projectPath);
|
|
4018
|
+
const todos = scanDirectory4(join30(projectPath, "src"), projectPath, pattern);
|
|
3921
4019
|
const issues = todos.map((t) => ({
|
|
3922
4020
|
severity: t.kind === "FIXME" || t.kind === "HACK" ? "warning" : "info",
|
|
3923
4021
|
message: `${t.file}:${t.line} \u2014 ${t.kind}: ${t.text || "(no description)"}`,
|
|
@@ -3962,7 +4060,7 @@ var TodoScannerRunner = class extends BaseRunner {
|
|
|
3962
4060
|
}
|
|
3963
4061
|
};
|
|
3964
4062
|
var SELF_REFERENCING_FILES = /* @__PURE__ */ new Set(["todo-scanner.ts", "todo-scanner.test.ts"]);
|
|
3965
|
-
function scanDirectory4(dir, projectRoot) {
|
|
4063
|
+
function scanDirectory4(dir, projectRoot, pattern) {
|
|
3966
4064
|
const todos = [];
|
|
3967
4065
|
try {
|
|
3968
4066
|
for (const entry of readdirSync15(dir)) {
|
|
@@ -3971,22 +4069,22 @@ function scanDirectory4(dir, projectRoot) {
|
|
|
3971
4069
|
const fullPath = join30(dir, entry);
|
|
3972
4070
|
const stat = statSync16(fullPath);
|
|
3973
4071
|
if (stat.isDirectory()) {
|
|
3974
|
-
todos.push(...scanDirectory4(fullPath, projectRoot));
|
|
4072
|
+
todos.push(...scanDirectory4(fullPath, projectRoot, pattern));
|
|
3975
4073
|
} else if (SOURCE_EXTENSIONS3.has(extname7(entry))) {
|
|
3976
|
-
todos.push(...scanFile2(fullPath, projectRoot));
|
|
4074
|
+
todos.push(...scanFile2(fullPath, projectRoot, pattern));
|
|
3977
4075
|
}
|
|
3978
4076
|
}
|
|
3979
4077
|
} catch {
|
|
3980
4078
|
}
|
|
3981
4079
|
return todos;
|
|
3982
4080
|
}
|
|
3983
|
-
function scanFile2(filePath, projectRoot) {
|
|
4081
|
+
function scanFile2(filePath, projectRoot, pattern) {
|
|
3984
4082
|
const todos = [];
|
|
3985
4083
|
try {
|
|
3986
4084
|
const lines = readFileSync25(filePath, "utf-8").split("\n");
|
|
3987
4085
|
const relPath = filePath.replace(projectRoot + "/", "");
|
|
3988
4086
|
for (let i = 0; i < lines.length; i++) {
|
|
3989
|
-
const match = stripStringLiterals(lines[i]).match(
|
|
4087
|
+
const match = stripStringLiterals(lines[i]).match(pattern);
|
|
3990
4088
|
if (match) {
|
|
3991
4089
|
todos.push({
|
|
3992
4090
|
file: relPath,
|
|
@@ -4006,8 +4104,10 @@ var TypeScriptRunner = class extends BaseRunner {
|
|
|
4006
4104
|
async isApplicable(projectPath) {
|
|
4007
4105
|
return existsSync19(join31(projectPath, "tsconfig.json"));
|
|
4008
4106
|
}
|
|
4009
|
-
async run(projectPath) {
|
|
4107
|
+
async run(projectPath, options) {
|
|
4010
4108
|
const elapsed = timer();
|
|
4109
|
+
const thresholds = options?.checkConfig?.thresholds;
|
|
4110
|
+
const maxErrors = thresholds?.maxErrors ?? 20;
|
|
4011
4111
|
try {
|
|
4012
4112
|
const { stdout, stderr } = await execa13("tsc", ["--noEmit", "--pretty", "false"], {
|
|
4013
4113
|
cwd: projectPath,
|
|
@@ -4039,7 +4139,7 @@ var TypeScriptRunner = class extends BaseRunner {
|
|
|
4039
4139
|
category: this.category,
|
|
4040
4140
|
name: "Type Safety",
|
|
4041
4141
|
score,
|
|
4042
|
-
status: count === 0 ? "pass" : count >
|
|
4142
|
+
status: count === 0 ? "pass" : count > maxErrors ? "fail" : "warning",
|
|
4043
4143
|
issues,
|
|
4044
4144
|
toolsUsed: ["tsc"],
|
|
4045
4145
|
duration: elapsed(),
|
|
@@ -4362,7 +4462,11 @@ async function runSickbay(options = {}) {
|
|
|
4362
4462
|
const applicable = await runner.isApplicable(projectPath, context);
|
|
4363
4463
|
if (!applicable) return null;
|
|
4364
4464
|
options.onCheckStart?.(runner.name);
|
|
4365
|
-
const
|
|
4465
|
+
const checkCfg = getCheckConfig(config, runner.name);
|
|
4466
|
+
const result = await runner.run(projectPath, {
|
|
4467
|
+
verbose: options.verbose,
|
|
4468
|
+
checkConfig: checkCfg ? { thresholds: checkCfg.thresholds, exclude: checkCfg.exclude } : void 0
|
|
4469
|
+
});
|
|
4366
4470
|
options.onCheckComplete?.(result);
|
|
4367
4471
|
return result;
|
|
4368
4472
|
})
|
|
@@ -4478,6 +4582,7 @@ async function getDependencyTree(projectPath, packageManager) {
|
|
|
4478
4582
|
|
|
4479
4583
|
export {
|
|
4480
4584
|
defineConfig,
|
|
4585
|
+
getCheckConfig,
|
|
4481
4586
|
isCheckDisabled,
|
|
4482
4587
|
resolveConfigMeta,
|
|
4483
4588
|
validateConfig,
|
package/dist/config.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
detectPackageManager,
|
|
13
13
|
detectProject,
|
|
14
14
|
getAvailableChecks,
|
|
15
|
+
getCheckConfig,
|
|
15
16
|
getDependencyTree,
|
|
16
17
|
getScoreColor,
|
|
17
18
|
getScoreEmoji,
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
runSickbay,
|
|
22
23
|
runSickbayMonorepo,
|
|
23
24
|
validateConfig
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-NYE7UH3H.js";
|
|
25
26
|
export {
|
|
26
27
|
CRITICAL_LINES,
|
|
27
28
|
SCORE_EXCELLENT,
|
|
@@ -36,6 +37,7 @@ export {
|
|
|
36
37
|
detectPackageManager,
|
|
37
38
|
detectProject,
|
|
38
39
|
getAvailableChecks,
|
|
40
|
+
getCheckConfig,
|
|
39
41
|
getDependencyTree,
|
|
40
42
|
getScoreColor,
|
|
41
43
|
getScoreEmoji,
|
package/dist/index.js
CHANGED
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
} from "./chunk-TYG7ZQBP.js";
|
|
9
9
|
import {
|
|
10
10
|
Header
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EONIXEDB.js";
|
|
12
12
|
import {
|
|
13
13
|
getScoreEmoji,
|
|
14
14
|
runSickbay,
|
|
15
15
|
runSickbayMonorepo
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-NYE7UH3H.js";
|
|
17
17
|
|
|
18
18
|
// src/index.ts
|
|
19
19
|
import { existsSync } from "fs";
|
|
@@ -176,7 +176,7 @@ function App({
|
|
|
176
176
|
setMonorepoReport(r);
|
|
177
177
|
setProjectName(`monorepo (${r.packages.length} packages)`);
|
|
178
178
|
try {
|
|
179
|
-
const { getDependencyTree } = await import("./dist-
|
|
179
|
+
const { getDependencyTree } = await import("./dist-PJQ2AQQL.js");
|
|
180
180
|
const { saveDepTree } = await import("./history-XLNVZEDI.js");
|
|
181
181
|
const packages = {};
|
|
182
182
|
for (const pkg of r.packages) {
|
|
@@ -245,7 +245,7 @@ function App({
|
|
|
245
245
|
} catch {
|
|
246
246
|
}
|
|
247
247
|
try {
|
|
248
|
-
const { getDependencyTree } = await import("./dist-
|
|
248
|
+
const { getDependencyTree } = await import("./dist-PJQ2AQQL.js");
|
|
249
249
|
const { saveDepTree } = await import("./history-XLNVZEDI.js");
|
|
250
250
|
const tree = await getDependencyTree(projectPath, r.projectInfo.packageManager);
|
|
251
251
|
saveDepTree(projectPath, tree);
|
|
@@ -303,7 +303,7 @@ if (existsSync(globalConfigPath)) {
|
|
|
303
303
|
}
|
|
304
304
|
config({ debug: false, quiet: true });
|
|
305
305
|
var program = new Command();
|
|
306
|
-
program.name("sickbay").description("React project health check CLI").version("1.7.
|
|
306
|
+
program.name("sickbay").description("React project health check CLI").version("1.7.5", "-v, --version").enablePositionalOptions().passThroughOptions().option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single named package (monorepo only)").option("--json", "output raw JSON report").option("--web", "open web dashboard after scan").option("--no-ai", "disable AI features").option("--no-quotes", "suppress personality quotes in output").option("--verbose", "show verbose output").action(async (options) => {
|
|
307
307
|
if (options.path && options.path !== process.cwd()) {
|
|
308
308
|
const projectEnvPath = join(options.path, ".env");
|
|
309
309
|
if (existsSync(projectEnvPath)) {
|
|
@@ -313,13 +313,13 @@ program.name("sickbay").description("React project health check CLI").version("1
|
|
|
313
313
|
const updatePromise = (async () => {
|
|
314
314
|
try {
|
|
315
315
|
const { checkForUpdate } = await import("./update-check-M7IFOMLJ.js");
|
|
316
|
-
return await checkForUpdate("1.7.
|
|
316
|
+
return await checkForUpdate("1.7.5");
|
|
317
317
|
} catch {
|
|
318
318
|
return null;
|
|
319
319
|
}
|
|
320
320
|
})();
|
|
321
321
|
const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
|
|
322
|
-
const { detectMonorepo, runSickbay: runSickbay2, runSickbayMonorepo: runSickbayMonorepo2 } = await import("./dist-
|
|
322
|
+
const { detectMonorepo, runSickbay: runSickbay2, runSickbayMonorepo: runSickbayMonorepo2 } = await import("./dist-PJQ2AQQL.js");
|
|
323
323
|
const monorepoInfo = await detectMonorepo(options.path);
|
|
324
324
|
if (options.package && monorepoInfo.isMonorepo) {
|
|
325
325
|
const { readFileSync } = await import("fs");
|
|
@@ -383,7 +383,7 @@ program.name("sickbay").description("React project health check CLI").version("1
|
|
|
383
383
|
} catch {
|
|
384
384
|
}
|
|
385
385
|
try {
|
|
386
|
-
const { getDependencyTree } = await import("./dist-
|
|
386
|
+
const { getDependencyTree } = await import("./dist-PJQ2AQQL.js");
|
|
387
387
|
const { saveDepTree } = await import("./history-XLNVZEDI.js");
|
|
388
388
|
const tree = await getDependencyTree(options.path, report.projectInfo.packageManager);
|
|
389
389
|
saveDepTree(options.path, tree);
|
|
@@ -406,7 +406,7 @@ program.name("sickbay").description("React project health check CLI").version("1
|
|
|
406
406
|
);
|
|
407
407
|
});
|
|
408
408
|
program.command("init").description("Initialize .sickbay/ folder and run an initial baseline scan").option("-p, --path <path>", "project path to initialize", process.cwd()).action(async (options) => {
|
|
409
|
-
const { initSickbay } = await import("./init-
|
|
409
|
+
const { initSickbay } = await import("./init-WBLDUGEB.js");
|
|
410
410
|
await initSickbay(options.path);
|
|
411
411
|
});
|
|
412
412
|
program.command("fix").description("Interactively fix issues found by sickbay scan").option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single package (monorepo only)").option("--all", "apply all available fixes without prompting").option("--dry-run", "show what would be fixed without executing").option("--verbose", "show verbose output").action(async (options) => {
|
|
@@ -416,9 +416,9 @@ program.command("fix").description("Interactively fix issues found by sickbay sc
|
|
|
416
416
|
config({ path: projectEnvPath, override: true });
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
|
-
const { resolveProject } = await import("./resolve-package-
|
|
419
|
+
const { resolveProject } = await import("./resolve-package-7S4ZB3OA.js");
|
|
420
420
|
const resolution = await resolveProject(options.path, options.package);
|
|
421
|
-
const { FixApp } = await import("./FixApp-
|
|
421
|
+
const { FixApp } = await import("./FixApp-46TGLR3C.js");
|
|
422
422
|
const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
|
|
423
423
|
const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
|
|
424
424
|
render(
|
|
@@ -441,10 +441,10 @@ program.command("trend").description("Show score history and trends over time").
|
|
|
441
441
|
config({ path: projectEnvPath, override: true });
|
|
442
442
|
}
|
|
443
443
|
}
|
|
444
|
-
const { resolveProject } = await import("./resolve-package-
|
|
444
|
+
const { resolveProject } = await import("./resolve-package-7S4ZB3OA.js");
|
|
445
445
|
const resolution = await resolveProject(options.path, options.package);
|
|
446
446
|
const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
|
|
447
|
-
const { TrendApp } = await import("./TrendApp-
|
|
447
|
+
const { TrendApp } = await import("./TrendApp-WMFR4L3A.js");
|
|
448
448
|
render(
|
|
449
449
|
React6.createElement(TrendApp, {
|
|
450
450
|
projectPath,
|
|
@@ -463,10 +463,10 @@ program.command("stats").description("Show a quick codebase overview and project
|
|
|
463
463
|
config({ path: projectEnvPath, override: true });
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
|
-
const { resolveProject } = await import("./resolve-package-
|
|
466
|
+
const { resolveProject } = await import("./resolve-package-7S4ZB3OA.js");
|
|
467
467
|
const resolution = await resolveProject(options.path, options.package);
|
|
468
468
|
const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
|
|
469
|
-
const { StatsApp } = await import("./StatsApp-
|
|
469
|
+
const { StatsApp } = await import("./StatsApp-5YMWUNHQ.js");
|
|
470
470
|
render(
|
|
471
471
|
React6.createElement(StatsApp, {
|
|
472
472
|
projectPath,
|
|
@@ -478,7 +478,7 @@ program.command("stats").description("Show a quick codebase overview and project
|
|
|
478
478
|
);
|
|
479
479
|
});
|
|
480
480
|
program.command("tui").description("Launch the persistent developer dashboard").option("-p, --path <path>", "project path to monitor", process.cwd()).option("--no-watch", "disable file-watching auto-refresh").option("--no-quotes", "suppress personality quotes in output").option("--refresh <seconds>", "auto-refresh interval in seconds", "300").option("-c, --checks <checks>", "comma-separated list of checks to run").action(async (options) => {
|
|
481
|
-
const { TuiApp } = await import("./TuiApp-
|
|
481
|
+
const { TuiApp } = await import("./TuiApp-DDC2E3SZ.js");
|
|
482
482
|
const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
|
|
483
483
|
render(
|
|
484
484
|
React6.createElement(TuiApp, {
|
|
@@ -498,10 +498,10 @@ program.command("doctor").description("Diagnose project setup and configuration
|
|
|
498
498
|
config({ path: projectEnvPath, override: true });
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
|
-
const { resolveProject } = await import("./resolve-package-
|
|
501
|
+
const { resolveProject } = await import("./resolve-package-7S4ZB3OA.js");
|
|
502
502
|
const resolution = await resolveProject(options.path, options.package);
|
|
503
503
|
const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
|
|
504
|
-
const { DoctorApp } = await import("./DoctorApp-
|
|
504
|
+
const { DoctorApp } = await import("./DoctorApp-XVZMFDLM.js");
|
|
505
505
|
render(
|
|
506
506
|
React6.createElement(DoctorApp, {
|
|
507
507
|
projectPath,
|
|
@@ -514,13 +514,13 @@ program.command("doctor").description("Diagnose project setup and configuration
|
|
|
514
514
|
);
|
|
515
515
|
});
|
|
516
516
|
program.command("badge").description("Generate a health score badge for your README").option("-p, --path <path>", "project path", process.cwd()).option("--package <name>", "scope to a single package (monorepo only)").option("--html", "output HTML <img> tag instead of markdown").option("--url", "output bare badge URL only").option("--label <text>", "custom badge label", "sickbay").option("--scan", "run a fresh scan instead of using last report").action(async (options) => {
|
|
517
|
-
const { resolveProject } = await import("./resolve-package-
|
|
517
|
+
const { resolveProject } = await import("./resolve-package-7S4ZB3OA.js");
|
|
518
518
|
const resolution = await resolveProject(options.path, options.package);
|
|
519
519
|
const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
|
|
520
520
|
const { loadScoreFromLastReport, badgeUrl, badgeMarkdown, badgeHtml } = await import("./badge-KQ73KEIN.js");
|
|
521
521
|
let score = options.scan ? null : loadScoreFromLastReport(projectPath);
|
|
522
522
|
if (score === null) {
|
|
523
|
-
const { runSickbay: runSickbay2 } = await import("./dist-
|
|
523
|
+
const { runSickbay: runSickbay2 } = await import("./dist-PJQ2AQQL.js");
|
|
524
524
|
const report = await runSickbay2({ projectPath, quotes: false });
|
|
525
525
|
try {
|
|
526
526
|
const { saveEntry, saveLastReport } = await import("./history-XLNVZEDI.js");
|
|
@@ -549,7 +549,7 @@ program.command("diff <branch>").description("Compare health score against anoth
|
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
551
|
const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
|
|
552
|
-
const { DiffApp } = await import("./DiffApp-
|
|
552
|
+
const { DiffApp } = await import("./DiffApp-CG2DRKTU.js");
|
|
553
553
|
render(
|
|
554
554
|
React6.createElement(DiffApp, {
|
|
555
555
|
projectPath: options.path,
|