@socketsecurity/cli-with-sentry 1.9.1 → 1.10.1

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +5 -5
  3. package/dist/cli.js +379 -127
  4. package/dist/cli.js.map +1 -1
  5. package/dist/constants.js +4 -4
  6. package/dist/constants.js.map +1 -1
  7. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  8. package/dist/manifest-scripts/socket-facts.init.gradle +244 -56
  9. package/dist/manifest-scripts/socket-facts.plugin.scala +290 -73
  10. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  11. package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts.map +1 -1
  12. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +5 -2
  13. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -1
  14. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +5 -2
  15. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -1
  16. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +5 -2
  17. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
  18. package/dist/types/commands/manifest/enumerate-workspaces.d.mts.map +1 -1
  19. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +8 -1
  20. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
  21. package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
  22. package/dist/types/commands/manifest/handle-manifest-dynamic-sbom-inference.d.mts +2 -1
  23. package/dist/types/commands/manifest/handle-manifest-dynamic-sbom-inference.d.mts.map +1 -1
  24. package/dist/types/commands/manifest/run-manifest-facts.d.mts +4 -1
  25. package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
  26. package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -1
  27. package/dist/types/commands/manifest/scripts/facts.d.mts +3 -2
  28. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
  29. package/dist/types/commands/manifest/scripts/records.d.mts +10 -4
  30. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
  31. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +3 -2
  32. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -1
  33. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +3 -3
  34. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -1
  35. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +29 -8
  36. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -1
  37. package/dist/types/commands/manifest/scripts/resolution-report.d.mts +14 -3
  38. package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -1
  39. package/dist/types/commands/manifest/scripts/run.d.mts +2 -1
  40. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
  41. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
  42. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  43. package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
  44. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  45. package/dist/types/commands/scan/warn-socket-json-only-flags.d.mts +2 -0
  46. package/dist/types/commands/scan/warn-socket-json-only-flags.d.mts.map +1 -0
  47. package/dist/types/constants.d.mts +1 -1
  48. package/dist/utils.js +2 -1
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1824,12 +1824,16 @@ function reachMemoryLimitToMb(value) {
1824
1824
  // independent reactors is intentionally duplicated across all of their
1825
1825
  // components[].
1826
1826
 
1827
- // `[]` means resolved and found nothing (e.g. a pom/BOM with no artifact).
1827
+ // `[]` means resolved and found nothing (e.g. a pom/BOM with no artifact); no
1828
+ // `targets` means the artifact did not resolve.
1828
1829
  function attachPaths(entry, artifactPaths) {
1829
1830
  const paths = artifactPaths.pathsById.get(entry.id);
1831
+ const targets = paths ? paths.targets : [];
1830
1832
  return {
1831
1833
  ...entry,
1832
- targets: [...(paths?.targets ?? [])],
1834
+ ...(targets ? {
1835
+ targets: [...targets]
1836
+ } : {}),
1833
1837
  sources: [...(paths?.sources ?? [])]
1834
1838
  };
1835
1839
  }
@@ -2351,9 +2355,17 @@ function classifyGradleFailure(detail) {
2351
2355
  if (t.includes('no matching variant') || t.includes('no variants of') || t.includes('unable to find a matching variant') || t.includes('no compatible variant')) {
2352
2356
  return 'no-matching-variant';
2353
2357
  }
2354
- if (t.includes('cannot choose between')) {
2358
+ // The artifact-level form: several transforms produce the requested variant
2359
+ // ("transformation chains" from Gradle 8.12, "transforms" before it).
2360
+ if (t.includes('cannot choose between') || t.includes('found multiple transformation chains') || t.includes('found multiple transforms that can produce a variant')) {
2355
2361
  return 'variant-ambiguity';
2356
2362
  }
2363
+ // One line per problem of the artifact; a checksum missing from the
2364
+ // metadata is no evidence against it, unlike any other problem.
2365
+ const problems = t.split('\n').filter(l => l.startsWith('on artifact '));
2366
+ if (problems.length && problems.every(l => l.endsWith(': checksum is missing from verification metadata.'))) {
2367
+ return 'checksum-missing';
2368
+ }
2357
2369
  if (t.includes('could not get') || t.includes('could not head') || t.includes('status code 401') || t.includes('status code 403') || t.includes('connection refused') || t.includes('connection timed out') || t.includes('read timed out') || t.includes('certification path') || t.includes('peer not authenticated')) {
2358
2370
  return 'repository-or-network';
2359
2371
  }
@@ -2365,8 +2377,9 @@ function classifyGradleFailure(detail) {
2365
2377
 
2366
2378
  // Every kind blocks except variant-ambiguity: the module demonstrably exists
2367
2379
  // and is almost always captured via another configuration, so it's benign for
2368
- // the SBOM (non-blocking, count only). A module that resolves in NO config
2369
- // surfaces as not-found / no-matching-variant, which stay blocking.
2380
+ // the SBOM (non-blocking, count only). The renderer blocks it anyway when the
2381
+ // module resolved in no configuration of its project. Nor does checksum-missing:
2382
+ // teams often record checksums only for what their own tasks resolve.
2370
2383
  const GRADLE_DIALECT = {
2371
2384
  label: 'Gradle',
2372
2385
  classify: classifyGradleFailure,
@@ -2399,8 +2412,13 @@ const GRADLE_DIALECT = {
2399
2412
  blocking: true
2400
2413
  }, {
2401
2414
  key: 'variant-ambiguity',
2415
+ header: n => ` Ambiguous variant — ${n} could not choose a variant, and the module resolved in no other configuration:`,
2402
2416
  blocking: false,
2403
2417
  notice: (n, depCount, configCount) => `Skipped ${depCount} dependency(ies) with ambiguous variant selection in ${configCount} configuration(s) — re-run with --verbose for ${n}'s messages.`
2418
+ }, {
2419
+ key: 'checksum-missing',
2420
+ blocking: false,
2421
+ notice: (n, depCount, configCount) => `Left ${depCount} dependency(ies) uninstalled in ${configCount} configuration(s): ${n}'s verification metadata has no checksum for their artifacts — re-run with --verbose for ${n}'s messages.`
2404
2422
  }]
2405
2423
  };
2406
2424
 
@@ -2445,19 +2463,29 @@ const SBT_DIALECT = {
2445
2463
  }]
2446
2464
  };
2447
2465
 
2448
- // Maven's resolver (Aether/maven-resolver): no attribute-based variants. Two
2449
- // failure shapes (artifact-resolution miss with config = scope, dependency-graph
2450
- // build failure with config = "graph") both classify off the root-cause message.
2466
+ // Maven's resolver (Aether/maven-resolver): no attribute-based variants. Both
2467
+ // failure shapes (artifact-resolution miss, dependency-graph build failure)
2468
+ // classify off the root-cause message.
2451
2469
  function classifyMavenFailure(detail) {
2452
2470
  const t = (detail || '').toLowerCase();
2453
- if (t.includes('could not transfer') || t.includes('connection refused') || t.includes('connect timed out') || t.includes('connection timed out') || t.includes('read timed out') || t.includes('status code: 401') || t.includes('status code: 403') || t.includes('unauthorized') || t.includes('forbidden') || t.includes('peer not authenticated') || t.includes('certpathbuilderexception')) {
2471
+ if (t.includes('could not transfer') || t.includes('connection refused') || t.includes('connect timed out') || t.includes('connection timed out') || t.includes('read timed out') || t.includes('status code: 401') || t.includes('status code: 403') || t.includes('unauthorized') || t.includes('forbidden') || t.includes('peer not authenticated') || t.includes('certpathbuilderexception') ||
2472
+ // Maven 3.8.1+ blocks plain-HTTP repositories behind its
2473
+ // maven-default-http-blocker mirror.
2474
+ t.includes('blocked mirror for repositories') ||
2475
+ // Offline (-o): no repository was asked, so the artifact may well exist.
2476
+ t.includes('in offline mode')) {
2454
2477
  return 'repository-or-network';
2455
2478
  }
2456
- if (t.includes('could not find artifact') || t.includes('failure to find') || t.includes('could not resolve') || t.includes('no versions available') || t.includes('not found')) {
2479
+ if (t.includes('could not find artifact') || t.includes('failure to find') || t.includes('could not resolve') || t.includes('no versions available') || t.includes('not found') ||
2480
+ // maven-resolver's remote repository filters (prefixes, groupId): the
2481
+ // repository is known not to serve that path or group.
2482
+ t.includes('not allowed from')) {
2457
2483
  return 'not-found';
2458
2484
  }
2459
2485
  // POM exists but can't be read/parsed.
2460
- if (t.includes('failed to read artifact descriptor') || t.includes('invalid pom') || t.includes('could not parse pom')) {
2486
+ if (t.includes('failed to read artifact descriptor') || t.includes('invalid pom') || t.includes('could not parse pom') ||
2487
+ // Maven's warning for a POM its descriptor policy tolerated as invalid.
2488
+ t.includes('is invalid, transitive dependencies')) {
2461
2489
  return 'config-problem';
2462
2490
  }
2463
2491
  return 'other';
@@ -2488,68 +2516,143 @@ const MAVEN_DIALECT = {
2488
2516
  }]
2489
2517
  };
2490
2518
 
2519
+ // A resolution that threw without naming a dependency: one configuration, or
2520
+ // with an empty `config` the whole project.
2521
+
2522
+ // A module (`group:name`, from any longer coordinate) within one project.
2523
+ function moduleKey(project, coord) {
2524
+ return `${project}\t${coord.split(':').slice(0, 2).join(':')}`;
2525
+ }
2526
+
2491
2527
  // Recognized from the build tool's message; drives wording AND whether the kind
2492
2528
  // is blocking. An unrecognized message degrades to 'other' (blocking) — safe.
2493
2529
 
2494
2530
  // Per-resolver classify + render/score policy.
2495
2531
 
2532
+ // Where the invoking command lets the user set each build option, so a remedy
2533
+ // names something they can actually set.
2534
+
2535
+ const DSI_SETUP_COMMAND = 'socket manifest setup --dynamic-sbom-inference';
2536
+ const REACH_CONTINUE_ON_INSTALL_ERRORS_FLAG = '--reach-continue-on-install-errors';
2537
+
2538
+ // The single-build manifest commands take every option as a flag.
2539
+ const FLAG_HINTS = {
2540
+ flags: new Set(['excludeConfigs', 'ignoreUnresolved', 'verbose']),
2541
+ setupCommand: 'socket manifest setup',
2542
+ socketJson: constants.default.SOCKET_JSON
2543
+ };
2496
2544
  const RESOLUTION_REPORT_ARTIFACT_LIMIT = 15;
2497
2545
  const RESOLUTION_REPORT_CONFIG_LIMIT = 20;
2498
2546
 
2499
2547
  // Drop a bare "group:name" when a versioned "group:name:v" of the same module
2500
- // is also present: the lenient resolver reports both forms for one failure.
2501
- function dedupCoords(coords) {
2502
- const set = new Set(coords);
2548
+ // failed in the same projects: the lenient resolver reports both forms for one
2549
+ // failure.
2550
+ function dedupInfos(infos) {
2503
2551
  const versioned = new Set();
2504
- for (const c of set) {
2505
- const p = c.split(':');
2552
+ for (const i of infos) {
2553
+ const p = i.coord.split(':');
2506
2554
  if (p.length >= 3) {
2507
- versioned.add(`${p[0]}:${p[1]}`);
2555
+ for (const project of i.projects) {
2556
+ versioned.add(moduleKey(project, i.coord));
2557
+ }
2508
2558
  }
2509
2559
  }
2510
- return [...set].filter(c => c.split(':').length >= 3 || !versioned.has(c)).sort();
2560
+ return infos.filter(i => i.coord.split(':').length >= 3 || ![...i.projects].every(p => versioned.has(moduleKey(p, i.coord)))).sort((a, b) => a.coord < b.coord ? -1 : a.coord > b.coord ? 1 : 0);
2561
+ }
2562
+ function firstLine(s) {
2563
+ return (s || '').split('\n').map(l => l.trim()).find(Boolean) ?? '';
2511
2564
  }
2512
2565
  function fmtList(list, limit) {
2513
2566
  const shown = list.slice(0, limit).join(', ');
2514
2567
  return list.length > limit ? `${shown} (+${list.length - limit} more)` : shown;
2515
2568
  }
2516
- function firstLine(s) {
2517
- return (s || '').split('\n').map(l => l.trim()).find(Boolean) ?? '';
2569
+ function flagText(option, value) {
2570
+ const flag = `--${utils.camelToKebab(option)}`;
2571
+ return value === true ? flag : `${flag} '${value}'`;
2572
+ }
2573
+
2574
+ // Where a failure happened, for builds with several subprojects.
2575
+ function inProjects(projects) {
2576
+ const list = [...(typeof projects === 'string' ? [projects] : projects)].filter(Boolean).sort().map(p => `\`${p}\``);
2577
+ return list.length ? ` (in ${fmtList(list, 3)})` : '';
2578
+ }
2579
+ function installErrorsHint(hints, ignored) {
2580
+ const flag = hints.continueOnInstallErrorsFlag;
2581
+ if (!flag) {
2582
+ return [];
2583
+ }
2584
+ return [ignored ? `Reachability analysis cannot install the ignored dependencies; pass ${flag} unless you already do.` : `If you ignore unresolved dependencies, also pass ${flag}: reachability analysis cannot install them.`];
2518
2585
  }
2519
2586
 
2520
- // Severity is per-kind; the exit-code decision lives in the caller. We do NOT
2521
- // cross-reference what resolved elsewhere: the failed selector carries no
2522
- // classifier/type, so relating a failed and a succeeded dep is unsound.
2523
- function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
2587
+ // `excludableConfigs` is empty when excluding configurations cannot clear
2588
+ // every blocking failure.
2589
+ function proceedHint(hints, tool, excludableConfigs) {
2590
+ const options = [['ignoreUnresolved', true]];
2591
+ if (excludableConfigs.length) {
2592
+ options.push(['excludeConfigs', excludableConfigs.join(',')]);
2593
+ }
2594
+ const asFlags = options.filter(o => hints.flags.has(o[0]));
2595
+ const inSocketJson = options.filter(o => !hints.flags.has(o[0]));
2596
+ const setup = `\`${hints.setupCommand}\``;
2597
+ if (!inSocketJson.length) {
2598
+ return asFlags.length === 1 ? [`To proceed, re-run with ${flagText(...asFlags[0])}.`] : ['To proceed, re-run with either:', ...asFlags.map(o => ` ${flagText(...o)}`)];
2599
+ }
2600
+ if (!asFlags.length) {
2601
+ return [`To proceed, set ${options.length > 1 ? 'either of these' : 'this'} under defaults.manifest.${tool} in ${hints.socketJson} (${setup} walks you through it):`, ...options.map(([option, value]) => ` "${option}": ${JSON.stringify(value)}`)];
2602
+ }
2603
+ return ['To proceed, either:', ...asFlags.map(o => ` re-run with ${flagText(...o)}`), ...inSocketJson.map(([option, value]) => ` set defaults.manifest.${tool}.${option} to ${JSON.stringify(value)} in ${hints.socketJson} (${setup})`)];
2604
+ }
2605
+
2606
+ // What resolved elsewhere is consulted only by module (group:name): a failed
2607
+ // selector carries no classifier/type to relate it to one artifact.
2608
+ function renderResolutionReport(failures, scannedConfigs, tool, opts = {}) {
2609
+ const dialect = dialectFor(tool);
2524
2610
  const name = dialect.label;
2611
+ const hints = opts.hints ?? FLAG_HINTS;
2525
2612
  const unscannable = opts.unscannable ?? [];
2526
- const unscannableConfigs = new Set(unscannable.map(u => u.config));
2613
+ const unscannableConfigs = new Set(unscannable.filter(u => u.config).map(u => u.config));
2527
2614
  const specOf = new Map(dialect.categories.map(c => [c.key, c]));
2528
2615
  const isBlocking = cat => specOf.get(cat)?.blocking ?? true;
2529
2616
 
2530
- // Aggregate by (coord, category): one module can fail with different causes
2531
- // across configs. Keep first-seen detail, union the configs.
2617
+ // A non-blocking kind is only benign when its module resolved anyway: one
2618
+ // that resolved in no configuration of its project is missing from that
2619
+ // project's SBOM.
2620
+ const resolved = opts.resolvedModules;
2621
+ const isFailureBlocking = (category, f) => isBlocking(category) || !!resolved && !resolved.has(moduleKey(f.project, f.coord));
2622
+
2623
+ // Aggregate by (coord, category, blocking): one module can fail with
2624
+ // different causes across configs, and block in only some projects. Keep
2625
+ // first-seen detail, union the configs and projects.
2532
2626
 
2533
2627
  const byKey = new Map();
2534
- const keyOf = (coord, category) => `${coord} ${category}`;
2535
2628
  for (const f of failures) {
2536
2629
  const category = dialect.classify(f.detail);
2537
- const key = keyOf(f.coord, category);
2630
+ const blocking = isFailureBlocking(category, f);
2631
+ const key = `${f.coord} ${category} ${blocking}`;
2538
2632
  let info = byKey.get(key);
2539
2633
  if (!info) {
2540
2634
  info = {
2541
2635
  coord: f.coord,
2542
2636
  category,
2637
+ blocking,
2543
2638
  detail: f.detail,
2544
- configs: new Set()
2639
+ configs: new Set(),
2640
+ projects: new Set(),
2641
+ configless: false
2545
2642
  };
2546
2643
  byKey.set(key, info);
2547
2644
  }
2548
2645
  if (f.config) {
2549
2646
  info.configs.add(f.config);
2647
+ } else {
2648
+ info.configless = true;
2649
+ }
2650
+ if (f.project) {
2651
+ info.projects.add(f.project);
2550
2652
  }
2551
2653
  }
2552
2654
  const allInfos = [...byKey.values()];
2655
+ const isInfoBlocking = info => info.blocking;
2553
2656
 
2554
2657
  // A whole-config throw is classified by the same cause rules as a per-dep
2555
2658
  // failure: ambiguity stays lenient, every other cause is fail-closed.
@@ -2563,31 +2666,43 @@ function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
2563
2666
  });
2564
2667
  const blockingUnscannable = unscannableInfos.filter(u => u.blocking);
2565
2668
  const nonBlockingUnscannable = unscannableInfos.filter(u => !u.blocking);
2669
+ const blockingUnscannableConfigs = blockingUnscannable.filter(u => u.config);
2670
+ const blockingUnscannableProjects = blockingUnscannable.filter(u => !u.config);
2566
2671
  const perDepBlockingConfigs = new Set();
2567
2672
  for (const info of allInfos) {
2568
- if (isBlocking(info.category)) {
2673
+ if (isInfoBlocking(info)) {
2569
2674
  for (const c of info.configs) {
2570
2675
  perDepBlockingConfigs.add(c);
2571
2676
  }
2572
2677
  }
2573
2678
  }
2574
- const blockingConfigs = new Set([...perDepBlockingConfigs, ...blockingUnscannable.map(u => u.config)]);
2679
+ const blockingConfigs = new Set([...perDepBlockingConfigs, ...blockingUnscannableConfigs.map(u => u.config)]);
2575
2680
  const blockingFailed = [...blockingConfigs].sort();
2681
+ // A blocking failure outside any configuration leaves every configuration of
2682
+ // its project incomplete, so none can be reported as succeeded.
2683
+ const hasBlockingConfiglessFailure = blockingUnscannableProjects.length > 0 || allInfos.some(i => i.configless && isInfoBlocking(i));
2576
2684
  // An un-scannable config was attempted but resolved nothing, so it didn't succeed.
2577
- const succeeded = scannedConfigs.filter(c => !blockingConfigs.has(c) && !unscannableConfigs.has(c)).sort();
2685
+ const succeeded = hasBlockingConfiglessFailure ? [] : scannedConfigs.filter(c => !blockingConfigs.has(c) && !unscannableConfigs.has(c)).sort();
2578
2686
  const groups = dialect.categories.map(spec => ({
2579
2687
  spec,
2580
- infos: dedupCoords(allInfos.filter(i => i.category === spec.key).map(i => i.coord)).map(c => byKey.get(keyOf(c, spec.key)))
2688
+ infos: dedupInfos(allInfos.filter(i => i.category === spec.key))
2689
+ }));
2690
+ const blockingGroups = groups.map(g => ({
2691
+ ...g,
2692
+ infos: g.infos.filter(isInfoBlocking)
2693
+ })).filter(g => g.infos.length);
2694
+ const nonBlockingGroups = groups.map(g => ({
2695
+ ...g,
2696
+ infos: g.infos.filter(i => !isInfoBlocking(i))
2581
2697
  })).filter(g => g.infos.length);
2582
- const blockingGroups = groups.filter(g => g.spec.blocking);
2583
- const nonBlockingGroups = groups.filter(g => !g.spec.blocking);
2584
2698
  const blockingCount = blockingGroups.reduce((n, g) => n + g.infos.length, 0);
2585
2699
  const hasBlockingFailures = blockingCount > 0 || blockingUnscannable.length > 0;
2586
2700
  const willFail = hasBlockingFailures && !opts.ignoreUnresolved;
2587
2701
  const out = [];
2588
2702
  if (hasBlockingFailures) {
2589
2703
  if (blockingCount > 0) {
2590
- out.push(opts.ignoreUnresolved ? `Ignored ${blockingCount} unresolved dependency(ies) in ${perDepBlockingConfigs.size} configuration(s):` : `Could not resolve ${blockingCount} dependency(ies) in ${perDepBlockingConfigs.size} configuration(s):`);
2704
+ const inConfigs = perDepBlockingConfigs.size ? ` in ${perDepBlockingConfigs.size} configuration(s)` : '';
2705
+ out.push(opts.ignoreUnresolved ? `Ignored ${blockingCount} unresolved dependency(ies)${inConfigs}:` : `Could not resolve ${blockingCount} dependency(ies)${inConfigs}:`);
2591
2706
  for (const {
2592
2707
  infos,
2593
2708
  spec
@@ -2597,29 +2712,36 @@ function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
2597
2712
  for (const info of infos.slice(0, RESOLUTION_REPORT_ARTIFACT_LIMIT)) {
2598
2713
  const fl = firstLine(info.detail);
2599
2714
  const reasonSuffix = spec.showReason && fl ? ` [${fl}]` : '';
2600
- out.push(` - ${info.coord}${reasonSuffix}`);
2715
+ out.push(` - ${info.coord}${inProjects(info.projects)}${reasonSuffix}`);
2601
2716
  }
2602
2717
  if (infos.length > RESOLUTION_REPORT_ARTIFACT_LIMIT) {
2603
2718
  out.push(` … and ${infos.length - RESOLUTION_REPORT_ARTIFACT_LIMIT} more`);
2604
2719
  }
2605
2720
  }
2606
2721
  }
2607
- if (blockingUnscannable.length) {
2608
- // Separate from the per-dep block above, but only if there is one — otherwise
2609
- // the summary would lead with a blank line (a dangling ✗ under logger.fail).
2722
+ const pushUnscannable = (items, header, label) => {
2723
+ if (!items.length) {
2724
+ return;
2725
+ }
2726
+ // Separate from the block above, but only if there is one — otherwise the
2727
+ // summary would lead with a blank line (a dangling ✗ under logger.fail).
2610
2728
  if (out.length) {
2611
2729
  out.push('');
2612
2730
  }
2613
- out.push(opts.ignoreUnresolved ? `Ignored ${blockingUnscannable.length} configuration(s) that could not be scanned:` : `Could not scan ${blockingUnscannable.length} configuration(s) (reason from ${name}):`);
2614
- for (const u of blockingUnscannable.slice(0, RESOLUTION_REPORT_CONFIG_LIMIT)) {
2731
+ out.push(header);
2732
+ for (const u of items.slice(0, RESOLUTION_REPORT_CONFIG_LIMIT)) {
2615
2733
  const fl = firstLine(u.detail);
2616
- out.push(` - ${u.config}${fl ? ` [${fl}]` : ''}`);
2734
+ out.push(` - ${label(u)}${fl ? ` [${fl}]` : ''}`);
2617
2735
  }
2618
- if (blockingUnscannable.length > RESOLUTION_REPORT_CONFIG_LIMIT) {
2619
- out.push(` … and ${blockingUnscannable.length - RESOLUTION_REPORT_CONFIG_LIMIT} more`);
2736
+ if (items.length > RESOLUTION_REPORT_CONFIG_LIMIT) {
2737
+ out.push(` … and ${items.length - RESOLUTION_REPORT_CONFIG_LIMIT} more`);
2620
2738
  }
2739
+ };
2740
+ pushUnscannable(blockingUnscannableConfigs, opts.ignoreUnresolved ? `Ignored ${blockingUnscannableConfigs.length} configuration(s) that could not be scanned:` : `Could not scan ${blockingUnscannableConfigs.length} configuration(s) (reason from ${name}):`, u => `${u.config}${inProjects(u.project)}`);
2741
+ pushUnscannable(blockingUnscannableProjects, opts.ignoreUnresolved ? `Ignored ${blockingUnscannableProjects.length} project(s) whose dependencies could not be resolved:` : `Could not resolve the dependencies of ${blockingUnscannableProjects.length} project(s) (reason from ${name}):`, u => `\`${u.project}\``);
2742
+ if (succeeded.length || blockingFailed.length) {
2743
+ out.push('');
2621
2744
  }
2622
- out.push('');
2623
2745
  if (succeeded.length) {
2624
2746
  out.push(`Resolution succeeded in: ${fmtList(succeeded, RESOLUTION_REPORT_CONFIG_LIMIT)}`);
2625
2747
  }
@@ -2628,14 +2750,12 @@ function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
2628
2750
  }
2629
2751
  if (willFail) {
2630
2752
  out.push('');
2631
- out.push(`To proceed, re-run with either:`);
2632
- out.push(` --ignore-unresolved`);
2633
- if (blockingFailed.length) {
2634
- out.push(` --exclude-configs '${blockingFailed.join(',')}'`);
2635
- }
2753
+ out.push(...proceedHint(hints, tool, hasBlockingConfiglessFailure ? [] : blockingFailed), ...installErrorsHint(hints, false));
2754
+ } else if (hints.continueOnInstallErrorsFlag) {
2755
+ out.push('', ...installErrorsHint(hints, true));
2636
2756
  }
2637
2757
  out.push('');
2638
- out.push(`Re-run with --verbose for ${name}'s full messages.`);
2758
+ out.push(hints.flags.has('verbose') ? `Re-run with --verbose for ${name}'s full messages.` : `For ${name}'s full messages, ${verboseHint(hints, tool)}.`);
2639
2759
  }
2640
2760
  const notices = [];
2641
2761
  for (const {
@@ -2648,11 +2768,11 @@ function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
2648
2768
  const configCount = new Set(infos.flatMap(i => [...i.configs])).size;
2649
2769
  notices.push(spec.notice(name, infos.length, configCount));
2650
2770
  }
2651
- // A config-level throw whose cause classifies as variant ambiguity is surfaced, not failed —
2652
- // matching the deliberately-lenient per-dep variant-ambiguity policy.
2771
+ // A config-level throw whose cause classifies as variant ambiguity is surfaced, not failed: many
2772
+ // configurations are marked resolvable yet too underspecified to resolve.
2653
2773
  if (nonBlockingUnscannable.length) {
2654
2774
  const n = new Set(nonBlockingUnscannable.map(u => u.config)).size;
2655
- notices.push(`Could not scan ${n} configuration(s) — re-run with --verbose for ${name}'s messages.`);
2775
+ notices.push(hints.flags.has('verbose') ? `Could not scan ${n} configuration(s) — re-run with --verbose for ${name}'s messages.` : `Could not scan ${n} configuration(s); for ${name}'s messages, ${verboseHint(hints, tool)}.`);
2656
2776
  }
2657
2777
  const detailLines = [`${name}'s full message for each unresolved dependency:`];
2658
2778
  for (const info of allInfos) {
@@ -2664,10 +2784,10 @@ function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
2664
2784
  }
2665
2785
  if (unscannable.length) {
2666
2786
  detailLines.push('');
2667
- detailLines.push(`${name} configurations that could not be scanned:`);
2787
+ detailLines.push(`${name} resolutions that could not be scanned:`);
2668
2788
  for (const u of unscannable) {
2669
2789
  detailLines.push('');
2670
- detailLines.push(` ${u.config}:`);
2790
+ detailLines.push(` ${u.config || 'all dependencies'}${inProjects(u.project)}:`);
2671
2791
  for (const line of (u.detail || '(no message)').split('\n')) {
2672
2792
  detailLines.push(` ${line}`);
2673
2793
  }
@@ -2691,7 +2811,10 @@ function dialectFor(tool) {
2691
2811
  }
2692
2812
  }
2693
2813
  function renderResolutionErrorReport(failures, scannedConfigs = [], tool = 'gradle', opts = {}) {
2694
- return renderResolutionReport(failures, scannedConfigs, dialectFor(tool), opts);
2814
+ return renderResolutionReport(failures, scannedConfigs, tool, opts);
2815
+ }
2816
+ function verboseHint(hints, tool) {
2817
+ return hints.flags.has('verbose') ? 'pass --verbose' : `set defaults.manifest.${tool}.verbose to true in ${hints.socketJson}`;
2695
2818
  }
2696
2819
 
2697
2820
  const PURL_TYPE_MAVEN = 'maven';
@@ -2726,7 +2849,7 @@ function assembleFacts(parsed, opts = {}) {
2726
2849
  return {
2727
2850
  facts,
2728
2851
  report: buildReport(parsed),
2729
- artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], graphs, fileExists)
2852
+ artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], [...parsed.excludedProjects.values()], graphs, fileExists)
2730
2853
  };
2731
2854
  }
2732
2855
 
@@ -2753,11 +2876,13 @@ function buildPerRoot(parsed) {
2753
2876
  children: [],
2754
2877
  direct: false,
2755
2878
  project: n.project,
2756
- targets: []
2879
+ targets: [],
2880
+ unfetched: false
2757
2881
  };
2758
2882
  nodes.set(id, node);
2759
2883
  }
2760
2884
  node.direct ||= n.direct;
2885
+ node.unfetched ||= n.unfetched;
2761
2886
  node.targets.push(...n.targets);
2762
2887
  }
2763
2888
  for (const [p, c] of r.edges) {
@@ -2797,7 +2922,7 @@ function mergeById(perRoot) {
2797
2922
  prod: false,
2798
2923
  direct: false,
2799
2924
  project: node.project,
2800
- targets: new Set()
2925
+ resolutions: []
2801
2926
  };
2802
2927
  finalNodes.set(coordId, fn);
2803
2928
  }
@@ -2807,8 +2932,11 @@ function mergeById(perRoot) {
2807
2932
  if (node.direct) {
2808
2933
  fn.direct = true;
2809
2934
  }
2810
- for (const t of node.targets) {
2811
- fn.targets.add(t);
2935
+ if (node.unfetched || node.targets.length) {
2936
+ fn.resolutions.push({
2937
+ targets: node.targets,
2938
+ unfetched: node.unfetched
2939
+ });
2812
2940
  }
2813
2941
  }
2814
2942
  }
@@ -3029,19 +3157,34 @@ function byProject(projects, graphs, ids) {
3029
3157
  return [p.projectKey, g ? [...ids(g)].sort() : []];
3030
3158
  }));
3031
3159
  }
3032
- function buildArtifactPaths(finalNodes, projects, graphs, fileExists) {
3160
+ function buildArtifactPaths(finalNodes, projects, excludedProjects, graphs, fileExists) {
3033
3161
  const pathsById = new Map();
3162
+ // Every root that fetched an artifact lists its file, so check each path once.
3163
+ const existing = new Map();
3164
+ const exists = p => {
3165
+ let e = existing.get(p);
3166
+ if (e === undefined) {
3167
+ e = fileExists(p);
3168
+ existing.set(p, e);
3169
+ }
3170
+ return e;
3171
+ };
3034
3172
  for (const [id, fn] of finalNodes) {
3035
3173
  if (!fn.project) {
3036
- pathsById.set(id, {
3174
+ // Only a root that fetched every file of the artifact, all still on
3175
+ // disk, resolved it: a partial list would claim it resolved whole.
3176
+ const complete = fn.resolutions.filter(r => !r.unfetched && r.targets.every(exists));
3177
+ pathsById.set(id, fn.resolutions.length && !complete.length ? {
3178
+ sources: []
3179
+ } : {
3037
3180
  sources: [],
3038
- targets: [...fn.targets].filter(fileExists).sort()
3181
+ targets: [...new Set(complete.flatMap(r => r.targets))].sort()
3039
3182
  });
3040
3183
  }
3041
3184
  }
3042
3185
  // A project's own component shares its id, so this also covers dependency
3043
3186
  // edges onto a sibling project.
3044
- for (const p of projects) {
3187
+ for (const p of [...projects, ...excludedProjects]) {
3045
3188
  pathsById.set(p.projectKey, {
3046
3189
  sources: [...new Set(p.sources)].filter(fileExists).sort(),
3047
3190
  targets: [...new Set(p.targets)].filter(fileExists).sort()
@@ -3056,7 +3199,7 @@ function buildArtifactPaths(finalNodes, projects, graphs, fileExists) {
3056
3199
  function buildReport(parsed) {
3057
3200
  const seen = new Set();
3058
3201
  const failures = parsed.failures.filter(f => {
3059
- const key = `${f.coord}|${f.detail}|${f.config}`;
3202
+ const key = `${f.coord}|${f.detail}|${f.config}|${f.project}`;
3060
3203
  if (seen.has(key)) {
3061
3204
  return false;
3062
3205
  }
@@ -3065,15 +3208,24 @@ function buildReport(parsed) {
3065
3208
  });
3066
3209
  const seenUnscannable = new Set();
3067
3210
  const unscannable = parsed.unscannable.filter(u => {
3068
- const key = `${u.config}|${u.detail}`;
3211
+ const key = `${u.config}|${u.detail}|${u.project}`;
3069
3212
  if (seenUnscannable.has(key)) {
3070
3213
  return false;
3071
3214
  }
3072
3215
  seenUnscannable.add(key);
3073
3216
  return true;
3074
3217
  });
3218
+ const resolvedModules = new Set();
3219
+ if (failures.length) {
3220
+ for (const r of parsed.roots.values()) {
3221
+ for (const n of r.nodes.values()) {
3222
+ resolvedModules.add(moduleKey(r.projectKey, `${n.coord.group}:${n.coord.name}`));
3223
+ }
3224
+ }
3225
+ }
3075
3226
  return {
3076
3227
  failures,
3228
+ resolvedModules,
3077
3229
  scannedConfigs: parsed.scannedConfigs,
3078
3230
  unscannable
3079
3231
  };
@@ -3227,13 +3379,15 @@ function serializeExcludePathPatterns(paths) {
3227
3379
  // projectSrc projectKey path (--with-files only)
3228
3380
  // projectTgt projectKey path (--with-files only)
3229
3381
  // projectBuild projectKey path (build-root-relative; may be absent on disk)
3382
+ // excludedProject projectKey (--with-files only; paths without a project)
3230
3383
  // root rootId projectKey config prod(0|1)
3231
3384
  // node rootId coordId group name version ext classifier direct(0|1) project
3232
3385
  // edge rootId parentCoordId childCoordId
3233
3386
  // file rootId coordId path (--with-files only)
3387
+ // unfetched rootId coordId (--with-files only; requested artifact not fetched)
3234
3388
  // scanned config
3235
- // failure coord detail config
3236
- // unscannable config detail
3389
+ // failure coord detail config projectKey (config empty: not placeable in one)
3390
+ // unscannable config detail projectKey (config empty: whole project)
3237
3391
  //
3238
3392
  // `projectKey` is the tool's unique project identity (Maven: GAV; Gradle:
3239
3393
  // project path; sbt: project id), emitted as the project id; a `node` whose
@@ -3269,12 +3423,14 @@ function parseRecords(text) {
3269
3423
  buildRoot: '',
3270
3424
  entry: '',
3271
3425
  projects: new Map(),
3426
+ excludedProjects: new Map(),
3272
3427
  roots: new Map(),
3273
3428
  scannedConfigs: [],
3274
3429
  failures: [],
3275
3430
  unscannable: []
3276
3431
  };
3277
3432
  const scanned = new Set();
3433
+ const excluded = new Set();
3278
3434
  const root = id => {
3279
3435
  let r = result.roots.get(id);
3280
3436
  if (!r) {
@@ -3343,6 +3499,9 @@ function parseRecords(text) {
3343
3499
  project(f[1] ?? '').targets.push(f[2]);
3344
3500
  }
3345
3501
  break;
3502
+ case 'excludedProject':
3503
+ excluded.add(f[1] ?? '');
3504
+ break;
3346
3505
  case 'projectBuild':
3347
3506
  if (f[2]) {
3348
3507
  project(f[1] ?? '').buildFiles.push(f[2]);
@@ -3371,7 +3530,8 @@ function parseRecords(text) {
3371
3530
  },
3372
3531
  direct: bool(f[8]),
3373
3532
  project: f[9] ?? '',
3374
- targets: []
3533
+ targets: [],
3534
+ unfetched: false
3375
3535
  });
3376
3536
  break;
3377
3537
  }
@@ -3384,6 +3544,14 @@ function parseRecords(text) {
3384
3544
  }
3385
3545
  break;
3386
3546
  }
3547
+ case 'unfetched':
3548
+ {
3549
+ const node = root(f[1] ?? '').nodes.get(f[2] ?? '');
3550
+ if (node) {
3551
+ node.unfetched = true;
3552
+ }
3553
+ break;
3554
+ }
3387
3555
  case 'file':
3388
3556
  {
3389
3557
  const node = root(f[1] ?? '').nodes.get(f[2] ?? '');
@@ -3402,20 +3570,29 @@ function parseRecords(text) {
3402
3570
  result.failures.push({
3403
3571
  coord: f[1],
3404
3572
  detail: f[2] ?? '',
3405
- config: f[3] ?? ''
3573
+ config: f[3] ?? '',
3574
+ project: f[4] ?? ''
3406
3575
  });
3407
3576
  }
3408
3577
  break;
3409
3578
  case 'unscannable':
3410
- if (f[1]) {
3579
+ if (f[1] || f[3]) {
3411
3580
  result.unscannable.push({
3412
- config: f[1],
3413
- detail: f[2] ?? ''
3581
+ config: f[1] ?? '',
3582
+ detail: f[2] ?? '',
3583
+ project: f[3] ?? ''
3414
3584
  });
3415
3585
  }
3416
3586
  break;
3417
3587
  }
3418
3588
  }
3589
+ for (const key of excluded) {
3590
+ const p = result.projects.get(key);
3591
+ if (p) {
3592
+ result.projects.delete(key);
3593
+ result.excludedProjects.set(key, p);
3594
+ }
3595
+ }
3419
3596
  result.scannedConfigs = [...scanned].sort();
3420
3597
  return result;
3421
3598
  }
@@ -3431,6 +3608,10 @@ function manifestScriptsPath(...parts) {
3431
3608
  return path.join(constants.default.distPath, 'manifest-scripts', ...parts);
3432
3609
  }
3433
3610
 
3611
+ // Streamed build output is a diagnostic: it goes to stderr so the command's own
3612
+ // stdout (e.g. --json) stays machine-readable.
3613
+ const STREAM_TO_STDERR = ['inherit', 2, 'inherit'];
3614
+
3434
3615
  // Don't throw on a non-zero exit: the script emits failure records, so a usable
3435
3616
  // records file still exists. A non-exit spawn error (e.g. missing executable)
3436
3617
  // propagates.
@@ -3438,7 +3619,7 @@ async function runNeverThrow(bin, args, opts) {
3438
3619
  try {
3439
3620
  const result = await spawn.spawn(bin, args, {
3440
3621
  cwd: opts.projectDir,
3441
- stdio: opts.stdio ?? 'inherit',
3622
+ stdio: opts.stdio === 'pipe' ? 'pipe' : [...STREAM_TO_STDERR],
3442
3623
  ...(opts.env ? {
3443
3624
  env: opts.env
3444
3625
  } : {}),
@@ -3563,7 +3744,10 @@ async function invokeGradle(opts, initScriptFilename, task, tmpDirPrefix) {
3563
3744
  const bin = resolveBuildToolBin('gradle', opts.projectDir, opts.bin);
3564
3745
  // Disable the configuration cache: the init script's legacy
3565
3746
  // resolvedConfiguration API and shared accumulator aren't cache-safe.
3566
- const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []), task, '--no-daemon', '--console=plain'];
3747
+ const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false',
3748
+ // Names each artifact's own verification problem, so a checksum missing
3749
+ // from the metadata can be told apart from a mismatch.
3750
+ '-Porg.gradle.dependency.verification.console=verbose', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []), task, '--no-daemon', '--console=plain'];
3567
3751
  const out = await runNeverThrow(bin, args, opts);
3568
3752
  return await assembleFromRecords('gradle', out, recordsFile);
3569
3753
  });
@@ -3577,6 +3761,11 @@ async function enumerateGradleWorkspaces(opts) {
3577
3761
  async function invokeSbtIn(globalBase, opts, pluginSourceFilename, pluginDestFilename, task) {
3578
3762
  await writeSbtPlugin(globalBase, pluginSourceFilename, pluginDestFilename);
3579
3763
  const recordsFile = path.join(globalBase, 'records.tsv');
3764
+ // The global base is shared across sbt roots: a build that dies before
3765
+ // writing must not be read as the previous root's records.
3766
+ await u.promises.rm(recordsFile, {
3767
+ force: true
3768
+ });
3580
3769
  const bin = resolveBuildToolBin('sbt', opts.projectDir, opts.bin);
3581
3770
  // Fresh per-run global base (not ~/.sbt): sbt executes everything under
3582
3771
  // plugins/, so a shared path is a code-injection surface. BSP off for this
@@ -3654,6 +3843,7 @@ async function runManifestFacts({
3654
3843
  ecosystem,
3655
3844
  excludeConfigs,
3656
3845
  excludePaths,
3846
+ hints = FLAG_HINTS,
3657
3847
  ignoreUnresolved,
3658
3848
  includeConfigs,
3659
3849
  javaHome,
@@ -3705,7 +3895,7 @@ async function runManifestFacts({
3705
3895
  logger.logger.info(`(Running ${ecosystem} with output streaming; this can take a while.)`);
3706
3896
  result = await runManifestScript(ecosystem, scriptOpts);
3707
3897
  } else {
3708
- logger.logger.info(`(No live output; pass --verbose to stream the ${ecosystem} build output.)`);
3898
+ logger.logger.info(`(No live output; to stream the ${ecosystem} build output, ${verboseHint(hints, ecosystem)}.)`);
3709
3899
  spinner.start(`Resolving ${ecosystem} dependencies ...`);
3710
3900
  result = await runManifestScript(ecosystem, scriptOpts);
3711
3901
  if (result.code === 0) {
@@ -3721,7 +3911,7 @@ async function runManifestFacts({
3721
3911
  spinner.failAndStop(`Failed to run ${ecosystem}.`);
3722
3912
  }
3723
3913
  process.exitCode = 1;
3724
- logger.logger.fail(`Could not run the ${ecosystem} build tool` + (verbose ? `: ${utils.getErrorMessageOr(e, String(e))}` : ' (run with --verbose for details).'));
3914
+ logger.logger.fail(`Could not run the ${ecosystem} build tool: ${utils.getErrorMessageOr(e, String(e))}`);
3725
3915
  return null;
3726
3916
  }
3727
3917
  const {
@@ -3735,7 +3925,9 @@ async function runManifestFacts({
3735
3925
  stdout
3736
3926
  } = result;
3737
3927
  const rendered = renderResolutionErrorReport(report.failures, report.scannedConfigs, ecosystem, {
3928
+ hints,
3738
3929
  ignoreUnresolved,
3930
+ resolvedModules: report.resolvedModules,
3739
3931
  unscannable: report.unscannable
3740
3932
  });
3741
3933
  if (rendered.hasBlockingFailures) {
@@ -3821,16 +4013,22 @@ async function runManifestFacts({
3821
4013
  };
3822
4014
  }
3823
4015
 
4016
+ // Relative to the user's shell when inside it, else absolute.
4017
+ function displayPath(file) {
4018
+ const rel = path.relative(process.cwd(), file);
4019
+ return rel.startsWith('..') || path.isAbsolute(rel) ? file : rel;
4020
+ }
4021
+
3824
4022
  // A cascaded (not just root-level) disabled additionally skips this one build
3825
4023
  // root here, on top of its existing root-only ecosystem-wide meaning for
3826
4024
  // auto/gradle/etc. facts:false has no pom-mode equivalent here (facts-only),
3827
4025
  // so it skips too.
3828
- function getSkipReason(disabled, facts) {
4026
+ function getSkipReason(ecosystem, disabled, facts) {
3829
4027
  if (disabled) {
3830
- return 'defaults.manifest.<ecosystem>.disabled is true';
4028
+ return `defaults.manifest.${ecosystem}.disabled is true`;
3831
4029
  }
3832
4030
  if (facts === false) {
3833
- return 'defaults.manifest.<ecosystem>.facts is false (pom mode)';
4031
+ return `defaults.manifest.${ecosystem}.facts is false (pom mode)`;
3834
4032
  }
3835
4033
  return undefined;
3836
4034
  }
@@ -3849,6 +4047,8 @@ function nearestDisabledRoot(dir, disabledRoots) {
3849
4047
  async function runEcosystemCandidates({
3850
4048
  candidatesByTool,
3851
4049
  excludePaths,
4050
+ ignoreUnresolved: ignoreUnresolvedOverride,
4051
+ optionFlags,
3852
4052
  realCwd,
3853
4053
  rootSockJson,
3854
4054
  sbtTmpDir,
@@ -3879,7 +4079,8 @@ async function runEcosystemCandidates({
3879
4079
  ignoreUnresolved,
3880
4080
  includeConfigs,
3881
4081
  javaHome,
3882
- skipReason
4082
+ skipReason,
4083
+ verbose: configVerbose
3883
4084
  } = resolveEcosystemConfig(ecosystem, dir, sockJson);
3884
4085
  if (skipReason) {
3885
4086
  // Only warn for a genuinely new disabled root, not one already
@@ -3914,12 +4115,18 @@ async function runEcosystemCandidates({
3914
4115
  ecosystem,
3915
4116
  excludeConfigs,
3916
4117
  excludePaths: excludePathsForRoot,
3917
- ignoreUnresolved,
4118
+ hints: {
4119
+ continueOnInstallErrorsFlag: withFiles ? REACH_CONTINUE_ON_INSTALL_ERRORS_FLAG : undefined,
4120
+ flags: optionFlags,
4121
+ setupCommand: DSI_SETUP_COMMAND,
4122
+ socketJson: displayPath(path.join(dir, constants.SOCKET_JSON))
4123
+ },
4124
+ ignoreUnresolved: ignoreUnresolvedOverride ?? ignoreUnresolved,
3918
4125
  includeConfigs,
3919
4126
  javaHome,
3920
4127
  sidecarAcc,
3921
4128
  tmpDir: ecosystem === 'sbt' ? sbtTmpDir : undefined,
3922
- verbose,
4129
+ verbose: verbose || configVerbose,
3923
4130
  withFiles
3924
4131
  });
3925
4132
  if (result === null) {
@@ -3928,8 +4135,11 @@ async function runEcosystemCandidates({
3928
4135
  ecosystem,
3929
4136
  status: 'failed'
3930
4137
  });
3931
- logger.logger.warn(`Aborting ${ecosystem} discovery: ${dir}'s workspace layout could not be determined, so its remaining build roots cannot be safely classified as covered or independent.`);
3932
- for (const abortedDir of dirs.slice(dirIndex + 1)) {
4138
+ const abortedDirs = dirs.slice(dirIndex + 1);
4139
+ if (abortedDirs.length) {
4140
+ logger.logger.warn(`Aborting ${ecosystem} discovery: ${dir} failed to generate Socket facts, and without them the remaining build roots cannot be safely classified as covered or independent.`);
4141
+ }
4142
+ for (const abortedDir of abortedDirs) {
3933
4143
  outcomes.push({
3934
4144
  dir: abortedDir,
3935
4145
  ecosystem,
@@ -3966,6 +4176,8 @@ async function runEcosystemCandidates({
3966
4176
  async function generateRecursiveManifests({
3967
4177
  cwd,
3968
4178
  excludePaths,
4179
+ ignoreUnresolved,
4180
+ optionFlags = new Set(),
3969
4181
  sbtTmpDir,
3970
4182
  sidecarAcc,
3971
4183
  verbose,
@@ -3987,6 +4199,8 @@ async function generateRecursiveManifests({
3987
4199
  const runAll = resolvedSbtTmpDir => runEcosystemCandidates({
3988
4200
  candidatesByTool,
3989
4201
  excludePaths,
4202
+ ignoreUnresolved,
4203
+ optionFlags,
3990
4204
  realCwd,
3991
4205
  rootSockJson,
3992
4206
  sbtTmpDir: resolvedSbtTmpDir,
@@ -3998,10 +4212,10 @@ async function generateRecursiveManifests({
3998
4212
  // A shared global base across every sbt root in this run lets sbt's own
3999
4213
  // Scala-toolchain cache under <base>/boot survive between invocations
4000
4214
  // instead of being reprovisioned per root (the plugin file is rewritten and
4001
- // records.tsv is fully overwritten - not appended - on every invocation, so
4002
- // reuse is safe). A caller-supplied sbtTmpDir is reused as-is (the caller
4003
- // owns its lifetime); otherwise one is allocated and cleaned up here, but
4004
- // only when there's an sbt root to benefit from sharing it at all.
4215
+ // records.tsv is removed before every invocation, so reuse is safe). A
4216
+ // caller-supplied sbtTmpDir is reused as-is (the caller owns its lifetime);
4217
+ // otherwise one is allocated and cleaned up here, but only when there's an
4218
+ // sbt root to benefit from sharing it at all.
4005
4219
  const outcomes = sbtTmpDir ? await runAll(sbtTmpDir) : candidatesByTool.get('sbt')?.length ? await utils.withTmpDir('socket-sbt-facts-shared-', runAll) : await runAll(undefined);
4006
4220
  if (verbose) {
4007
4221
  logger.logger.info(`Discovered ${outcomes.length} build-tool candidate(s).`);
@@ -4023,7 +4237,8 @@ function resolveEcosystemConfig(ecosystem, dir, sockJson) {
4023
4237
  ignoreUnresolved: Boolean(config?.ignoreUnresolved),
4024
4238
  includeConfigs: config?.includeConfigs ?? '',
4025
4239
  javaHome: config?.javaHome ?? undefined,
4026
- skipReason: getSkipReason(config?.disabled, config?.facts)
4240
+ skipReason: getSkipReason(ecosystem, config?.disabled, config?.facts),
4241
+ verbose: Boolean(config?.verbose)
4027
4242
  };
4028
4243
  }
4029
4244
  if (ecosystem === 'gradle') {
@@ -4036,7 +4251,8 @@ function resolveEcosystemConfig(ecosystem, dir, sockJson) {
4036
4251
  ignoreUnresolved: Boolean(config?.ignoreUnresolved),
4037
4252
  includeConfigs: config?.includeConfigs ?? '',
4038
4253
  javaHome: config?.javaHome ?? undefined,
4039
- skipReason: getSkipReason(config?.disabled, config?.facts)
4254
+ skipReason: getSkipReason(ecosystem, config?.disabled, config?.facts),
4255
+ verbose: Boolean(config?.verbose)
4040
4256
  };
4041
4257
  }
4042
4258
  const config = sockJson.defaults?.manifest?.maven;
@@ -4048,7 +4264,8 @@ function resolveEcosystemConfig(ecosystem, dir, sockJson) {
4048
4264
  ignoreUnresolved: Boolean(config?.ignoreUnresolved),
4049
4265
  includeConfigs: config?.includeConfigs ?? '',
4050
4266
  javaHome: config?.javaHome ?? undefined,
4051
- skipReason: getSkipReason(config?.disabled)
4267
+ skipReason: getSkipReason(ecosystem, config?.disabled),
4268
+ verbose: Boolean(config?.verbose)
4052
4269
  };
4053
4270
  }
4054
4271
 
@@ -6463,6 +6680,7 @@ async function convertGradleToFacts({
6463
6680
  excludeConfigs,
6464
6681
  excludePaths,
6465
6682
  gradleOpts,
6683
+ hints,
6466
6684
  ignoreUnresolved,
6467
6685
  includeConfigs,
6468
6686
  javaHome,
@@ -6470,13 +6688,14 @@ async function convertGradleToFacts({
6470
6688
  verbose,
6471
6689
  withFiles
6472
6690
  }) {
6473
- await runManifestFacts({
6691
+ return await runManifestFacts({
6474
6692
  bin,
6475
6693
  buildOpts: gradleOpts,
6476
6694
  cwd,
6477
6695
  ecosystem: 'gradle',
6478
6696
  excludeConfigs,
6479
6697
  excludePaths,
6698
+ hints,
6480
6699
  ignoreUnresolved,
6481
6700
  includeConfigs,
6482
6701
  javaHome,
@@ -6493,6 +6712,7 @@ async function convertMavenToFacts({
6493
6712
  cwd,
6494
6713
  excludeConfigs,
6495
6714
  excludePaths,
6715
+ hints,
6496
6716
  ignoreUnresolved,
6497
6717
  includeConfigs,
6498
6718
  javaHome,
@@ -6501,13 +6721,14 @@ async function convertMavenToFacts({
6501
6721
  verbose,
6502
6722
  withFiles
6503
6723
  }) {
6504
- await runManifestFacts({
6724
+ return await runManifestFacts({
6505
6725
  bin,
6506
6726
  buildOpts: mavenOpts,
6507
6727
  cwd,
6508
6728
  ecosystem: 'maven',
6509
6729
  excludeConfigs,
6510
6730
  excludePaths,
6731
+ hints,
6511
6732
  ignoreUnresolved,
6512
6733
  includeConfigs,
6513
6734
  javaHome,
@@ -6525,6 +6746,7 @@ async function convertSbtToFacts({
6525
6746
  cwd,
6526
6747
  excludeConfigs,
6527
6748
  excludePaths,
6749
+ hints,
6528
6750
  ignoreUnresolved,
6529
6751
  includeConfigs,
6530
6752
  javaHome,
@@ -6534,13 +6756,14 @@ async function convertSbtToFacts({
6534
6756
  verbose,
6535
6757
  withFiles
6536
6758
  }) {
6537
- await runManifestFacts({
6759
+ return await runManifestFacts({
6538
6760
  bin,
6539
6761
  buildOpts: sbtOpts,
6540
6762
  cwd,
6541
6763
  ecosystem: 'sbt',
6542
6764
  excludeConfigs,
6543
6765
  excludePaths,
6766
+ hints,
6544
6767
  ignoreUnresolved,
6545
6768
  includeConfigs,
6546
6769
  javaHome,
@@ -7004,12 +7227,20 @@ async function handleManifestConda({
7004
7227
  await outputRequirements(data, outputKind, out === '-' ? out : path.resolve(cwd, out));
7005
7228
  }
7006
7229
 
7007
- // Under --auto-manifest, a manifest generator that failed — raising the exit
7008
- // code above the value captured before it ran — aborts the whole run: a partial
7009
- // or empty SBOM silently under-reports dependencies. The generator has already
7010
- // logged the specifics. A tolerated resolution failure (ignoreUnresolved) warns
7011
- // without touching the exit code, so it passes through here and the run
7012
- // continues.
7230
+ // Auto-manifest reads every build option from the root socket.json.
7231
+ const AUTO_MANIFEST_HINTS = {
7232
+ flags: new Set(),
7233
+ setupCommand: 'socket manifest setup',
7234
+ socketJson: constants.SOCKET_JSON
7235
+ };
7236
+ // A failed generator aborts --auto-manifest: a partial SBOM silently
7237
+ // under-reports dependencies. Facts generators return null on failure; the
7238
+ // others only change the exit code.
7239
+ function abortFactsRunIfFailed(ecosystem, outcome) {
7240
+ if (outcome === null) {
7241
+ throw new utils.InputError(`Auto-manifest generation failed for the ${ecosystem} project; aborting (see the errors above).`);
7242
+ }
7243
+ }
7013
7244
  function abortManifestRunIfFailed(ecosystem, beforeExitCode) {
7014
7245
  if (process.exitCode && process.exitCode !== beforeExitCode) {
7015
7246
  throw new utils.InputError(`Auto-manifest generation failed for the ${ecosystem} project; aborting (see the errors above).`);
@@ -7026,6 +7257,10 @@ async function generateAutoManifest({
7026
7257
  }) {
7027
7258
  const sockJson = utils.readOrDefaultSocketJson(cwd);
7028
7259
  const generatedFiles = [];
7260
+ const hints = computeArtifactsSidecar ? {
7261
+ ...AUTO_MANIFEST_HINTS,
7262
+ continueOnInstallErrorsFlag: REACH_CONTINUE_ON_INSTALL_ERRORS_FLAG
7263
+ } : AUTO_MANIFEST_HINTS;
7029
7264
 
7030
7265
  // Resolved paths across all JVM roots, serialized to one sidecar at the end.
7031
7266
  const sidecarAcc = computeArtifactsSidecar ? new Map() : undefined;
@@ -7047,19 +7282,19 @@ async function generateAutoManifest({
7047
7282
  // `defaults.manifest.sbt.facts: false` in socket.json.
7048
7283
  if (sockJson.defaults?.manifest?.sbt?.facts !== false) {
7049
7284
  logger.logger.log('Detected a Scala sbt build, generating Socket facts...');
7050
- const beforeExitCode = process.exitCode;
7051
- await convertSbtToFacts({
7285
+ const outcome = await convertSbtToFacts({
7052
7286
  ...sbtArgs,
7053
7287
  excludeConfigs: sockJson.defaults?.manifest?.sbt?.excludeConfigs ?? '',
7054
7288
  excludePaths,
7055
7289
  ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.sbt?.ignoreUnresolved),
7056
7290
  includeConfigs: sockJson.defaults?.manifest?.sbt?.includeConfigs ?? '',
7057
7291
  javaHome: sockJson.defaults?.manifest?.sbt?.javaHome ?? undefined,
7292
+ hints,
7058
7293
  sidecarAcc,
7059
7294
  tmpDir,
7060
7295
  withFiles: computeArtifactsSidecar
7061
7296
  });
7062
- abortManifestRunIfFailed('sbt', beforeExitCode);
7297
+ abortFactsRunIfFailed('sbt', outcome);
7063
7298
  } else {
7064
7299
  logger.logger.log('Detected a Scala sbt build, generating pom files with sbt...');
7065
7300
  const beforeExitCode = process.exitCode;
@@ -7082,18 +7317,18 @@ async function generateAutoManifest({
7082
7317
  // `defaults.manifest.gradle.facts: false` in socket.json.
7083
7318
  if (sockJson.defaults?.manifest?.gradle?.facts !== false) {
7084
7319
  logger.logger.log('Detected a gradle build (Gradle, Kotlin, Scala), generating Socket facts...');
7085
- const beforeExitCode = process.exitCode;
7086
- await convertGradleToFacts({
7320
+ const outcome = await convertGradleToFacts({
7087
7321
  ...gradleArgs,
7088
7322
  excludeConfigs: sockJson.defaults?.manifest?.gradle?.excludeConfigs ?? '',
7089
7323
  excludePaths,
7090
7324
  ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.gradle?.ignoreUnresolved),
7091
7325
  includeConfigs: sockJson.defaults?.manifest?.gradle?.includeConfigs ?? '',
7092
7326
  javaHome: sockJson.defaults?.manifest?.gradle?.javaHome ?? undefined,
7327
+ hints,
7093
7328
  sidecarAcc,
7094
7329
  withFiles: computeArtifactsSidecar
7095
7330
  });
7096
- abortManifestRunIfFailed('gradle', beforeExitCode);
7331
+ abortFactsRunIfFailed('gradle', outcome);
7097
7332
  } else {
7098
7333
  logger.logger.log('Detected a gradle build (Gradle, Kotlin, Scala), running default gradle generator...');
7099
7334
  const beforeExitCode = process.exitCode;
@@ -7103,8 +7338,7 @@ async function generateAutoManifest({
7103
7338
  }
7104
7339
  if (!sockJson?.defaults?.manifest?.maven?.disabled && detected.maven) {
7105
7340
  logger.logger.log('Detected a Maven pom.xml build, generating Socket facts...');
7106
- const beforeExitCode = process.exitCode;
7107
- await convertMavenToFacts({
7341
+ const outcome = await convertMavenToFacts({
7108
7342
  // Configured bin wins; else prefer ./mvnw, else mvn on PATH.
7109
7343
  bin: sockJson.defaults?.manifest?.maven?.bin ?? resolveBuildToolBin('maven', cwd),
7110
7344
  cwd,
@@ -7114,11 +7348,12 @@ async function generateAutoManifest({
7114
7348
  includeConfigs: sockJson.defaults?.manifest?.maven?.includeConfigs ?? '',
7115
7349
  javaHome: sockJson.defaults?.manifest?.maven?.javaHome ?? undefined,
7116
7350
  mavenOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.maven?.mavenOpts ?? undefined),
7351
+ hints,
7117
7352
  sidecarAcc,
7118
7353
  verbose: Boolean(sockJson.defaults?.manifest?.maven?.verbose),
7119
7354
  withFiles: computeArtifactsSidecar
7120
7355
  });
7121
- abortManifestRunIfFailed('maven', beforeExitCode);
7356
+ abortFactsRunIfFailed('maven', outcome);
7122
7357
  }
7123
7358
  if (!sockJson?.defaults?.manifest?.conda?.disabled && detected.conda) {
7124
7359
  const condaFile = sockJson.defaults?.manifest?.conda?.infile || detected.condaFile || constants.ENVIRONMENT_YML;
@@ -7414,17 +7649,13 @@ async function createNewScan({
7414
7649
  logger.logger.success('Reachability analysis completed successfully');
7415
7650
  reachabilityReport = reachResult.data?.reachabilityReport;
7416
7651
 
7417
- // Mirror the SBOM inputs Coana analyzed; otherwise its fresh report
7418
- // (appended below) supersedes every facts file.
7419
- const pathsForScan = reach.reachUseOnlyPregeneratedSboms ? filterToPregeneratedSboms(packagePaths, supportedFiles).filter(p => !utils.isReachabilityReportPath(p, {
7652
+ // Upload Coana's inputs next to its report: depscan drops the report's
7653
+ // components without public metadata, such as private libraries.
7654
+ const pathsForScan = (reach.reachUseOnlyPregeneratedSboms ? filterToPregeneratedSboms(packagePaths, supportedFiles) : packagePaths).filter(p => !utils.isReachabilityReportPath(p, {
7420
7655
  cwd,
7421
7656
  outputPath: constants.default.DOT_SOCKET_DOT_FACTS_JSON
7422
- })) : packagePaths.filter(p => !utils.isSocketFactsFile(p));
7423
-
7424
- // Append coana's reachability report, but not twice: a pre-generated facts
7425
- // input can resolve to the same path coana wrote its report to.
7426
- const reportPath = reachabilityReport ? path.resolve(cwd, reachabilityReport) : undefined;
7427
- scanPaths = [...pathsForScan.filter(p => path.resolve(cwd, p) !== reportPath), ...(reachabilityReport ? [reachabilityReport] : [])];
7657
+ }));
7658
+ scanPaths = [...pathsForScan, ...(reachabilityReport ? [reachabilityReport] : [])];
7428
7659
  tier1ReachabilityScanId = reachResult.data?.tier1ReachabilityScanId;
7429
7660
  }
7430
7661
  }
@@ -9989,7 +10220,7 @@ const excludePathsFlag$1 = {
9989
10220
  excludePaths: {
9990
10221
  type: 'string',
9991
10222
  isMultiple: true,
9992
- description: 'List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (`--cwd` if set), not the reachability target: `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.'
10223
+ description: 'List of glob patterns to exclude from the scan, including SCA/SBOM manifest discovery and (when --reach is enabled) full application reachability analysis. Patterns are anchored micromatch globs matched relative to the Socket scan root, which is the command working directory (`--cwd` if set), not the reachability target: `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags. An excluded JVM build module is not analysed as a project of its own, but where a project that is not excluded depends on it, its code is analysed as part of that project.'
9993
10224
  }
9994
10225
  };
9995
10226
 
@@ -12634,7 +12865,7 @@ const excludePathsFlag = {
12634
12865
  excludePaths: {
12635
12866
  type: 'string',
12636
12867
  isMultiple: true,
12637
- description: 'List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (`--cwd` if set): `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags.'
12868
+ description: 'List of glob patterns to exclude from manifest/facts generation. Patterns are anchored micromatch globs matched relative to CWD (`--cwd` if set): `tests` matches only `<cwd>/tests`; use `**/tests` to match at any depth. Negation patterns (`!path`) are not supported. Accepts a comma-separated value or multiple flags. An excluded JVM build module is not analysed as a project of its own, but where a project that is not excluded depends on it, its code is analysed as part of that project.'
12638
12869
  }
12639
12870
  };
12640
12871
 
@@ -12935,12 +13166,15 @@ async function outputManifestDynamicSbomInference(result, outputKind) {
12935
13166
  async function handleManifestDynamicSbomInference({
12936
13167
  cwd,
12937
13168
  excludePaths,
13169
+ ignoreUnresolved,
12938
13170
  outputKind,
12939
13171
  verbose
12940
13172
  }) {
12941
13173
  const outcomes = await generateRecursiveManifests({
12942
13174
  cwd,
12943
13175
  excludePaths,
13176
+ ignoreUnresolved,
13177
+ optionFlags: new Set(['ignoreUnresolved', 'verbose']),
12944
13178
  verbose
12945
13179
  });
12946
13180
  const result = !outcomes.length ? {
@@ -12968,6 +13202,10 @@ const config$d = {
12968
13202
  ...flags.commonFlags,
12969
13203
  ...flags.outputFlags,
12970
13204
  ...excludePathsFlag,
13205
+ ignoreUnresolved: {
13206
+ type: 'boolean',
13207
+ description: "Warn on unresolved dependencies instead of failing the run (unresolved deps are not emitted to the facts files); overrides each build's socket.json setting"
13208
+ },
12971
13209
  verbose: {
12972
13210
  type: 'boolean',
12973
13211
  default: false,
@@ -13012,6 +13250,7 @@ async function run$E(argv, importMeta, {
13012
13250
  });
13013
13251
  const {
13014
13252
  dryRun,
13253
+ ignoreUnresolved,
13015
13254
  json,
13016
13255
  markdown,
13017
13256
  verbose: verboseFlag
@@ -13055,6 +13294,7 @@ async function run$E(argv, importMeta, {
13055
13294
  await handleManifestDynamicSbomInference({
13056
13295
  cwd,
13057
13296
  excludePaths,
13297
+ ignoreUnresolved,
13058
13298
  outputKind,
13059
13299
  verbose
13060
13300
  });
@@ -14731,7 +14971,7 @@ async function enumerateWorkspaces({
14731
14971
  result = await enumerateWorkspaces$1(ecosystem, scriptOpts);
14732
14972
  } catch (e) {
14733
14973
  process.exitCode = 1;
14734
- logger.logger.fail(`Could not run the ${ecosystem} build tool` + (' (run with --verbose for details).'));
14974
+ logger.logger.fail(`Could not run the ${ecosystem} build tool: ${utils.getErrorMessageOr(e, String(e))}`);
14735
14975
  return;
14736
14976
  }
14737
14977
  if (!result.projects.length) {
@@ -19871,6 +20111,16 @@ async function validateReachabilityTarget(targets, cwd) {
19871
20111
  return result;
19872
20112
  }
19873
20113
 
20114
+ // Build options that `socket manifest <tool>` takes as flags but the scan
20115
+ // commands read from each build's socket.json, so passing them does nothing.
20116
+ const SOCKET_JSON_ONLY_FLAGS = ['excludeConfigs', 'ignoreUnresolved', 'includeConfigs', 'verbose'];
20117
+ function warnSocketJsonOnlyFlags(flags, command) {
20118
+ const ignored = SOCKET_JSON_ONLY_FLAGS.filter(name => flags[name] !== undefined).map(name => `--${utils.camelToKebab(name)}`);
20119
+ if (ignored.length) {
20120
+ logger.logger.warn(`Ignoring ${ignored.join(', ')}: \`${command}\` reads build options from each build's socket.json. Set them there, for example with \`${DSI_SETUP_COMMAND}\`.`);
20121
+ }
20122
+ }
20123
+
19874
20124
  const CMD_NAME$a = 'create';
19875
20125
 
19876
20126
  // Accepted for backwards compatibility but forwarded nowhere, so passing one
@@ -20110,6 +20360,7 @@ async function run$d(argv, importMeta, {
20110
20360
 
20111
20361
  // Validate ecosystem values against the reachability-supported set.
20112
20362
  const reachEcosystems = utils.parseReachEcosystems(utils.cmdFlagValueToArray(cli.flags['reachEcosystems']));
20363
+ warnSocketJsonOnlyFlags(cli.flags, `${parentName} ${CMD_NAME$a}`);
20113
20364
  const dryRun = !!cli.flags['dryRun'];
20114
20365
  const uvMembers = !!cli.flags['uvMembers'];
20115
20366
  let {
@@ -22463,6 +22714,7 @@ async function run$7(argv, importMeta, {
22463
22714
  importMeta,
22464
22715
  parentName
22465
22716
  });
22717
+ warnSocketJsonOnlyFlags(cli.flags, `${parentName} ${CMD_NAME$4}`);
22466
22718
  const {
22467
22719
  cwd: cwdOverride,
22468
22720
  dynamicSbomInference,
@@ -24539,5 +24791,5 @@ process.on('unhandledRejection', async (reason, promise) => {
24539
24791
  // eslint-disable-next-line n/no-process-exit
24540
24792
  process.exit(1);
24541
24793
  });
24542
- //# debugId=8345ea37-0e60-45d9-8a5b-618ab51856c6
24794
+ //# debugId=3195b2a9-2bce-44ed-8eea-bd5e763d40fa
24543
24795
  //# sourceMappingURL=cli.js.map