@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 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 withDefaults = (overrides) => ({
981
- ...DEFAULT_EXTERNAL_ANALYSIS_CONFIG,
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
- onProgress?.({ stage: "package_json_loaded" });
1034
- try {
1035
- const directSpecs = parsePackageJson(packageJson.raw);
1036
- const lockfile = selectLockfile(input.repositoryPath);
1037
- let extraction;
1038
- if (lockfile === null) {
1039
- const resolvedGraph = await resolveRegistryGraphFromDirectSpecs(directSpecs, {
1040
- maxNodes: 500,
1041
- maxDepth: 8
1042
- });
1043
- if (resolvedGraph.nodes.length === 0) {
1044
- return {
1045
- targetPath: input.repositoryPath,
1046
- available: false,
1047
- reason: "lockfile_not_found"
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
- const directNames = new Set(extraction.directDependencies.map((dependency) => dependency.name));
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
- let completed = 0;
1072
- const metadataEntries = await mapWithConcurrency(
1073
- extraction.nodes,
1103
+ const metadataEntries = await collectDependencyMetadata(
1104
+ extraction,
1105
+ metadataProvider,
1074
1106
  config.metadataRequestConcurrency,
1075
- async (node) => {
1076
- const result = {
1077
- key: `${node.name}@${node.version}`,
1078
- metadata: await metadataProvider.getMetadata(node.name, node.version, {
1079
- directDependency: directNames.has(node.name)
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(input.repositoryPath, extraction, metadataByKey, config);
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("text")
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(new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")).addOption(new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")).option("--output <path>", "write check output to a file path").option("--no-trace", "disable trace embedding in generated snapshot").action(
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("--baseline-ref <gitRef>", "resolve baseline snapshot from a git reference (for example origin/main)").option("--baseline-sha <sha>", "explicit baseline commit SHA (only valid with --baseline-ref auto)").addOption(
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(new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")).option("--no-trace", "disable trace embedding in generated snapshot").action(
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 {