@getcodesentinel/codesentinel 1.9.3 → 1.9.5
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/index.js +136 -108
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -254,6 +254,46 @@ var DEFAULT_EXTERNAL_ANALYSIS_CONFIG = {
|
|
|
254
254
|
maxHighRiskDependencies: 100,
|
|
255
255
|
metadataRequestConcurrency: 8
|
|
256
256
|
};
|
|
257
|
+
var mapWithConcurrency = async (values, limit, handler) => {
|
|
258
|
+
const effectiveLimit = Math.max(1, limit);
|
|
259
|
+
const workerCount = Math.min(effectiveLimit, values.length);
|
|
260
|
+
const results = new Array(values.length);
|
|
261
|
+
let index = 0;
|
|
262
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
263
|
+
while (true) {
|
|
264
|
+
const current = index;
|
|
265
|
+
index += 1;
|
|
266
|
+
if (current >= values.length) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const value = values[current];
|
|
270
|
+
if (value !== void 0) {
|
|
271
|
+
results[current] = await handler(value);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
await Promise.all(workers);
|
|
276
|
+
return results;
|
|
277
|
+
};
|
|
278
|
+
var collectDependencyMetadata = async (extraction, metadataProvider, concurrency, onProgress) => {
|
|
279
|
+
const directNames = new Set(extraction.directDependencies.map((dependency) => dependency.name));
|
|
280
|
+
let completed = 0;
|
|
281
|
+
return mapWithConcurrency(extraction.nodes, concurrency, async (node) => {
|
|
282
|
+
const result = {
|
|
283
|
+
key: `${node.name}@${node.version}`,
|
|
284
|
+
metadata: await metadataProvider.getMetadata(node.name, node.version, {
|
|
285
|
+
directDependency: directNames.has(node.name)
|
|
286
|
+
})
|
|
287
|
+
};
|
|
288
|
+
completed += 1;
|
|
289
|
+
onProgress?.({
|
|
290
|
+
completed,
|
|
291
|
+
total: extraction.nodes.length,
|
|
292
|
+
packageName: node.name
|
|
293
|
+
});
|
|
294
|
+
return result;
|
|
295
|
+
});
|
|
296
|
+
};
|
|
257
297
|
var LOCKFILE_CANDIDATES = [
|
|
258
298
|
{ fileName: "pnpm-lock.yaml", kind: "pnpm" },
|
|
259
299
|
{ fileName: "package-lock.json", kind: "npm" },
|
|
@@ -545,6 +585,24 @@ var parseYarnLock = (raw, directSpecs) => {
|
|
|
545
585
|
var parseBunLock = (_raw, _directSpecs) => {
|
|
546
586
|
throw new Error("unsupported_lockfile_format");
|
|
547
587
|
};
|
|
588
|
+
var parseLockfileExtraction = (lockfileKind, lockfileRaw, directSpecs) => {
|
|
589
|
+
switch (lockfileKind) {
|
|
590
|
+
case "pnpm":
|
|
591
|
+
return parsePnpmLockfile(lockfileRaw, directSpecs);
|
|
592
|
+
case "npm":
|
|
593
|
+
case "npm-shrinkwrap":
|
|
594
|
+
return {
|
|
595
|
+
...parsePackageLock(lockfileRaw, directSpecs),
|
|
596
|
+
kind: lockfileKind
|
|
597
|
+
};
|
|
598
|
+
case "yarn":
|
|
599
|
+
return parseYarnLock(lockfileRaw, directSpecs);
|
|
600
|
+
case "bun":
|
|
601
|
+
return parseBunLock(lockfileRaw, directSpecs);
|
|
602
|
+
default:
|
|
603
|
+
throw new Error("unsupported_lockfile_format");
|
|
604
|
+
}
|
|
605
|
+
};
|
|
548
606
|
var parseRetryAfterMs = (value) => {
|
|
549
607
|
if (value === null) {
|
|
550
608
|
return null;
|
|
@@ -977,77 +1035,31 @@ var resolveRegistryGraphFromDirectSpecs = async (directSpecs, options) => {
|
|
|
977
1035
|
truncated
|
|
978
1036
|
};
|
|
979
1037
|
};
|
|
980
|
-
var
|
|
981
|
-
|
|
982
|
-
...overrides
|
|
983
|
-
});
|
|
984
|
-
var parseExtraction = (lockfileKind, lockfileRaw, directSpecs) => {
|
|
985
|
-
switch (lockfileKind) {
|
|
986
|
-
case "pnpm":
|
|
987
|
-
return parsePnpmLockfile(lockfileRaw, directSpecs);
|
|
988
|
-
case "npm":
|
|
989
|
-
case "npm-shrinkwrap":
|
|
990
|
-
return {
|
|
991
|
-
...parsePackageLock(lockfileRaw, directSpecs),
|
|
992
|
-
kind: lockfileKind
|
|
993
|
-
};
|
|
994
|
-
case "yarn":
|
|
995
|
-
return parseYarnLock(lockfileRaw, directSpecs);
|
|
996
|
-
case "bun":
|
|
997
|
-
return parseBunLock(lockfileRaw, directSpecs);
|
|
998
|
-
default:
|
|
999
|
-
throw new Error("unsupported_lockfile_format");
|
|
1000
|
-
}
|
|
1001
|
-
};
|
|
1002
|
-
var mapWithConcurrency = async (values, limit, handler) => {
|
|
1003
|
-
const effectiveLimit = Math.max(1, limit);
|
|
1004
|
-
const workerCount = Math.min(effectiveLimit, values.length);
|
|
1005
|
-
const results = new Array(values.length);
|
|
1006
|
-
let index = 0;
|
|
1007
|
-
const workers = Array.from({ length: workerCount }, async () => {
|
|
1008
|
-
while (true) {
|
|
1009
|
-
const current = index;
|
|
1010
|
-
index += 1;
|
|
1011
|
-
if (current >= values.length) {
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
const value = values[current];
|
|
1015
|
-
if (value !== void 0) {
|
|
1016
|
-
results[current] = await handler(value);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
});
|
|
1020
|
-
await Promise.all(workers);
|
|
1021
|
-
return results;
|
|
1022
|
-
};
|
|
1023
|
-
var analyzeDependencyExposure = async (input, metadataProvider, onProgress) => {
|
|
1024
|
-
const config = withDefaults(input.config);
|
|
1025
|
-
const packageJson = loadPackageJson(input.repositoryPath);
|
|
1038
|
+
var prepareDependencyExtraction = async (repositoryPath) => {
|
|
1039
|
+
const packageJson = loadPackageJson(repositoryPath);
|
|
1026
1040
|
if (packageJson === null) {
|
|
1027
1041
|
return {
|
|
1028
|
-
targetPath: input.repositoryPath,
|
|
1029
1042
|
available: false,
|
|
1030
1043
|
reason: "package_json_not_found"
|
|
1031
1044
|
};
|
|
1032
1045
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
extraction = {
|
|
1046
|
+
const directSpecs = parsePackageJson(packageJson.raw);
|
|
1047
|
+
const lockfile = selectLockfile(repositoryPath);
|
|
1048
|
+
if (lockfile === null) {
|
|
1049
|
+
const resolvedGraph = await resolveRegistryGraphFromDirectSpecs(directSpecs, {
|
|
1050
|
+
maxNodes: 500,
|
|
1051
|
+
maxDepth: 8
|
|
1052
|
+
});
|
|
1053
|
+
if (resolvedGraph.nodes.length === 0) {
|
|
1054
|
+
return {
|
|
1055
|
+
available: false,
|
|
1056
|
+
reason: "lockfile_not_found"
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
available: true,
|
|
1061
|
+
lockfileKind: "npm",
|
|
1062
|
+
extraction: {
|
|
1051
1063
|
kind: "npm",
|
|
1052
1064
|
directDependencies: resolvedGraph.directDependencies.map((dependency) => ({
|
|
1053
1065
|
name: dependency.name,
|
|
@@ -1055,46 +1067,61 @@ var analyzeDependencyExposure = async (input, metadataProvider, onProgress) => {
|
|
|
1055
1067
|
scope: dependency.scope
|
|
1056
1068
|
})),
|
|
1057
1069
|
nodes: resolvedGraph.nodes
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
available: true,
|
|
1075
|
+
lockfileKind: lockfile.kind,
|
|
1076
|
+
extraction: parseLockfileExtraction(lockfile.kind, lockfile.raw, directSpecs)
|
|
1077
|
+
};
|
|
1078
|
+
};
|
|
1079
|
+
var withDefaults = (overrides) => ({
|
|
1080
|
+
...DEFAULT_EXTERNAL_ANALYSIS_CONFIG,
|
|
1081
|
+
...overrides
|
|
1082
|
+
});
|
|
1083
|
+
var analyzeDependencyExposure = async (input, metadataProvider, onProgress) => {
|
|
1084
|
+
const config = withDefaults(input.config);
|
|
1085
|
+
try {
|
|
1086
|
+
const prepared = await prepareDependencyExtraction(input.repositoryPath);
|
|
1087
|
+
if (!prepared.available) {
|
|
1088
|
+
return {
|
|
1089
|
+
targetPath: input.repositoryPath,
|
|
1090
|
+
available: false,
|
|
1091
|
+
reason: prepared.reason
|
|
1058
1092
|
};
|
|
1059
|
-
onProgress?.({ stage: "lockfile_selected", kind: "npm" });
|
|
1060
|
-
} else {
|
|
1061
|
-
extraction = parseExtraction(lockfile.kind, lockfile.raw, directSpecs);
|
|
1062
|
-
onProgress?.({ stage: "lockfile_selected", kind: lockfile.kind });
|
|
1063
1093
|
}
|
|
1064
|
-
|
|
1094
|
+
onProgress?.({ stage: "package_json_loaded" });
|
|
1095
|
+
onProgress?.({ stage: "lockfile_selected", kind: prepared.lockfileKind });
|
|
1096
|
+
const { extraction } = prepared;
|
|
1065
1097
|
onProgress?.({
|
|
1066
1098
|
stage: "lockfile_parsed",
|
|
1067
1099
|
dependencyNodes: extraction.nodes.length,
|
|
1068
1100
|
directDependencies: extraction.directDependencies.length
|
|
1069
1101
|
});
|
|
1070
1102
|
onProgress?.({ stage: "metadata_fetch_started", total: extraction.nodes.length });
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1103
|
+
const metadataEntries = await collectDependencyMetadata(
|
|
1104
|
+
extraction,
|
|
1105
|
+
metadataProvider,
|
|
1074
1106
|
config.metadataRequestConcurrency,
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
};
|
|
1082
|
-
completed += 1;
|
|
1083
|
-
onProgress?.({
|
|
1084
|
-
stage: "metadata_fetch_progress",
|
|
1085
|
-
completed,
|
|
1086
|
-
total: extraction.nodes.length,
|
|
1087
|
-
packageName: node.name
|
|
1088
|
-
});
|
|
1089
|
-
return result;
|
|
1090
|
-
}
|
|
1107
|
+
(event) => onProgress?.({
|
|
1108
|
+
stage: "metadata_fetch_progress",
|
|
1109
|
+
completed: event.completed,
|
|
1110
|
+
total: event.total,
|
|
1111
|
+
packageName: event.packageName
|
|
1112
|
+
})
|
|
1091
1113
|
);
|
|
1092
1114
|
onProgress?.({ stage: "metadata_fetch_completed", total: extraction.nodes.length });
|
|
1093
1115
|
const metadataByKey = /* @__PURE__ */ new Map();
|
|
1094
1116
|
for (const entry of metadataEntries) {
|
|
1095
1117
|
metadataByKey.set(entry.key, entry.metadata);
|
|
1096
1118
|
}
|
|
1097
|
-
const summary = buildExternalAnalysisSummary(
|
|
1119
|
+
const summary = buildExternalAnalysisSummary(
|
|
1120
|
+
input.repositoryPath,
|
|
1121
|
+
extraction,
|
|
1122
|
+
metadataByKey,
|
|
1123
|
+
config
|
|
1124
|
+
);
|
|
1098
1125
|
if (summary.available) {
|
|
1099
1126
|
onProgress?.({
|
|
1100
1127
|
stage: "summary_built",
|
|
@@ -5343,10 +5370,7 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
5343
5370
|
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
5344
5371
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5345
5372
|
).addOption(
|
|
5346
|
-
new Option(
|
|
5347
|
-
"--output <mode>",
|
|
5348
|
-
"output mode: summary (default) or json (full analysis object)"
|
|
5349
|
-
).choices(["summary", "json"]).default("summary")
|
|
5373
|
+
new Option("--output <mode>", "output mode: summary (default) or json (full analysis object)").choices(["summary", "json"]).default("summary")
|
|
5350
5374
|
).option("--json", "shortcut for --output json").action(
|
|
5351
5375
|
async (path, options) => {
|
|
5352
5376
|
const logger = createStderrLogger(options.logLevel);
|
|
@@ -5378,12 +5402,7 @@ program.command("explain").argument("[path]", "path to the project to analyze").
|
|
|
5378
5402
|
top: Number.isFinite(top) ? top : 5,
|
|
5379
5403
|
format: options.format
|
|
5380
5404
|
};
|
|
5381
|
-
const result = await runExplainCommand(
|
|
5382
|
-
path,
|
|
5383
|
-
options.authorIdentity,
|
|
5384
|
-
explainOptions,
|
|
5385
|
-
logger
|
|
5386
|
-
);
|
|
5405
|
+
const result = await runExplainCommand(path, options.authorIdentity, explainOptions, logger);
|
|
5387
5406
|
process.stdout.write(`${formatExplainOutput(result, options.format)}
|
|
5388
5407
|
`);
|
|
5389
5408
|
}
|
|
@@ -5394,10 +5413,7 @@ program.command("dependency-risk").argument("<dependency>", "dependency spec to
|
|
|
5394
5413
|
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
5395
5414
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5396
5415
|
).addOption(
|
|
5397
|
-
new Option(
|
|
5398
|
-
"--output <mode>",
|
|
5399
|
-
"output mode: summary (default) or json (full analysis object)"
|
|
5400
|
-
).choices(["summary", "json"]).default("summary")
|
|
5416
|
+
new Option("--output <mode>", "output mode: summary (default) or json (full analysis object)").choices(["summary", "json"]).default("summary")
|
|
5401
5417
|
).option("--json", "shortcut for --output json").option("--max-nodes <count>", "maximum dependency nodes to resolve", "250").option("--max-depth <count>", "maximum dependency depth to traverse", "6").action(
|
|
5402
5418
|
async (dependency, options) => {
|
|
5403
5419
|
const logger = createStderrLogger(options.logLevel);
|
|
@@ -5432,7 +5448,7 @@ program.command("report").argument("[path]", "path to the project to analyze").a
|
|
|
5432
5448
|
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
5433
5449
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5434
5450
|
).addOption(
|
|
5435
|
-
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("
|
|
5451
|
+
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("md")
|
|
5436
5452
|
).option("--output <path>", "write rendered report to a file path").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").action(
|
|
5437
5453
|
async (path, options) => {
|
|
5438
5454
|
const logger = createStderrLogger(options.logLevel);
|
|
@@ -5505,7 +5521,11 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
5505
5521
|
"--log-level <level>",
|
|
5506
5522
|
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
5507
5523
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5508
|
-
).option("--compare <baseline>", "baseline snapshot path").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(
|
|
5524
|
+
).option("--compare <baseline>", "baseline snapshot path").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(
|
|
5525
|
+
new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")
|
|
5526
|
+
).addOption(
|
|
5527
|
+
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")
|
|
5528
|
+
).option("--output <path>", "write check output to a file path").option("--no-trace", "disable trace embedding in generated snapshot").action(
|
|
5509
5529
|
async (path, options) => {
|
|
5510
5530
|
const logger = createStderrLogger(options.logLevel);
|
|
5511
5531
|
try {
|
|
@@ -5548,7 +5568,13 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
|
|
|
5548
5568
|
"--log-level <level>",
|
|
5549
5569
|
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
5550
5570
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5551
|
-
).option("--baseline <path>", "baseline snapshot path").option(
|
|
5571
|
+
).option("--baseline <path>", "baseline snapshot path").option(
|
|
5572
|
+
"--baseline-ref <gitRef>",
|
|
5573
|
+
"resolve baseline snapshot from a git reference (for example origin/main)"
|
|
5574
|
+
).option(
|
|
5575
|
+
"--baseline-sha <sha>",
|
|
5576
|
+
"explicit baseline commit SHA (only valid with --baseline-ref auto)"
|
|
5577
|
+
).addOption(
|
|
5552
5578
|
new Option(
|
|
5553
5579
|
"--main-branch <name>",
|
|
5554
5580
|
"add a default branch candidate for auto baseline resolution (repeatable)"
|
|
@@ -5556,7 +5582,9 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
|
|
|
5556
5582
|
).option(
|
|
5557
5583
|
"--main-branches <names>",
|
|
5558
5584
|
"comma-separated default branch candidates for auto baseline resolution (for example: main,master)"
|
|
5559
|
-
).option("--snapshot <path>", "write current snapshot JSON to path").option("--report <path>", "write markdown CI summary report").option("--json-output <path>", "write machine-readable CI JSON output").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(
|
|
5585
|
+
).option("--snapshot <path>", "write current snapshot JSON to path").option("--report <path>", "write markdown CI summary report").option("--json-output <path>", "write machine-readable CI JSON output").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(
|
|
5586
|
+
new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")
|
|
5587
|
+
).option("--no-trace", "disable trace embedding in generated snapshot").action(
|
|
5560
5588
|
async (path, options) => {
|
|
5561
5589
|
const logger = createStderrLogger(options.logLevel);
|
|
5562
5590
|
try {
|