hermex 2.0.0-beta.6 → 2.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +199 -37
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +53 -2
- package/package.json +6 -1
package/dist/cli.mjs
CHANGED
|
@@ -302,7 +302,9 @@ function formatRuleType(type) {
|
|
|
302
302
|
case "require_packages": return "require_packages";
|
|
303
303
|
case "require_scripts": return "require_scripts";
|
|
304
304
|
case "require_package_fields": return "pkg_fields";
|
|
305
|
+
case "forbid_package_fields": return "pkg_fields";
|
|
305
306
|
case "engine_version": return "engine_version";
|
|
307
|
+
case "codeowners": return "codeowners";
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
function ruleIcon(violation) {
|
|
@@ -321,11 +323,21 @@ function describeViolation(v) {
|
|
|
321
323
|
if (v.type === "require_packages") return `${patterns} not installed${suffix}`;
|
|
322
324
|
if (v.type === "forbid_packages") return `${patterns} is forbidden${suffix}`;
|
|
323
325
|
if (v.type === "require_scripts") return `script ${patterns} missing in package.json${suffix}`;
|
|
324
|
-
if (v.type === "require_package_fields")
|
|
326
|
+
if (v.type === "require_package_fields") {
|
|
327
|
+
if (v.fieldPath && v.actualValue !== void 0) return `field ${v.fieldPath} is ${chalk.yellow(v.actualValue)}, does not match required value${suffix}`;
|
|
328
|
+
return `field ${patterns} missing in package.json${suffix}`;
|
|
329
|
+
}
|
|
330
|
+
if (v.type === "forbid_package_fields") return `field ${v.fieldPath ?? patterns} is forbidden in package.json${suffix}`;
|
|
325
331
|
if (v.type === "engine_version") {
|
|
326
332
|
if (!v.installedRange) return `engines.node not specified (required ${v.requiredRange})${suffix}`;
|
|
327
333
|
return `engines.node is ${chalk.yellow(v.installedRange)}, required ${chalk.cyan(v.requiredRange)}${suffix}`;
|
|
328
334
|
}
|
|
335
|
+
if (v.type === "codeowners") {
|
|
336
|
+
if (v.matchedFiles.length === 0) return `CODEOWNERS not found (looked in ${patterns})${suffix}`;
|
|
337
|
+
const shown = v.matchedFiles.slice(0, 3).join(", ");
|
|
338
|
+
const more = v.matchedFiles.length > 3 ? ` and ${v.matchedFiles.length - 3} more` : "";
|
|
339
|
+
return `${v.matchedFiles.length} scanned file(s) have no owner: ${shown}${more}${suffix}`;
|
|
340
|
+
}
|
|
329
341
|
return `${patterns} not present${suffix}`;
|
|
330
342
|
}
|
|
331
343
|
function printRules(aggregated) {
|
|
@@ -420,11 +432,19 @@ const RuleConfigSchema = z.object({
|
|
|
420
432
|
message: z.string().optional()
|
|
421
433
|
});
|
|
422
434
|
const RuleConfigOrArraySchema = z.union([RuleConfigSchema, z.array(RuleConfigSchema)]);
|
|
435
|
+
const PackageFieldRuleSchema = RuleConfigSchema.extend({
|
|
436
|
+
/** Optional micromatch patterns the field's stringified value must match */
|
|
437
|
+
values: z.array(z.string()).optional() });
|
|
438
|
+
const PackageFieldRuleOrArraySchema = z.union([PackageFieldRuleSchema, z.array(PackageFieldRuleSchema)]);
|
|
423
439
|
const EngineVersionRuleSchema = z.object({
|
|
424
440
|
severity: RuleSeveritySchema,
|
|
425
441
|
range: z.string(),
|
|
426
442
|
message: z.string().optional()
|
|
427
443
|
});
|
|
444
|
+
const CodeownersRuleSchema = z.object({
|
|
445
|
+
severity: RuleSeveritySchema,
|
|
446
|
+
message: z.string().optional()
|
|
447
|
+
});
|
|
428
448
|
const ThresholdSchema = z.union([z.number(), z.literal(false)]);
|
|
429
449
|
const HermexConfigSchema = z.object({
|
|
430
450
|
includes: z.array(z.string()).default(["**/*.{tsx,jsx,ts,js}"]),
|
|
@@ -450,15 +470,18 @@ const HermexConfigSchema = z.object({
|
|
|
450
470
|
forbid_packages: RuleConfigOrArraySchema.default([]),
|
|
451
471
|
require_packages: RuleConfigOrArraySchema.default([]),
|
|
452
472
|
require_scripts: RuleConfigOrArraySchema.default([]),
|
|
453
|
-
require_package_fields:
|
|
454
|
-
|
|
473
|
+
require_package_fields: PackageFieldRuleOrArraySchema.default([]),
|
|
474
|
+
forbid_package_fields: PackageFieldRuleOrArraySchema.default([]),
|
|
475
|
+
engine_version: z.union([EngineVersionRuleSchema, z.array(EngineVersionRuleSchema)]).optional(),
|
|
476
|
+
codeowners: CodeownersRuleSchema.optional()
|
|
455
477
|
}).default(() => ({
|
|
456
478
|
detect_files: [],
|
|
457
479
|
require_files: [],
|
|
458
480
|
forbid_packages: [],
|
|
459
481
|
require_packages: [],
|
|
460
482
|
require_scripts: [],
|
|
461
|
-
require_package_fields: []
|
|
483
|
+
require_package_fields: [],
|
|
484
|
+
forbid_package_fields: []
|
|
462
485
|
})),
|
|
463
486
|
output: z.object({
|
|
464
487
|
summary: z.union([z.literal("log"), z.literal(false)]).default("log"),
|
|
@@ -870,10 +893,10 @@ function analyzeConditionalExpression(node, state) {
|
|
|
870
893
|
*/
|
|
871
894
|
function analyzeArrayExpression(node, state) {
|
|
872
895
|
if (node.elements?.some((elem) => {
|
|
873
|
-
if (elem?.type === "Identifier") return state.componentNames.has(elem.value);
|
|
896
|
+
if (elem?.expression?.type === "Identifier") return state.componentNames.has(elem.expression.value);
|
|
874
897
|
return false;
|
|
875
898
|
})) state.usagePatterns.arrayMappings.add({
|
|
876
|
-
components: node.elements?.map((elem) => elem?.value).filter(Boolean),
|
|
899
|
+
components: node.elements?.map((elem) => elem?.expression?.value).filter(Boolean),
|
|
877
900
|
line: node.span?.start || 0
|
|
878
901
|
});
|
|
879
902
|
}
|
|
@@ -899,11 +922,11 @@ function analyzeObjectExpression(node, state) {
|
|
|
899
922
|
* Analyzes React.lazy() imports
|
|
900
923
|
*/
|
|
901
924
|
function analyzeLazyImport(node, state) {
|
|
902
|
-
const arg = node.arguments?.[0];
|
|
925
|
+
const arg = node.arguments?.[0]?.expression;
|
|
903
926
|
if (arg?.type === "ArrowFunctionExpression" && arg.body?.type === "CallExpression") {
|
|
904
927
|
const importCall = arg.body;
|
|
905
928
|
if (importCall.callee?.type === "Import") {
|
|
906
|
-
const source = importCall.arguments?.[0]?.value;
|
|
929
|
+
const source = importCall.arguments?.[0]?.expression?.value;
|
|
907
930
|
if (source) state.usagePatterns.lazyImports.add({
|
|
908
931
|
source,
|
|
909
932
|
line: node.span?.start || 0
|
|
@@ -915,7 +938,7 @@ function analyzeLazyImport(node, state) {
|
|
|
915
938
|
* Analyzes dynamic import() calls
|
|
916
939
|
*/
|
|
917
940
|
function analyzeDynamicImport(node, state) {
|
|
918
|
-
const source = node.arguments?.[0]?.value;
|
|
941
|
+
const source = node.arguments?.[0]?.expression?.value;
|
|
919
942
|
if (source) state.usagePatterns.dynamicImports.add({
|
|
920
943
|
source,
|
|
921
944
|
line: node.span?.start || 0
|
|
@@ -929,7 +952,7 @@ function analyzeDynamicImport(node, state) {
|
|
|
929
952
|
function analyzeHOCUsage(node, state) {
|
|
930
953
|
state.usagePatterns.hocUsage.add({
|
|
931
954
|
function: node.callee?.value || "[unknown]",
|
|
932
|
-
component: node.arguments?.[0]?.value || "[unknown]",
|
|
955
|
+
component: node.arguments?.[0]?.expression?.value || "[unknown]",
|
|
933
956
|
line: node.span?.start || 0
|
|
934
957
|
});
|
|
935
958
|
}
|
|
@@ -937,7 +960,7 @@ function analyzeHOCUsage(node, state) {
|
|
|
937
960
|
* Analyzes React.memo() usage
|
|
938
961
|
*/
|
|
939
962
|
function analyzeMemoUsage(node, state) {
|
|
940
|
-
const component = node.arguments?.[0];
|
|
963
|
+
const component = node.arguments?.[0]?.expression;
|
|
941
964
|
if (component?.type === "Identifier" && state.componentNames.has(component.value)) state.usagePatterns.memoizedComponents.add({
|
|
942
965
|
component: component.value,
|
|
943
966
|
line: node.span?.start || 0
|
|
@@ -968,7 +991,7 @@ function analyzeMemberExpression(node, state) {
|
|
|
968
991
|
* Checks if a node represents HOC pattern
|
|
969
992
|
*/
|
|
970
993
|
function isHOCPattern(node, state) {
|
|
971
|
-
return node.callee?.type === "Identifier" && node.arguments?.some((arg) => arg.type === "Identifier" && state.componentNames.has(arg.value));
|
|
994
|
+
return node.callee?.type === "Identifier" && node.arguments?.some((arg) => arg.expression?.type === "Identifier" && state.componentNames.has(arg.expression.value));
|
|
972
995
|
}
|
|
973
996
|
//#endregion
|
|
974
997
|
//#region src/swc-parser/core/visitor.ts
|
|
@@ -1297,18 +1320,63 @@ function evaluateScriptRules(repoPath, rulesConfig) {
|
|
|
1297
1320
|
}
|
|
1298
1321
|
//#endregion
|
|
1299
1322
|
//#region src/rules/package-field-rules.ts
|
|
1323
|
+
/** Resolves a dot-path like "engines.node" against the manifest object. */
|
|
1324
|
+
function getFieldAtPath(pkg, path) {
|
|
1325
|
+
let current = pkg;
|
|
1326
|
+
for (const key of path.split(".")) {
|
|
1327
|
+
if (current === null || typeof current !== "object" || !(key in current)) return {
|
|
1328
|
+
exists: false,
|
|
1329
|
+
value: void 0
|
|
1330
|
+
};
|
|
1331
|
+
current = current[key];
|
|
1332
|
+
}
|
|
1333
|
+
return {
|
|
1334
|
+
exists: true,
|
|
1335
|
+
value: current
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
/** Primitives compare by string form; objects/arrays never match a value pattern. */
|
|
1339
|
+
function valueMatches(value, valuePatterns) {
|
|
1340
|
+
if (value === null || typeof value === "object") return false;
|
|
1341
|
+
return micromatch.isMatch(String(value), valuePatterns);
|
|
1342
|
+
}
|
|
1300
1343
|
function evaluatePackageFieldRules(repoPath, rulesConfig) {
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1344
|
+
const requireRules = toArray(rulesConfig.require_package_fields);
|
|
1345
|
+
const forbidRules = toArray(rulesConfig.forbid_package_fields);
|
|
1346
|
+
if (requireRules.length === 0 && forbidRules.length === 0) return [];
|
|
1303
1347
|
const pkg = readPackageJson(repoPath);
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1348
|
+
const violations = [];
|
|
1349
|
+
for (const rule of requireRules) {
|
|
1350
|
+
const lookups = rule.patterns.map((p) => ({
|
|
1351
|
+
path: p,
|
|
1352
|
+
...getFieldAtPath(pkg, p)
|
|
1353
|
+
}));
|
|
1354
|
+
if (!lookups.some((l) => l.exists && (!rule.values || valueMatches(l.value, rule.values)))) {
|
|
1355
|
+
const mismatch = rule.values ? lookups.find((l) => l.exists) : void 0;
|
|
1356
|
+
violations.push({
|
|
1357
|
+
type: "require_package_fields",
|
|
1358
|
+
severity: rule.severity,
|
|
1359
|
+
patterns: rule.patterns,
|
|
1360
|
+
message: rule.message,
|
|
1361
|
+
matchedFiles: [],
|
|
1362
|
+
fieldPath: mismatch?.path,
|
|
1363
|
+
actualValue: mismatch && typeof mismatch.value !== "object" ? String(mismatch.value) : void 0
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
for (const rule of forbidRules) for (const pattern of rule.patterns) {
|
|
1368
|
+
const lookup = getFieldAtPath(pkg, pattern);
|
|
1369
|
+
if (lookup.exists && (!rule.values || valueMatches(lookup.value, rule.values))) violations.push({
|
|
1370
|
+
type: "forbid_package_fields",
|
|
1371
|
+
severity: rule.severity,
|
|
1372
|
+
patterns: rule.patterns,
|
|
1373
|
+
message: rule.message,
|
|
1374
|
+
matchedFiles: [],
|
|
1375
|
+
fieldPath: pattern,
|
|
1376
|
+
actualValue: lookup.value !== null && typeof lookup.value !== "object" ? String(lookup.value) : void 0
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
return violations;
|
|
1312
1380
|
}
|
|
1313
1381
|
//#endregion
|
|
1314
1382
|
//#region src/rules/engine-version.ts
|
|
@@ -1339,13 +1407,102 @@ function evaluateEngineVersion(repoPath, rulesConfig) {
|
|
|
1339
1407
|
});
|
|
1340
1408
|
}
|
|
1341
1409
|
//#endregion
|
|
1410
|
+
//#region src/rules/codeowners.ts
|
|
1411
|
+
const CODEOWNERS_LOCATIONS = [
|
|
1412
|
+
".github/CODEOWNERS",
|
|
1413
|
+
"CODEOWNERS",
|
|
1414
|
+
"docs/CODEOWNERS"
|
|
1415
|
+
];
|
|
1416
|
+
/** First existing location, GitHub search order. Null if none exist. */
|
|
1417
|
+
function findCodeownersFile(repoPath) {
|
|
1418
|
+
for (const location of CODEOWNERS_LOCATIONS) {
|
|
1419
|
+
const full = path$1.join(repoPath, location);
|
|
1420
|
+
if (fs$1.existsSync(full)) return full;
|
|
1421
|
+
}
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
/** Parses content into entries; skips blank lines and # comments. */
|
|
1425
|
+
function parseCodeowners(content) {
|
|
1426
|
+
const entries = [];
|
|
1427
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
1428
|
+
const line = rawLine.trim();
|
|
1429
|
+
if (!line || line.startsWith("#")) continue;
|
|
1430
|
+
const tokens = line.split(/\s+/);
|
|
1431
|
+
const pattern = tokens[0];
|
|
1432
|
+
const owners = tokens.slice(1);
|
|
1433
|
+
entries.push({
|
|
1434
|
+
pattern,
|
|
1435
|
+
globs: codeownersPatternToGlobs(pattern),
|
|
1436
|
+
owners
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
return entries;
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Translates a CODEOWNERS (gitignore-style) pattern into micromatch glob(s).
|
|
1443
|
+
*
|
|
1444
|
+
* Rules:
|
|
1445
|
+
* - `*` alone matches everything.
|
|
1446
|
+
* - A leading `/`, or a `/` anywhere in the middle of the pattern, anchors
|
|
1447
|
+
* the match to the repo root (the leading `/` is stripped once anchored).
|
|
1448
|
+
* - A pattern with no anchoring gets a `**\/` prefix so it matches at any depth.
|
|
1449
|
+
* - A trailing `/` restricts the match to a directory, so `/**` is appended.
|
|
1450
|
+
* - A bare name (no glob characters, no slash at all) also matches as a
|
|
1451
|
+
* directory anywhere, so the `/**` directory variant is added alongside
|
|
1452
|
+
* the plain match.
|
|
1453
|
+
*/
|
|
1454
|
+
function codeownersPatternToGlobs(pattern) {
|
|
1455
|
+
if (pattern === "*") return ["**"];
|
|
1456
|
+
let p = pattern;
|
|
1457
|
+
const hadLeadingSlash = p.startsWith("/");
|
|
1458
|
+
if (hadLeadingSlash) p = p.slice(1);
|
|
1459
|
+
const hadTrailingSlash = p.endsWith("/") && p.length > 1;
|
|
1460
|
+
if (hadTrailingSlash) p = p.slice(0, -1);
|
|
1461
|
+
const hasInternalSlash = p.includes("/");
|
|
1462
|
+
const anchored = hadLeadingSlash || hasInternalSlash;
|
|
1463
|
+
const hasGlobChars = /[*?[\]{}!()]/.test(p);
|
|
1464
|
+
const isBareName = !anchored && !hasGlobChars && !hadTrailingSlash;
|
|
1465
|
+
const base = anchored ? p : `**/${p}`;
|
|
1466
|
+
const globs = [hadTrailingSlash ? `${base}/**` : base];
|
|
1467
|
+
if (isBareName) globs.push(`${base}/**`);
|
|
1468
|
+
return globs;
|
|
1469
|
+
}
|
|
1470
|
+
/** Last matching entry wins; owned iff that entry has >= 1 owner. */
|
|
1471
|
+
function fileIsOwned(file, entries) {
|
|
1472
|
+
for (let i = entries.length - 1; i >= 0; i--) if (micromatch.isMatch(file, entries[i].globs, { dot: true })) return entries[i].owners.length > 0;
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
function evaluateCodeowners(repoPath, rulesConfig, scannedFiles) {
|
|
1476
|
+
const rule = rulesConfig.codeowners;
|
|
1477
|
+
if (!rule) return [];
|
|
1478
|
+
const filePath = findCodeownersFile(repoPath);
|
|
1479
|
+
if (!filePath) return [{
|
|
1480
|
+
type: "codeowners",
|
|
1481
|
+
severity: rule.severity,
|
|
1482
|
+
patterns: CODEOWNERS_LOCATIONS,
|
|
1483
|
+
message: rule.message,
|
|
1484
|
+
matchedFiles: []
|
|
1485
|
+
}];
|
|
1486
|
+
const entries = parseCodeowners(fs$1.readFileSync(filePath, "utf-8"));
|
|
1487
|
+
const unowned = scannedFiles.map((f) => (path$1.isAbsolute(f) ? path$1.relative(repoPath, f) : f).replace(/\\/g, "/")).filter((f) => !fileIsOwned(f, entries));
|
|
1488
|
+
if (unowned.length === 0) return [];
|
|
1489
|
+
return [{
|
|
1490
|
+
type: "codeowners",
|
|
1491
|
+
severity: rule.severity,
|
|
1492
|
+
patterns: [path$1.basename(filePath)],
|
|
1493
|
+
message: rule.message,
|
|
1494
|
+
matchedFiles: unowned
|
|
1495
|
+
}];
|
|
1496
|
+
}
|
|
1497
|
+
//#endregion
|
|
1342
1498
|
//#region src/rules/evaluator.ts
|
|
1343
|
-
function evaluateRules(repoPath, rulesConfig, excludes) {
|
|
1499
|
+
function evaluateRules(repoPath, rulesConfig, excludes, scannedFiles = []) {
|
|
1344
1500
|
return [
|
|
1345
1501
|
...evaluateFileRules(repoPath, rulesConfig, excludes),
|
|
1346
1502
|
...evaluateScriptRules(repoPath, rulesConfig),
|
|
1347
1503
|
...evaluatePackageFieldRules(repoPath, rulesConfig),
|
|
1348
|
-
...evaluateEngineVersion(repoPath, rulesConfig)
|
|
1504
|
+
...evaluateEngineVersion(repoPath, rulesConfig),
|
|
1505
|
+
...evaluateCodeowners(repoPath, rulesConfig, scannedFiles)
|
|
1349
1506
|
];
|
|
1350
1507
|
}
|
|
1351
1508
|
//#endregion
|
|
@@ -1507,14 +1664,15 @@ function aggregateReports(reports, versions = {}, config, multiVersions = {}) {
|
|
|
1507
1664
|
}
|
|
1508
1665
|
//#endregion
|
|
1509
1666
|
//#region src/utils/print-errors.ts
|
|
1510
|
-
function printErrors(errors) {
|
|
1667
|
+
function printErrors(errors, isJson) {
|
|
1511
1668
|
if (errors.length === 0) return;
|
|
1512
|
-
|
|
1669
|
+
const stream = isJson ? process.stderr : process.stdout;
|
|
1670
|
+
stream.write(chalk.yellow(`\n⚠ ${errors.length} file(s) failed to parse:\n`));
|
|
1513
1671
|
for (const { file, message } of errors) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1672
|
+
stream.write(chalk.yellow(` ${file}\n`));
|
|
1673
|
+
stream.write(chalk.gray(` ${message}\n`));
|
|
1516
1674
|
}
|
|
1517
|
-
|
|
1675
|
+
stream.write("\n");
|
|
1518
1676
|
}
|
|
1519
1677
|
//#endregion
|
|
1520
1678
|
//#region src/utils/file-utils.ts
|
|
@@ -1869,8 +2027,8 @@ function computeReleaseAge(installedVersion, timeMap, deprecated, thresholds, di
|
|
|
1869
2027
|
daysAgo
|
|
1870
2028
|
});
|
|
1871
2029
|
byBump.set(bump, list);
|
|
1872
|
-
const threshold = thresholds
|
|
1873
|
-
if (
|
|
2030
|
+
const threshold = thresholds[bump];
|
|
2031
|
+
if (threshold !== false && threshold !== void 0 && daysAgo <= threshold && (minCompliantReleasedDaysAgo === void 0 || daysAgo > minCompliantReleasedDaysAgo)) {
|
|
1874
2032
|
minCompliantVersion = version;
|
|
1875
2033
|
minCompliantReleasedDaysAgo = daysAgo;
|
|
1876
2034
|
}
|
|
@@ -1949,16 +2107,20 @@ async function enrichWithReleaseAge(packages, config) {
|
|
|
1949
2107
|
}
|
|
1950
2108
|
//#endregion
|
|
1951
2109
|
//#region src/commands/pipeline.ts
|
|
2110
|
+
const DECLARATION_FILE_RE = /\.d\.(ts|mts|cts)$/;
|
|
2111
|
+
function isDeclarationFile(filePath) {
|
|
2112
|
+
return DECLARATION_FILE_RE.test(filePath);
|
|
2113
|
+
}
|
|
1952
2114
|
/**
|
|
1953
2115
|
* Runs the shared parse → aggregate → rules → release-age pipeline used by
|
|
1954
2116
|
* both `scan` and `comply`. Returns `null` if no files matched (the spinner
|
|
1955
2117
|
* has already reported the failure); throws on unexpected errors.
|
|
1956
2118
|
*/
|
|
1957
|
-
async function runPipeline(config, spinner) {
|
|
2119
|
+
async function runPipeline(config, spinner, isJson) {
|
|
1958
2120
|
const lockfileResult = findAndParseLockfile(process.cwd());
|
|
1959
2121
|
spinner.succeed(chalk.blue(`📦 Found ${lockfileResult.lockfileType} lockfile (supports: ${lockfileResult.supportedVersions.join(", ")}) - ${Object.keys(lockfileResult.versions).length} packages`));
|
|
1960
2122
|
spinner.start("Finding files...");
|
|
1961
|
-
const files = await findFiles(config.includes, config.excludes);
|
|
2123
|
+
const files = (await findFiles(config.includes, config.excludes)).filter((f) => !isDeclarationFile(f));
|
|
1962
2124
|
if (files.length === 0) {
|
|
1963
2125
|
spinner.fail(chalk.red(`No files found matching includes: ${config.includes.join(", ")}`));
|
|
1964
2126
|
return null;
|
|
@@ -1982,9 +2144,9 @@ async function runPipeline(config, spinner) {
|
|
|
1982
2144
|
}
|
|
1983
2145
|
}
|
|
1984
2146
|
spinner.succeed(chalk.green(`Analysis complete! Analyzed ${reports.length}/${files.length} files`));
|
|
1985
|
-
printErrors(parseErrors);
|
|
2147
|
+
printErrors(parseErrors, isJson);
|
|
1986
2148
|
const aggregated = aggregateReports(reports, lockfileResult.versions, config, lockfileResult.multiVersions);
|
|
1987
|
-
const evaluatorViolations = evaluateRules(process.cwd(), config.rules, config.excludes);
|
|
2149
|
+
const evaluatorViolations = evaluateRules(process.cwd(), config.rules, config.excludes, files);
|
|
1988
2150
|
aggregated.ruleViolations = [...aggregated.ruleViolations, ...evaluatorViolations];
|
|
1989
2151
|
if (config.releaseAge.enabled) {
|
|
1990
2152
|
spinner.start("Fetching release age from registry...");
|
|
@@ -2022,7 +2184,7 @@ function registerScanCommand(program) {
|
|
|
2022
2184
|
async function executeScan(config) {
|
|
2023
2185
|
const { isJson, spinner } = createCommandContext(config);
|
|
2024
2186
|
try {
|
|
2025
|
-
const aggregated = await runPipeline(config, spinner);
|
|
2187
|
+
const aggregated = await runPipeline(config, spinner, isJson);
|
|
2026
2188
|
if (!aggregated) return;
|
|
2027
2189
|
if (isJson) printJson(aggregated);
|
|
2028
2190
|
else printScanResults(aggregated, config);
|
|
@@ -2090,7 +2252,7 @@ function registerComplyCommand(program) {
|
|
|
2090
2252
|
async function executeComply(config) {
|
|
2091
2253
|
const { isJson, spinner } = createCommandContext(config);
|
|
2092
2254
|
try {
|
|
2093
|
-
const aggregated = await runPipeline(config, spinner);
|
|
2255
|
+
const aggregated = await runPipeline(config, spinner, isJson);
|
|
2094
2256
|
if (!aggregated) {
|
|
2095
2257
|
process.exitCode = 2;
|
|
2096
2258
|
return;
|