@socketsecurity/cli-with-sentry 1.1.131 → 1.1.132

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 (55) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cli.js +1227 -144
  3. package/dist/cli.js.map +1 -1
  4. package/dist/constants.js +4 -4
  5. package/dist/constants.js.map +1 -1
  6. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  7. package/dist/manifest-scripts/socket-facts.init.gradle +479 -0
  8. package/dist/manifest-scripts/socket-facts.plugin.scala +444 -0
  9. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  10. package/dist/types/commands/manifest/cmd-manifest-gradle.d.mts.map +1 -1
  11. package/dist/types/commands/manifest/cmd-manifest-kotlin.d.mts.map +1 -1
  12. package/dist/types/commands/manifest/cmd-manifest-maven.d.mts.map +1 -1
  13. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +5 -6
  14. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -1
  15. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +5 -6
  16. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -1
  17. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +7 -8
  18. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
  19. package/dist/types/commands/manifest/generate_auto_manifest.d.mts +8 -1
  20. package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
  21. package/dist/types/commands/manifest/run-manifest-facts.d.mts +20 -0
  22. package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -0
  23. package/dist/types/commands/manifest/scripts/assemble.d.mts +16 -0
  24. package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -0
  25. package/dist/types/commands/manifest/scripts/build-tool.d.mts +3 -0
  26. package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -0
  27. package/dist/types/commands/manifest/scripts/facts.d.mts +46 -0
  28. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -0
  29. package/dist/types/commands/manifest/scripts/records.d.mts +64 -0
  30. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -0
  31. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +10 -0
  32. package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -0
  33. package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts +7 -0
  34. package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts.map +1 -0
  35. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +8 -0
  36. package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -0
  37. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +41 -0
  38. package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -0
  39. package/dist/types/commands/manifest/scripts/resolution-report.d.mts +12 -0
  40. package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -0
  41. package/dist/types/commands/manifest/scripts/run.d.mts +28 -0
  42. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -0
  43. package/dist/types/commands/manifest/scripts/sidecar.d.mts +27 -0
  44. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -0
  45. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  46. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  47. package/dist/types/commands/scan/perform-reachability-analysis.d.mts +4 -2
  48. package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
  49. package/dist/utils.js +1 -78
  50. package/dist/utils.js.map +1 -1
  51. package/package.json +3 -2
  52. package/dist/types/commands/manifest/coana-manifest-facts.d.mts +0 -27
  53. package/dist/types/commands/manifest/coana-manifest-facts.d.mts.map +0 -1
  54. package/dist/types/utils/auto-manifest-config.d.mts +0 -55
  55. package/dist/types/utils/auto-manifest-config.d.mts.map +0 -1
package/dist/cli.js CHANGED
@@ -1752,6 +1752,7 @@ async function performReachabilityAnalysis(options) {
1752
1752
  packagePaths,
1753
1753
  reachabilityOptions,
1754
1754
  repoName,
1755
+ resolvedPathsSidecar,
1755
1756
  spinner,
1756
1757
  target,
1757
1758
  uploadManifests = true
@@ -1840,25 +1841,18 @@ async function performReachabilityAnalysis(options) {
1840
1841
  spinner?.infoAndStop('Running reachability analysis with Coana...');
1841
1842
  const outputFilePath = outputPath || constants.default.DOT_SOCKET_DOT_FACTS_JSON;
1842
1843
 
1843
- // Coana reads `--auto-manifest-config` from a JSON file, so write the resolved
1844
- // per-ecosystem build-tool config (mapped from socket.json) to a temp file and
1845
- // pass its absolute path. Cleaned up in the finally below.
1846
- let autoManifestConfigPath;
1847
- const {
1848
- autoManifestConfig
1849
- } = reachabilityOptions;
1850
- if (autoManifestConfig && !utils.isAutoManifestConfigEmpty(autoManifestConfig)) {
1851
- autoManifestConfigPath = path.join(os.tmpdir(), `socket-auto-manifest-config-${require$$0.randomUUID()}.json`);
1852
- await fs$1.promises.writeFile(autoManifestConfigPath, JSON.stringify(autoManifestConfig), 'utf8');
1844
+ // Write the sidecar to a temp file for `--compute-artifacts-sidecar`; cleaned
1845
+ // up in the finally below.
1846
+ let sidecarPath;
1847
+ if (resolvedPathsSidecar?.length) {
1848
+ sidecarPath = path.join(os.tmpdir(), `socket-compute-artifacts-sidecar-${require$$0.randomUUID()}.json`);
1849
+ await fs$1.promises.writeFile(sidecarPath, JSON.stringify(resolvedPathsSidecar), 'utf8');
1853
1850
  }
1854
1851
 
1855
1852
  // Build Coana arguments.
1856
1853
  const coanaArgs = ['run', analysisTarget, '--output-dir', path.dirname(outputFilePath), '--socket-mode', outputFilePath, '--disable-report-submission', ...(isOmittedReachValue(reachabilityOptions.reachAnalysisTimeout) ? [] : ['--analysis-timeout', reachabilityOptions.reachAnalysisTimeout]), ...(isOmittedReachValue(reachabilityOptions.reachAnalysisMemoryLimit) ? [] : ['--memory-limit', reachabilityOptions.reachAnalysisMemoryLimit]), ...(reachabilityOptions.reachConcurrency ? ['--concurrency', `${reachabilityOptions.reachConcurrency}`] : []), ...(reachabilityOptions.reachContinueOnAnalysisErrors ? ['--reach-continue-on-analysis-errors'] : []), ...(reachabilityOptions.reachContinueOnInstallErrors ? ['--reach-continue-on-install-errors'] : []), ...(reachabilityOptions.reachContinueOnMissingLockFiles ? ['--reach-continue-on-missing-lock-files'] : []), ...(reachabilityOptions.reachContinueOnNoSourceFiles ? ['--reach-continue-on-no-source-files'] : []), ...(reachabilityOptions.reachDebug ? ['--debug'] : []), ...(reachabilityOptions.reachDetailedAnalysisLogFile ? ['--print-analysis-log-file'] : []), ...(reachabilityOptions.reachDisableAnalytics ? ['--disable-analytics-sharing'] : []), ...(reachabilityOptions.reachDisableExternalToolChecks ? ['--disable-external-tool-checks'] : []), ...(reachabilityOptions.reachEnableAnalysisSplitting ? [] : ['--disable-analysis-splitting']), ...(tarHash ? ['--run-without-docker', '--manifests-tar-hash', tarHash] : []),
1857
1854
  // Empty reachEcosystems implies scanning all ecosystems.
1858
- ...(reachabilityOptions.reachEcosystems.length ? ['--purl-types', ...reachabilityOptions.reachEcosystems] : []), ...(reachabilityOptions.reachExcludePaths.length ? ['--exclude-dirs', ...reachabilityOptions.reachExcludePaths] : []), ...(reachabilityOptions.reachLazyMode ? ['--lazy-mode'] : []), ...(reachabilityOptions.reachSkipCache ? ['--skip-cache-usage'] : []), ...(reachabilityOptions.reachUseOnlyPregeneratedSboms ? ['--use-only-pregenerated-sboms'] : []),
1859
- // Hand the per-ecosystem build-tool config (mapped from socket.json) to
1860
- // Coana's reach-time resolution, as a temp JSON file path.
1861
- ...(autoManifestConfigPath ? ['--auto-manifest-config', autoManifestConfigPath] : [])];
1855
+ ...(reachabilityOptions.reachEcosystems.length ? ['--purl-types', ...reachabilityOptions.reachEcosystems] : []), ...(reachabilityOptions.reachExcludePaths.length ? ['--exclude-dirs', ...reachabilityOptions.reachExcludePaths] : []), ...(reachabilityOptions.reachLazyMode ? ['--lazy-mode'] : []), ...(reachabilityOptions.reachSkipCache ? ['--skip-cache-usage'] : []), ...(reachabilityOptions.reachUseOnlyPregeneratedSboms ? ['--use-only-pregenerated-sboms'] : []), ...(sidecarPath ? ['--compute-artifacts-sidecar', sidecarPath] : [])];
1862
1856
 
1863
1857
  // Build environment variables.
1864
1858
  const coanaEnv = {};
@@ -1915,10 +1909,10 @@ async function performReachabilityAnalysis(options) {
1915
1909
  }
1916
1910
  };
1917
1911
  } finally {
1918
- // The run no longer needs the temp config file; best-effort cleanup.
1919
- if (autoManifestConfigPath) {
1912
+ // Best-effort cleanup of the temp sidecar.
1913
+ if (sidecarPath) {
1920
1914
  try {
1921
- await fs$1.promises.unlink(autoManifestConfigPath);
1915
+ await fs$1.promises.unlink(sidecarPath);
1922
1916
  } catch {
1923
1917
  // File may already be gone or unwritable.
1924
1918
  }
@@ -4282,102 +4276,1175 @@ async function extractBazelToMaven(opts) {
4282
4276
  }
4283
4277
  }
4284
4278
 
4285
- // Delegates Socket facts generation for a JVM build tool to the Coana CLI's
4286
- // `manifest <ecosystem>` command. The build-tool resolution scripts (the Gradle
4287
- // init script and the sbt plugin) live in Coana now, so socket-cli no longer
4288
- // runs them itself; it only asks Coana for the uploadable `.socket.facts.json`.
4279
+ // Gradle's variant-aware resolver: distinct exceptions give mutually-exclusive
4280
+ // phrasing, so most-specific-first substring checks classify reliably.
4281
+ function classifyGradleFailure(detail) {
4282
+ const t = (detail || '').toLowerCase();
4283
+ // Check before variant ambiguity.
4284
+ if (t.includes('conflict on capability')) {
4285
+ return 'capability-conflict';
4286
+ }
4287
+ // Zero compatible variants — the opposite of ambiguity below.
4288
+ if (t.includes('no matching variant') || t.includes('no variants of')) {
4289
+ return 'no-matching-variant';
4290
+ }
4291
+ if (t.includes('cannot choose between')) {
4292
+ return 'variant-ambiguity';
4293
+ }
4294
+ 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')) {
4295
+ return 'repository-or-network';
4296
+ }
4297
+ if (t.includes('could not find')) {
4298
+ return 'not-found';
4299
+ }
4300
+ return 'other';
4301
+ }
4302
+
4303
+ // Every kind blocks except variant-ambiguity: the module demonstrably exists
4304
+ // and is almost always captured via another configuration, so it's benign for
4305
+ // the SBOM (non-blocking, count only). A module that resolves in NO config
4306
+ // surfaces as not-found / no-matching-variant, which stay blocking.
4307
+ const GRADLE_DIALECT = {
4308
+ label: 'Gradle',
4309
+ classify: classifyGradleFailure,
4310
+ categories: [{
4311
+ key: 'not-found',
4312
+ header: () => ` Not found in any repository:`,
4313
+ blocking: true
4314
+ }, {
4315
+ key: 'no-matching-variant',
4316
+ header: n => ` No compatible variant — ${n} found the module but no variant matched the requested attributes:`,
4317
+ blocking: true
4318
+ }, {
4319
+ key: 'capability-conflict',
4320
+ header: n => ` Capability conflict — ${n} found two modules providing the same capability and cannot use both (add a capability-resolution or module-replacement rule):`,
4321
+ showReason: true,
4322
+ blocking: true
4323
+ }, {
4324
+ key: 'repository-or-network',
4325
+ header: n => ` Repository or network error — ${n} could not reach or authenticate to a repository:`,
4326
+ blocking: true
4327
+ }, {
4328
+ key: 'config-problem',
4329
+ header: n => ` Resolver/configuration problem (reason from ${n}):`,
4330
+ showReason: true,
4331
+ blocking: true
4332
+ }, {
4333
+ key: 'other',
4334
+ header: n => ` Other resolution failures (reason from ${n}):`,
4335
+ showReason: true,
4336
+ blocking: true
4337
+ }, {
4338
+ key: 'variant-ambiguity',
4339
+ blocking: false,
4340
+ notice: (n, depCount, configCount) => `Skipped ${depCount} dependency(ies) with ambiguous variant selection in ${configCount} configuration(s) — re-run with --verbose for ${n}'s messages.`
4341
+ }]
4342
+ };
4343
+
4344
+ // Ivy/sbt resolver: no attribute-based variants, so no variant categories
4345
+ // (Gradle-only). Ivy degrades transport failures to "not found".
4346
+ function classifyIvyFailure(detail) {
4347
+ const t = (detail || '').toLowerCase();
4348
+ if (t.includes('server access error') || t.includes('download failed') || t.includes('connection timed out') || t.includes('unauthorized') || t.includes('forbidden')) {
4349
+ return 'repository-or-network';
4350
+ }
4351
+ if (t.includes('no resolver found') || t.includes('configuration not found') || t.includes('configuration not public')) {
4352
+ return 'config-problem';
4353
+ }
4354
+ if (t.includes('not found') || t.includes('unresolved dependency')) {
4355
+ return 'not-found';
4356
+ }
4357
+ return 'other';
4358
+ }
4359
+
4360
+ // Every kind blocks; no non-blocking kind because Ivy has no variant ambiguity.
4361
+ const SBT_DIALECT = {
4362
+ label: 'sbt',
4363
+ classify: classifyIvyFailure,
4364
+ categories: [{
4365
+ key: 'not-found',
4366
+ header: () => ` Not found in any repository:`,
4367
+ blocking: true
4368
+ }, {
4369
+ key: 'repository-or-network',
4370
+ header: n => ` Repository or network error — ${n} could not reach or authenticate to a repository:`,
4371
+ blocking: true
4372
+ }, {
4373
+ key: 'config-problem',
4374
+ header: n => ` Resolver/configuration problem (reason from ${n}):`,
4375
+ showReason: true,
4376
+ blocking: true
4377
+ }, {
4378
+ key: 'other',
4379
+ header: n => ` Other resolution failures (reason from ${n}):`,
4380
+ showReason: true,
4381
+ blocking: true
4382
+ }]
4383
+ };
4384
+
4385
+ // Maven's resolver (Aether/maven-resolver): no attribute-based variants. Two
4386
+ // failure shapes (artifact-resolution miss with config = scope, dependency-graph
4387
+ // build failure with config = "graph") both classify off the root-cause message.
4388
+ function classifyMavenFailure(detail) {
4389
+ const t = (detail || '').toLowerCase();
4390
+ 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')) {
4391
+ return 'repository-or-network';
4392
+ }
4393
+ 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')) {
4394
+ return 'not-found';
4395
+ }
4396
+ // POM exists but can't be read/parsed.
4397
+ if (t.includes('failed to read artifact descriptor') || t.includes('invalid pom') || t.includes('could not parse pom')) {
4398
+ return 'config-problem';
4399
+ }
4400
+ return 'other';
4401
+ }
4402
+
4403
+ // Every kind blocks; no non-blocking kind because Maven has no variant ambiguity.
4404
+ const MAVEN_DIALECT = {
4405
+ label: 'Maven',
4406
+ classify: classifyMavenFailure,
4407
+ categories: [{
4408
+ key: 'not-found',
4409
+ header: () => ` Not found in any repository:`,
4410
+ blocking: true
4411
+ }, {
4412
+ key: 'repository-or-network',
4413
+ header: n => ` Repository or network error — ${n} could not reach or authenticate to a repository:`,
4414
+ blocking: true
4415
+ }, {
4416
+ key: 'config-problem',
4417
+ header: n => ` POM/descriptor problem (reason from ${n}):`,
4418
+ showReason: true,
4419
+ blocking: true
4420
+ }, {
4421
+ key: 'other',
4422
+ header: n => ` Other resolution failures (reason from ${n}):`,
4423
+ showReason: true,
4424
+ blocking: true
4425
+ }]
4426
+ };
4427
+
4428
+ // Recognized from the build tool's message; drives wording AND whether the kind
4429
+ // is blocking. An unrecognized message degrades to 'other' (blocking) — safe.
4430
+
4431
+ // Per-resolver classify + render/score policy.
4432
+
4433
+ const RESOLUTION_REPORT_ARTIFACT_LIMIT = 15;
4434
+ const RESOLUTION_REPORT_CONFIG_LIMIT = 20;
4435
+
4436
+ // Drop a bare "group:name" when a versioned "group:name:v" of the same module
4437
+ // is also present: the lenient resolver reports both forms for one failure.
4438
+ function dedupCoords(coords) {
4439
+ const set = new Set(coords);
4440
+ const versioned = new Set();
4441
+ for (const c of set) {
4442
+ const p = c.split(':');
4443
+ if (p.length >= 3) {
4444
+ versioned.add(`${p[0]}:${p[1]}`);
4445
+ }
4446
+ }
4447
+ return [...set].filter(c => c.split(':').length >= 3 || !versioned.has(c)).sort();
4448
+ }
4449
+ function fmtList(list, limit) {
4450
+ const shown = list.slice(0, limit).join(', ');
4451
+ return list.length > limit ? `${shown} (+${list.length - limit} more)` : shown;
4452
+ }
4453
+ function firstLine(s) {
4454
+ return (s || '').split('\n').map(l => l.trim()).find(Boolean) ?? '';
4455
+ }
4456
+
4457
+ // Severity is per-kind; the exit-code decision lives in the caller. We do NOT
4458
+ // cross-reference what resolved elsewhere: the failed selector carries no
4459
+ // classifier/type, so relating a failed and a succeeded dep is unsound.
4460
+ function renderResolutionReport(failures, scannedConfigs, dialect, opts = {}) {
4461
+ const name = dialect.label;
4462
+ const specOf = new Map(dialect.categories.map(c => [c.key, c]));
4463
+ const isBlocking = cat => specOf.get(cat)?.blocking ?? true;
4464
+
4465
+ // Aggregate by (coord, category): one module can fail with different causes
4466
+ // across configs. Keep first-seen detail, union the configs.
4467
+
4468
+ const byKey = new Map();
4469
+ const keyOf = (coord, category) => `${coord} ${category}`;
4470
+ for (const f of failures) {
4471
+ const category = dialect.classify(f.detail);
4472
+ const key = keyOf(f.coord, category);
4473
+ let info = byKey.get(key);
4474
+ if (!info) {
4475
+ info = {
4476
+ coord: f.coord,
4477
+ category,
4478
+ detail: f.detail,
4479
+ configs: new Set()
4480
+ };
4481
+ byKey.set(key, info);
4482
+ }
4483
+ if (f.config) {
4484
+ info.configs.add(f.config);
4485
+ }
4486
+ }
4487
+ const allInfos = [...byKey.values()];
4488
+ const blockingConfigs = new Set();
4489
+ for (const info of allInfos) {
4490
+ if (isBlocking(info.category)) {
4491
+ for (const c of info.configs) {
4492
+ blockingConfigs.add(c);
4493
+ }
4494
+ }
4495
+ }
4496
+ const blockingFailed = [...blockingConfigs].sort();
4497
+ const succeeded = scannedConfigs.filter(c => !blockingConfigs.has(c)).sort();
4498
+ const groups = dialect.categories.map(spec => ({
4499
+ spec,
4500
+ infos: dedupCoords(allInfos.filter(i => i.category === spec.key).map(i => i.coord)).map(c => byKey.get(keyOf(c, spec.key)))
4501
+ })).filter(g => g.infos.length);
4502
+ const blockingGroups = groups.filter(g => g.spec.blocking);
4503
+ const nonBlockingGroups = groups.filter(g => !g.spec.blocking);
4504
+ const blockingCount = blockingGroups.reduce((n, g) => n + g.infos.length, 0);
4505
+ const hasBlockingFailures = blockingCount > 0;
4506
+ const willFail = hasBlockingFailures && !opts.ignoreUnresolved;
4507
+ const out = [];
4508
+ if (hasBlockingFailures) {
4509
+ out.push(opts.ignoreUnresolved ? `Ignored ${blockingCount} unresolved dependency(ies) in ${blockingFailed.length} configuration(s):` : `Could not resolve ${blockingCount} dependency(ies) in ${blockingFailed.length} configuration(s):`);
4510
+ for (const {
4511
+ infos,
4512
+ spec
4513
+ } of blockingGroups) {
4514
+ out.push('');
4515
+ out.push(spec.header ? spec.header(name) : '');
4516
+ for (const info of infos.slice(0, RESOLUTION_REPORT_ARTIFACT_LIMIT)) {
4517
+ const fl = firstLine(info.detail);
4518
+ const reasonSuffix = spec.showReason && fl ? ` [${fl}]` : '';
4519
+ out.push(` - ${info.coord}${reasonSuffix}`);
4520
+ }
4521
+ if (infos.length > RESOLUTION_REPORT_ARTIFACT_LIMIT) {
4522
+ out.push(` … and ${infos.length - RESOLUTION_REPORT_ARTIFACT_LIMIT} more`);
4523
+ }
4524
+ }
4525
+ out.push('');
4526
+ if (succeeded.length) {
4527
+ out.push(`Resolution succeeded in: ${fmtList(succeeded, RESOLUTION_REPORT_CONFIG_LIMIT)}`);
4528
+ }
4529
+ if (blockingFailed.length) {
4530
+ out.push(`Resolution failed in: ${fmtList(blockingFailed, RESOLUTION_REPORT_CONFIG_LIMIT)}`);
4531
+ }
4532
+ if (willFail) {
4533
+ out.push('');
4534
+ out.push(`To proceed, re-run with either:`);
4535
+ out.push(` --ignore-unresolved`);
4536
+ if (blockingFailed.length) {
4537
+ out.push(` --exclude-configs '${blockingFailed.join(',')}'`);
4538
+ }
4539
+ }
4540
+ out.push('');
4541
+ out.push(`Re-run with --verbose for ${name}'s full messages.`);
4542
+ }
4543
+ const notices = [];
4544
+ for (const {
4545
+ infos,
4546
+ spec
4547
+ } of nonBlockingGroups) {
4548
+ if (!spec.notice) {
4549
+ continue;
4550
+ }
4551
+ const configCount = new Set(infos.flatMap(i => [...i.configs])).size;
4552
+ notices.push(spec.notice(name, infos.length, configCount));
4553
+ }
4554
+ const detailLines = [`${name}'s full message for each unresolved dependency:`];
4555
+ for (const info of allInfos) {
4556
+ detailLines.push('');
4557
+ detailLines.push(` ${info.coord}:`);
4558
+ for (const line of (info.detail || '(no message)').split('\n')) {
4559
+ detailLines.push(` ${line}`);
4560
+ }
4561
+ }
4562
+ return {
4563
+ summary: out.join('\n'),
4564
+ details: detailLines.join('\n'),
4565
+ hasBlockingFailures,
4566
+ nonBlockingNotice: notices.join('\n')
4567
+ };
4568
+ }
4569
+ function dialectFor(tool) {
4570
+ switch (tool) {
4571
+ case 'gradle':
4572
+ return GRADLE_DIALECT;
4573
+ case 'sbt':
4574
+ return SBT_DIALECT;
4575
+ case 'maven':
4576
+ return MAVEN_DIALECT;
4577
+ }
4578
+ }
4579
+ function renderResolutionErrorReport(failures, scannedConfigs = [], tool = 'gradle', opts = {}) {
4580
+ return renderResolutionReport(failures, scannedConfigs, dialectFor(tool), opts);
4581
+ }
4582
+
4583
+ // No sources/targets here: those are local absolute paths, returned in-memory
4584
+ // as ResolvedArtifactPaths, never serialized into the SBOM.
4585
+
4586
+ // Resolved on-disk paths for a --with-files run, keyed by coordinate. `targets`
4587
+ // = classpath entries (jars / module output dirs); `sources` = module source
4588
+ // roots.
4589
+
4590
+ // Coordinate-based (not `id`-based) so it also matches foreign SBOMs like
4591
+ // CycloneDX. Empty segments dropped.
4592
+ function mavenCoordinateKey(groupId, artifactId, type, classifier, version) {
4593
+ return [groupId, artifactId, type, classifier, version].filter(Boolean).join(':');
4594
+ }
4595
+
4596
+ const PURL_TYPE_MAVEN = 'maven';
4597
+ function assembleFacts(parsed, opts = {}) {
4598
+ const fileExists = opts.fileExists ?? fs$1.existsSync;
4599
+ const perRoot = buildPerRoot(parsed);
4600
+ const {
4601
+ directByRoot,
4602
+ finalNodes
4603
+ } = mergePathSensitive(perRoot);
4604
+ const tool = parsed.tool || 'gradle';
4605
+ const components = buildComponents(finalNodes);
4606
+ const projects = opts.emitProjects === false ? [] : buildProjects(parsed, finalNodes, directByRoot, perRoot);
4607
+ const metadata = {
4608
+ format: 'socket-facts-sbom',
4609
+ tool,
4610
+ toolVersion: parsed.toolVersion,
4611
+ ...(parsed.javaVersion ? {
4612
+ javaVersion: parsed.javaVersion
4613
+ } : {})
4614
+ };
4615
+ const facts = projects.length ? {
4616
+ metadata,
4617
+ projects,
4618
+ components
4619
+ } : {
4620
+ metadata,
4621
+ components
4622
+ };
4623
+ return {
4624
+ facts,
4625
+ report: buildReport(parsed),
4626
+ artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], fileExists)
4627
+ };
4628
+ }
4629
+ function gav(group, name, version) {
4630
+ return `${group}:${name}:${version}`;
4631
+ }
4632
+ function shortHash(s) {
4633
+ return require$$0.createHash('sha256').update(s, 'utf8').digest('hex').slice(0, 12);
4634
+ }
4635
+ function buildPerRoot(parsed) {
4636
+ const out = new Map();
4637
+ for (const [rootId, r] of parsed.roots) {
4638
+ const childrenByParent = new Map();
4639
+ for (const [p, c] of r.edges) {
4640
+ if (!r.nodes.has(p) || !r.nodes.has(c)) {
4641
+ continue;
4642
+ }
4643
+ let set = childrenByParent.get(p);
4644
+ if (!set) {
4645
+ set = new Set();
4646
+ childrenByParent.set(p, set);
4647
+ }
4648
+ set.add(c);
4649
+ }
4650
+ const nodes = new Map();
4651
+ for (const [coordId, n] of r.nodes) {
4652
+ nodes.set(coordId, {
4653
+ coord: n.coord,
4654
+ children: [...(childrenByParent.get(coordId) ?? [])].sort(),
4655
+ direct: n.direct,
4656
+ targets: n.targets
4657
+ });
4658
+ }
4659
+ out.set(rootId, {
4660
+ projectKey: r.projectKey,
4661
+ prod: r.prod,
4662
+ nodes
4663
+ });
4664
+ }
4665
+ return out;
4666
+ }
4667
+
4668
+ // A coordinate with identical subtrees everywhere collapses to one node (id =
4669
+ // coordId); divergent subtrees each get a content-addressed id
4670
+ // (`<coordId>#<subtree-hash>`) so per-subproject overrides stay distinct.
4671
+ function mergePathSensitive(perRoot) {
4672
+ const memo = new Map();
4673
+ const nodesOf = rootId => perRoot.get(rootId)?.nodes;
4674
+ function computeSig(rootId, coordId, onPath) {
4675
+ const memoKey = rootId + ' ' + coordId;
4676
+ const cached = memo.get(memoKey);
4677
+ if (cached !== undefined) {
4678
+ return cached;
4679
+ }
4680
+ if (onPath.has(coordId)) {
4681
+ // Cycle: back-edge as leaf.
4682
+ return coordId;
4683
+ }
4684
+ const node = nodesOf(rootId)?.get(coordId);
4685
+ if (!node) {
4686
+ return coordId;
4687
+ }
4688
+ onPath.add(coordId);
4689
+ const childSigs = node.children.map(c => computeSig(rootId, c, onPath));
4690
+ onPath.delete(coordId);
4691
+ // Digest, not the raw string: caching expanded subtree strings OOMs on
4692
+ // reconverging DAGs; a fixed-size digest keeps the pass O(V+E).
4693
+ const sig = coordId + '{' + childSigs.join(',') + '}';
4694
+ const digest = require$$0.createHash('sha256').update(sig, 'utf8').digest('hex').slice(0, 16);
4695
+ memo.set(memoKey, digest);
4696
+ return digest;
4697
+ }
4698
+
4699
+ // Sorted iteration keeps cyclic-graph signatures stable run-to-run.
4700
+ const sigsByCoord = new Map();
4701
+ for (const rootId of [...perRoot.keys()].sort()) {
4702
+ const nodes = perRoot.get(rootId).nodes;
4703
+ for (const coordId of [...nodes.keys()].sort()) {
4704
+ const sig = computeSig(rootId, coordId, new Set());
4705
+ let set = sigsByCoord.get(coordId);
4706
+ if (!set) {
4707
+ set = new Set();
4708
+ sigsByCoord.set(coordId, set);
4709
+ }
4710
+ set.add(sig);
4711
+ }
4712
+ }
4713
+ const divergent = coordId => (sigsByCoord.get(coordId)?.size ?? 0) > 1;
4714
+ const emittedIdMemo = new Map();
4715
+ const emittedIdFor = (rootId, coordId) => {
4716
+ const k = rootId + ' ' + coordId;
4717
+ let v = emittedIdMemo.get(k);
4718
+ if (v === undefined) {
4719
+ v = divergent(coordId) ? coordId + '#' + shortHash(computeSig(rootId, coordId, new Set())) : coordId;
4720
+ emittedIdMemo.set(k, v);
4721
+ }
4722
+ return v;
4723
+ };
4724
+ const finalNodes = new Map();
4725
+ const directByRoot = new Map();
4726
+ for (const [rootId, {
4727
+ nodes,
4728
+ prod
4729
+ }] of perRoot) {
4730
+ for (const [coordId, node] of nodes) {
4731
+ const eid = emittedIdFor(rootId, coordId);
4732
+ let fn = finalNodes.get(eid);
4733
+ if (!fn) {
4734
+ fn = {
4735
+ coord: node.coord,
4736
+ children: new Set(),
4737
+ prod: false,
4738
+ direct: false,
4739
+ targets: new Set()
4740
+ };
4741
+ finalNodes.set(eid, fn);
4742
+ }
4743
+ if (prod) {
4744
+ fn.prod = true;
4745
+ }
4746
+ if (node.direct) {
4747
+ fn.direct = true;
4748
+ }
4749
+ for (const c of node.children) {
4750
+ fn.children.add(emittedIdFor(rootId, c));
4751
+ }
4752
+ for (const t of node.targets) {
4753
+ fn.targets.add(t);
4754
+ }
4755
+ if (node.direct) {
4756
+ let d = directByRoot.get(rootId);
4757
+ if (!d) {
4758
+ d = new Set();
4759
+ directByRoot.set(rootId, d);
4760
+ }
4761
+ d.add(eid);
4762
+ }
4763
+ }
4764
+ }
4765
+ return {
4766
+ finalNodes,
4767
+ directByRoot
4768
+ };
4769
+ }
4770
+ function buildComponents(finalNodes) {
4771
+ return [...finalNodes.keys()].sort().map(id => {
4772
+ const fn = finalNodes.get(id);
4773
+ const c = fn.coord;
4774
+ const qualifiers = {
4775
+ __proto__: null
4776
+ };
4777
+ if (c.classifier) {
4778
+ qualifiers['classifier'] = c.classifier;
4779
+ }
4780
+ if (c.ext) {
4781
+ qualifiers['ext'] = c.ext;
4782
+ }
4783
+ const comp = {
4784
+ type: PURL_TYPE_MAVEN,
4785
+ namespace: c.group,
4786
+ name: c.name,
4787
+ ...(c.version ? {
4788
+ version: c.version
4789
+ } : {}),
4790
+ ...(Object.keys(qualifiers).length ? {
4791
+ qualifiers
4792
+ } : {}),
4793
+ id
4794
+ };
4795
+ if (fn.direct) {
4796
+ comp.direct = true;
4797
+ }
4798
+ if (!fn.prod) {
4799
+ comp.dev = true;
4800
+ }
4801
+ if (fn.children.size) {
4802
+ comp.dependencies = [...fn.children].sort();
4803
+ }
4804
+ return comp;
4805
+ });
4806
+ }
4807
+ function buildProjects(parsed, finalNodes, directByRoot, perRoot) {
4808
+ const idsByGav = new Map();
4809
+ for (const [id, fn] of finalNodes) {
4810
+ const key = gav(fn.coord.group, fn.coord.name, fn.coord.version ?? '');
4811
+ let set = idsByGav.get(key);
4812
+ if (!set) {
4813
+ set = new Set();
4814
+ idsByGav.set(key, set);
4815
+ }
4816
+ set.add(id);
4817
+ }
4818
+ const directByProject = new Map();
4819
+ for (const [rootId, ids] of directByRoot) {
4820
+ const pk = perRoot.get(rootId)?.projectKey ?? '';
4821
+ let set = directByProject.get(pk);
4822
+ if (!set) {
4823
+ set = new Set();
4824
+ directByProject.set(pk, set);
4825
+ }
4826
+ for (const id of ids) {
4827
+ set.add(id);
4828
+ }
4829
+ }
4830
+ const projects = [...parsed.projects.values()].map(p => {
4831
+ const entry = {
4832
+ type: PURL_TYPE_MAVEN,
4833
+ namespace: p.group,
4834
+ name: p.name,
4835
+ ...(p.version ? {
4836
+ version: p.version
4837
+ } : {}),
4838
+ subprojectDir: p.dir,
4839
+ dependencies: [...(directByProject.get(p.projectKey) ?? [])].sort(),
4840
+ resolvedAs: [...(idsByGav.get(gav(p.group, p.name, p.version)) ?? [])].sort()
4841
+ };
4842
+ return entry;
4843
+ });
4844
+ projects.sort((a, b) => {
4845
+ const ka = `${a.subprojectDir} ${a.namespace}:${a.name}`;
4846
+ const kb = `${b.subprojectDir} ${b.namespace}:${b.name}`;
4847
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
4848
+ });
4849
+ return projects;
4850
+ }
4851
+ function unionInto(map, key, add) {
4852
+ if (!add.length) {
4853
+ return;
4854
+ }
4855
+ const acc = map.get(key);
4856
+ if (acc) {
4857
+ for (const f of add) {
4858
+ if (!acc.includes(f)) {
4859
+ acc.push(f);
4860
+ }
4861
+ }
4862
+ } else {
4863
+ map.set(key, [...add]);
4864
+ }
4865
+ }
4866
+ function buildArtifactPaths(finalNodes, projects, fileExists) {
4867
+ const projectsByGav = new Map();
4868
+ for (const p of projects) {
4869
+ projectsByGav.set(gav(p.group, p.name, p.version), {
4870
+ sources: p.sources,
4871
+ targets: p.targets
4872
+ });
4873
+ }
4874
+ const targetsByCoord = new Map();
4875
+ const targetsByGav = new Map();
4876
+ const sourcesByCoord = new Map();
4877
+ const coords = new Set();
4878
+ for (const fn of finalNodes.values()) {
4879
+ const c = fn.coord;
4880
+ const coordKey = mavenCoordinateKey(c.group, c.name, c.ext, c.classifier, c.version);
4881
+ if (!coordKey) {
4882
+ continue;
4883
+ }
4884
+ coords.add(coordKey);
4885
+ const pi = projectsByGav.get(gav(c.group, c.name, c.version ?? ''));
4886
+ const sources = (pi?.sources ?? []).filter(fileExists);
4887
+ const targets = [...new Set([...fn.targets, ...(pi?.targets ?? [])])].filter(fileExists).sort();
4888
+ if (sources.length) {
4889
+ sourcesByCoord.set(coordKey, sources);
4890
+ }
4891
+ if (!targets.length) {
4892
+ continue;
4893
+ }
4894
+ targetsByCoord.set(coordKey, targets);
4895
+ const gavKey = mavenCoordinateKey(c.group, c.name, undefined, undefined, c.version);
4896
+ if (gavKey) {
4897
+ const acc = targetsByGav.get(gavKey);
4898
+ if (acc) {
4899
+ for (const f of targets) {
4900
+ if (!acc.includes(f)) {
4901
+ acc.push(f);
4902
+ }
4903
+ }
4904
+ } else {
4905
+ targetsByGav.set(gavKey, [...targets]);
4906
+ }
4907
+ }
4908
+ }
4909
+ // A top-level module is a `project` but usually not a dependency node, so its
4910
+ // source roots (where reachability starts) are missed by the node loop above;
4911
+ // emit first-party module paths here.
4912
+ for (const p of projects) {
4913
+ const coordKey = mavenCoordinateKey(p.group, p.name, undefined, undefined, p.version);
4914
+ if (!coordKey) {
4915
+ continue;
4916
+ }
4917
+ coords.add(coordKey);
4918
+ unionInto(sourcesByCoord, coordKey, p.sources.filter(fileExists));
4919
+ const targets = p.targets.filter(fileExists);
4920
+ unionInto(targetsByCoord, coordKey, targets);
4921
+ unionInto(targetsByGav, coordKey, targets);
4922
+ }
4923
+ return {
4924
+ targetsByCoord,
4925
+ targetsByGav,
4926
+ sourcesByCoord,
4927
+ coords
4928
+ };
4929
+ }
4930
+ function buildReport(parsed) {
4931
+ const seen = new Set();
4932
+ const failures = parsed.failures.filter(f => {
4933
+ const key = `${f.coord}|${f.detail}|${f.config}`;
4934
+ if (seen.has(key)) {
4935
+ return false;
4936
+ }
4937
+ seen.add(key);
4938
+ return true;
4939
+ });
4940
+ return {
4941
+ failures,
4942
+ scannedConfigs: parsed.scannedConfigs
4943
+ };
4944
+ }
4945
+
4946
+ // PATH fallback when no `bin` and no project wrapper.
4947
+ const DEFAULT_BUILD_TOOL_BIN = {
4948
+ __proto__: null,
4949
+ gradle: 'gradle',
4950
+ maven: 'mvn',
4951
+ sbt: 'sbt'
4952
+ };
4953
+
4954
+ // Project-local wrapper, preferred because it pins the expected build-tool
4955
+ // version. sbt has no wrapper convention. POSIX names only (no win32 target).
4956
+ const BUILD_TOOL_WRAPPER = {
4957
+ __proto__: null,
4958
+ gradle: 'gradlew',
4959
+ maven: 'mvnw'
4960
+ };
4961
+ function resolveBuildToolBin(tool, projectDir, bin) {
4962
+ if (bin) {
4963
+ return bin;
4964
+ }
4965
+ const wrapperName = BUILD_TOOL_WRAPPER[tool];
4966
+ if (wrapperName && fs$1.existsSync(path.resolve(projectDir, wrapperName))) {
4967
+ return `./${wrapperName}`;
4968
+ }
4969
+ return DEFAULT_BUILD_TOOL_BIN[tool];
4970
+ }
4971
+
4972
+ // Line-protocol the build-tool scripts emit to a records file (NOT stdout — sbt
4973
+ // prints unsilenceable resolution noise there). One record per line, fields
4974
+ // backslash-escaped (\\, \t, \n, \r) so a value can't break framing:
4975
+ //
4976
+ // <tag>\t<field>\t<field>...
4289
4977
  //
4290
- // The resolved artifact-paths sidecar is intentionally NOT requested here: it
4291
- // only matters for reachability analysis, which is internal to Coana, so Coana
4292
- // emits it itself when it runs reachability. `socket manifest` only needs the
4293
- // facts file.
4978
+ // meta tool toolVersion javaVersion
4979
+ // project projectKey group name version dir
4980
+ // projectSrc projectKey path (--with-files only)
4981
+ // projectTgt projectKey path (--with-files only)
4982
+ // root rootId projectKey config prod(0|1)
4983
+ // node rootId coordId group name version ext classifier direct(0|1)
4984
+ // edge rootId parentCoordId childCoordId
4985
+ // file rootId coordId path (--with-files only)
4986
+ // scanned config
4987
+ // failure coord detail config
4294
4988
  //
4295
- // `spawnCoanaDlx` resolves the Coana CLI via dlx (or a local build when
4296
- // `SOCKET_CLI_COANA_LOCAL_PATH` is set). `bin` (the gradle/maven/sbt executable)
4297
- // is always resolved by the caller to a concrete default (`<cwd>/gradlew`, or
4298
- // `mvn`/`sbt` on PATH) before we get here, so it is forwarded verbatim; the
4299
- // empty guard below is just a cheap safeguard against passing `--bin ''`.
4300
- async function runCoanaManifestFacts({
4989
+ // A `root` is one (subproject, configuration) resolution root; `coordId` is the
4990
+ // coordinate key (`group:name:ext:classifier:version`, empty segments dropped),
4991
+ // used opaquely as the per-root node key. Unknown tags are ignored.
4992
+
4993
+ function unescapeField(s) {
4994
+ if (!s.includes('\\')) {
4995
+ return s;
4996
+ }
4997
+ let out = '';
4998
+ for (let i = 0; i < s.length; i += 1) {
4999
+ const c = s[i];
5000
+ if (c === '\\' && i + 1 < s.length) {
5001
+ const n = s[++i];
5002
+ out += n === 't' ? '\t' : n === 'n' ? '\n' : n === 'r' ? '\r' : n;
5003
+ } else {
5004
+ out += c;
5005
+ }
5006
+ }
5007
+ return out;
5008
+ }
5009
+ function bool(s) {
5010
+ return s === '1' || s === 'true';
5011
+ }
5012
+ function parseRecords(text) {
5013
+ const result = {
5014
+ tool: '',
5015
+ toolVersion: '',
5016
+ javaVersion: '',
5017
+ projects: new Map(),
5018
+ roots: new Map(),
5019
+ scannedConfigs: [],
5020
+ failures: []
5021
+ };
5022
+ const scanned = new Set();
5023
+ const root = id => {
5024
+ let r = result.roots.get(id);
5025
+ if (!r) {
5026
+ r = {
5027
+ rootId: id,
5028
+ projectKey: '',
5029
+ config: '',
5030
+ prod: false,
5031
+ nodes: new Map(),
5032
+ edges: []
5033
+ };
5034
+ result.roots.set(id, r);
5035
+ }
5036
+ return r;
5037
+ };
5038
+ const project = key => {
5039
+ let p = result.projects.get(key);
5040
+ if (!p) {
5041
+ p = {
5042
+ projectKey: key,
5043
+ group: '',
5044
+ name: '',
5045
+ version: '',
5046
+ dir: '',
5047
+ sources: [],
5048
+ targets: []
5049
+ };
5050
+ result.projects.set(key, p);
5051
+ }
5052
+ return p;
5053
+ };
5054
+ for (const rawLine of text.split('\n')) {
5055
+ if (!rawLine) {
5056
+ continue;
5057
+ }
5058
+ const f = rawLine.split('\t').map(unescapeField);
5059
+ switch (f[0]) {
5060
+ case 'meta':
5061
+ result.tool = f[1] ?? '';
5062
+ result.toolVersion = f[2] ?? '';
5063
+ result.javaVersion = f[3] ?? '';
5064
+ break;
5065
+ case 'project':
5066
+ {
5067
+ const p = project(f[1] ?? '');
5068
+ p.group = f[2] ?? '';
5069
+ p.name = f[3] ?? '';
5070
+ p.version = f[4] ?? '';
5071
+ p.dir = f[5] ?? '';
5072
+ break;
5073
+ }
5074
+ case 'projectSrc':
5075
+ if (f[2]) {
5076
+ project(f[1] ?? '').sources.push(f[2]);
5077
+ }
5078
+ break;
5079
+ case 'projectTgt':
5080
+ if (f[2]) {
5081
+ project(f[1] ?? '').targets.push(f[2]);
5082
+ }
5083
+ break;
5084
+ case 'root':
5085
+ {
5086
+ const r = root(f[1] ?? '');
5087
+ r.projectKey = f[2] ?? '';
5088
+ r.config = f[3] ?? '';
5089
+ r.prod = bool(f[4]);
5090
+ break;
5091
+ }
5092
+ case 'node':
5093
+ {
5094
+ const r = root(f[1] ?? '');
5095
+ const coordId = f[2] ?? '';
5096
+ r.nodes.set(coordId, {
5097
+ coordId,
5098
+ coord: {
5099
+ group: f[3] ?? '',
5100
+ name: f[4] ?? '',
5101
+ version: f[5] ?? '',
5102
+ ext: f[6] ?? '',
5103
+ classifier: f[7] ?? ''
5104
+ },
5105
+ direct: bool(f[8]),
5106
+ targets: []
5107
+ });
5108
+ break;
5109
+ }
5110
+ case 'edge':
5111
+ {
5112
+ const parent = f[2] ?? '';
5113
+ const child = f[3] ?? '';
5114
+ if (parent !== child) {
5115
+ root(f[1] ?? '').edges.push([parent, child]);
5116
+ }
5117
+ break;
5118
+ }
5119
+ case 'file':
5120
+ {
5121
+ const node = root(f[1] ?? '').nodes.get(f[2] ?? '');
5122
+ if (node && f[3]) {
5123
+ node.targets.push(f[3]);
5124
+ }
5125
+ break;
5126
+ }
5127
+ case 'scanned':
5128
+ if (f[1]) {
5129
+ scanned.add(f[1]);
5130
+ }
5131
+ break;
5132
+ case 'failure':
5133
+ if (f[1]) {
5134
+ result.failures.push({
5135
+ coord: f[1],
5136
+ detail: f[2] ?? '',
5137
+ config: f[3] ?? ''
5138
+ });
5139
+ }
5140
+ break;
5141
+ }
5142
+ }
5143
+ result.scannedConfigs = [...scanned].sort();
5144
+ return result;
5145
+ }
5146
+
5147
+ const FACTS_TASK = 'socketFacts';
5148
+ const SBT_PLUGIN_FILENAME = 'SocketFactsPlugin.scala';
5149
+
5150
+ // Bundled emitter assets, copied into dist by the rollup build.
5151
+ function manifestScriptsPath(...parts) {
5152
+ return path.join(constants.default.distPath, 'manifest-scripts', ...parts);
5153
+ }
5154
+
5155
+ // Don't throw on a non-zero exit: the script emits failure records, so a usable
5156
+ // records file still exists. A non-exit spawn error (e.g. missing executable)
5157
+ // propagates.
5158
+ async function runNeverThrow(bin, args, opts) {
5159
+ try {
5160
+ const result = await spawn.spawn(bin, args, {
5161
+ cwd: opts.projectDir,
5162
+ stdio: opts.stdio ?? 'inherit',
5163
+ ...(opts.env ? {
5164
+ env: opts.env
5165
+ } : {}),
5166
+ ...(opts.signal ? {
5167
+ signal: opts.signal
5168
+ } : {})
5169
+ });
5170
+ return {
5171
+ code: result.code,
5172
+ stdout: typeof result.stdout === 'string' ? result.stdout : '',
5173
+ stderr: typeof result.stderr === 'string' ? result.stderr : ''
5174
+ };
5175
+ } catch (e) {
5176
+ if (spawn.isSpawnError(e)) {
5177
+ return {
5178
+ code: e.code,
5179
+ stdout: typeof e.stdout === 'string' ? e.stdout : '',
5180
+ stderr: typeof e.stderr === 'string' ? e.stderr : ''
5181
+ };
5182
+ }
5183
+ throw e;
5184
+ }
5185
+ }
5186
+ async function withTmpDir(prefix, fn) {
5187
+ const tmpDir = await fs$1.promises.mkdtemp(path.join(os.tmpdir(), prefix));
5188
+ try {
5189
+ return await fn(tmpDir);
5190
+ } finally {
5191
+ await fs$1.promises.rm(tmpDir, {
5192
+ recursive: true,
5193
+ force: true
5194
+ }).catch(() => {});
5195
+ }
5196
+ }
5197
+ async function writeSbtPlugin(globalBase) {
5198
+ const src = await fs$1.promises.readFile(manifestScriptsPath('socket-facts.plugin.scala'), 'utf8');
5199
+ const pluginsDir = path.join(globalBase, 'plugins');
5200
+ await fs$1.promises.mkdir(pluginsDir, {
5201
+ recursive: true
5202
+ });
5203
+ await fs$1.promises.writeFile(path.join(pluginsDir, SBT_PLUGIN_FILENAME), src);
5204
+ }
5205
+ async function assembleFromRecords(code, recordsFile) {
5206
+ const text = fs$1.existsSync(recordsFile) ? await fs$1.promises.readFile(recordsFile, 'utf8') : '';
5207
+ const {
5208
+ artifactPaths,
5209
+ facts,
5210
+ report
5211
+ } = assembleFacts(parseRecords(text));
5212
+ return {
5213
+ code,
5214
+ facts,
5215
+ report,
5216
+ artifactPaths
5217
+ };
5218
+ }
5219
+
5220
+ // Missing only in an unbuilt local checkout. Fail loudly: without the extension,
5221
+ // Maven silently emits an empty SBOM.
5222
+ function assertMavenExtensionBuilt(jarPath) {
5223
+ if (fs$1.existsSync(jarPath)) {
5224
+ return;
5225
+ }
5226
+ throw new Error(`Maven manifest extension jar not found at ${jarPath}. It is bundled in the published CLI; for local dev build it with: bash src/commands/manifest/scripts/maven-extension/build-jar.sh`);
5227
+ }
5228
+
5229
+ // Runs the build-tool script (which emits a records file) and assembles it.
5230
+ // Writes no files; the caller persists facts or consumes artifactPaths.
5231
+ async function runManifestScript(tool, opts) {
5232
+ switch (tool) {
5233
+ case 'gradle':
5234
+ return await runGradle(opts);
5235
+ case 'sbt':
5236
+ return await runSbt(opts);
5237
+ case 'maven':
5238
+ return await runMaven(opts);
5239
+ }
5240
+ }
5241
+ function commonProps(opts, prefix) {
5242
+ const props = [];
5243
+ if (opts.withFiles) {
5244
+ props.push(`${prefix}socket.withFiles=true`);
5245
+ }
5246
+ if (opts.populateFilesFor) {
5247
+ props.push(`${prefix}socket.populateFilesFor=${opts.populateFilesFor}`);
5248
+ }
5249
+ if (opts.includeConfigs) {
5250
+ props.push(`${prefix}socket.includeConfigs=${opts.includeConfigs}`);
5251
+ }
5252
+ if (opts.excludeConfigs) {
5253
+ props.push(`${prefix}socket.excludeConfigs=${opts.excludeConfigs}`);
5254
+ }
5255
+ return props;
5256
+ }
5257
+ async function runGradle(opts) {
5258
+ const initScript = manifestScriptsPath('socket-facts.init.gradle');
5259
+ return await withTmpDir('socket-gradle-facts-', async tmp => {
5260
+ const recordsFile = path.join(tmp, 'records.tsv');
5261
+ const bin = resolveBuildToolBin('gradle', opts.projectDir, opts.bin);
5262
+ // Disable the configuration cache: the init script's legacy
5263
+ // resolvedConfiguration API and shared accumulator aren't cache-safe.
5264
+ const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []), FACTS_TASK, '--no-daemon', '--console=plain'];
5265
+ const out = await runNeverThrow(bin, args, opts);
5266
+ return await assembleFromRecords(out.code, recordsFile);
5267
+ });
5268
+ }
5269
+ async function runSbt(opts) {
5270
+ return await withTmpDir('socket-sbt-facts-', async globalBase => {
5271
+ await writeSbtPlugin(globalBase);
5272
+ const recordsFile = path.join(globalBase, 'records.tsv');
5273
+ const bin = resolveBuildToolBin('sbt', opts.projectDir, opts.bin);
5274
+ // Fresh per-run global base (not ~/.sbt): sbt executes everything under
5275
+ // plugins/, so a shared path is a code-injection surface. BSP off for this run.
5276
+ const props = [`-Dsbt.global.base=${globalBase}`, '-Dsbt.server.autostart=false', `-Dsocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-D')];
5277
+ // sbt's launcher doesn't always honor JAVA_HOME; never override a
5278
+ // caller-supplied --java-home.
5279
+ const javaHome = opts.env?.['JAVA_HOME'] ?? process.env['JAVA_HOME'];
5280
+ const javaHomeOpt = javaHome && !(opts.toolOpts ?? []).includes('--java-home') ? ['--java-home', javaHome] : [];
5281
+ const args = [...javaHomeOpt, ...props, ...(opts.toolOpts ?? []), '--batch', FACTS_TASK];
5282
+ const out = await runNeverThrow(bin, args, opts);
5283
+ return await assembleFromRecords(out.code, recordsFile);
5284
+ });
5285
+ }
5286
+ async function runMaven(opts) {
5287
+ const jarPath = manifestScriptsPath('maven-extension', 'coana-maven-extension.jar');
5288
+ assertMavenExtensionBuilt(jarPath);
5289
+ return await withTmpDir('socket-maven-facts-', async tmp => {
5290
+ const recordsFile = path.join(tmp, 'records.tsv');
5291
+ const bin = resolveBuildToolBin('maven', opts.projectDir, opts.bin);
5292
+ // `validate` is the cheapest phase that triggers the afterSessionEnd
5293
+ // extension; no compile needed (analysis uses configured paths, not classes).
5294
+ const props = [`-Dmaven.ext.class.path=${jarPath}`, '-Dcoana.task=socket-facts', `-Dsocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-D')];
5295
+ const args = [...props, ...(opts.toolOpts ?? []), '--batch-mode', 'validate'];
5296
+ const out = await runNeverThrow(bin, args, opts);
5297
+ return await assembleFromRecords(out.code, recordsFile);
5298
+ });
5299
+ }
5300
+
5301
+ // Frozen contract with `coana run --compute-artifacts-sidecar`; change only in
5302
+ // sync with the coana consumer. Per coordinate: targets/sources present →
5303
+ // resolved (coana uses the paths); both empty → resolved with no artifact
5304
+ // (pom/BOM), not a failure; absent → coana degrades that vuln to precomputed.
5305
+
5306
+ // Bare array, no schema version: socket-cli pins the coana version, so producer
5307
+ // and consumer never drift.
5308
+
5309
+ // Keyed by full coordinate; unions paths so multiple build roots merge into one.
5310
+
5311
+ function pushUnique(into, from) {
5312
+ for (const f of from) {
5313
+ if (!into.includes(f)) {
5314
+ into.push(f);
5315
+ }
5316
+ }
5317
+ }
5318
+ function addEntry(acc, artifactPaths, group, name, version, ext, classifier) {
5319
+ const coordKey = mavenCoordinateKey(group, name, ext || undefined, classifier ?? undefined, version || undefined);
5320
+ if (!coordKey) {
5321
+ return;
5322
+ }
5323
+ let entry = acc.get(coordKey);
5324
+ if (!entry) {
5325
+ entry = {
5326
+ group,
5327
+ name,
5328
+ version,
5329
+ ext,
5330
+ classifier,
5331
+ targets: [],
5332
+ sources: []
5333
+ };
5334
+ acc.set(coordKey, entry);
5335
+ }
5336
+ pushUnique(entry.targets, artifactPaths.targetsByCoord.get(coordKey) ?? []);
5337
+ pushUnique(entry.sources, artifactPaths.sourcesByCoord.get(coordKey) ?? []);
5338
+ }
5339
+
5340
+ // Emit an entry for every SBOM component AND every first-party project: a
5341
+ // top-level module is a project, not a dependency component, yet its source
5342
+ // roots are where reachability starts, so the sidecar must carry them.
5343
+ function accumulateSidecar(acc, facts, artifactPaths) {
5344
+ for (const comp of facts.components) {
5345
+ addEntry(acc, artifactPaths, comp.namespace ?? '', comp.name, comp.version ?? '', comp.qualifiers?.['ext'] ?? '', comp.qualifiers?.['classifier'] ?? null);
5346
+ }
5347
+ // First-party modules have no ext/classifier.
5348
+ for (const proj of facts.projects ?? []) {
5349
+ addEntry(acc, artifactPaths, proj.namespace ?? '', proj.name, proj.version ?? '', '', null);
5350
+ }
5351
+ }
5352
+ function serializeSidecar(acc) {
5353
+ const resolved = [...acc.values()];
5354
+ for (const entry of resolved) {
5355
+ entry.targets.sort();
5356
+ entry.sources.sort();
5357
+ }
5358
+ resolved.sort((a, b) => {
5359
+ const ka = `${a.group}:${a.name}:${a.ext}:${a.classifier ?? ''}:${a.version}`;
5360
+ const kb = `${b.group}:${b.name}:${b.ext}:${b.classifier ?? ''}:${b.version}`;
5361
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
5362
+ });
5363
+ return resolved;
5364
+ }
5365
+
5366
+ // Runs the bundled build-tool resolution script for a JVM project and writes
5367
+ // `.socket.facts.json`. `withFiles` (reachability only) additionally folds
5368
+ // resolved artifact paths into `sidecarAcc`. A blocking resolution failure — or
5369
+ // a build that crashes without emitting any facts — throws unless
5370
+ // `ignoreUnresolved`.
5371
+ async function runManifestFacts({
4301
5372
  bin,
4302
5373
  buildOpts,
4303
- buildOptsFlag,
4304
5374
  cwd,
4305
5375
  ecosystem,
4306
5376
  excludeConfigs,
4307
5377
  ignoreUnresolved,
4308
5378
  includeConfigs,
4309
- verbose
5379
+ sidecarAcc,
5380
+ verbose,
5381
+ withFiles
4310
5382
  }) {
4311
- // Pin the facts output location explicitly rather than relying on Coana's
4312
- // "project root" default. `factsPath` is then the single source of truth for
4313
- // both what we tell Coana to write and what we verify exists below, so the
4314
- // two can't drift apart if Coana's default ever changes. This is deliberately
4315
- // NOT user-configurable: Socket facts always land in the project root so that
4316
- // `socket scan create <project>` finds them (see cmd-manifest-scala.mts, which
4317
- // rejects --out/--stdout in facts mode).
4318
- const factsDir = cwd;
4319
- const factsFile = constants.default.DOT_SOCKET_DOT_FACTS_JSON;
4320
- const factsPath = path.join(factsDir, factsFile);
4321
- // `coana manifest <ecosystem> <path>` emits `.socket.facts.json` by default;
4322
- // there is no `--facts` flag (the artifact-paths sidecar is reachability-
4323
- // internal and not requested here).
4324
- const coanaArgs = ['manifest', ecosystem, cwd, '--output-dir', factsDir, '--output-file', factsFile];
4325
- if (bin) {
4326
- coanaArgs.push('--bin', bin);
4327
- }
4328
- if (includeConfigs) {
4329
- coanaArgs.push('--include-configs', includeConfigs);
4330
- }
4331
- if (excludeConfigs) {
4332
- coanaArgs.push('--exclude-configs', excludeConfigs);
4333
- }
4334
- if (ignoreUnresolved) {
4335
- coanaArgs.push('--ignore-unresolved');
4336
- }
4337
- if (verbose) {
4338
- coanaArgs.push('--debug');
5383
+ const factsPath = path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
5384
+ logger.logger.log(`Generating Socket facts for the ${ecosystem} project at \`${cwd}\` ...`);
5385
+ const {
5386
+ artifactPaths,
5387
+ code,
5388
+ facts,
5389
+ report
5390
+ } = await runManifestScript(ecosystem, {
5391
+ bin: bin || undefined,
5392
+ excludeConfigs: excludeConfigs || undefined,
5393
+ includeConfigs: includeConfigs || undefined,
5394
+ projectDir: cwd,
5395
+ toolOpts: buildOpts,
5396
+ withFiles
5397
+ });
5398
+ const rendered = renderResolutionErrorReport(report.failures, report.scannedConfigs, ecosystem, {
5399
+ ignoreUnresolved
5400
+ });
5401
+ if (rendered.hasBlockingFailures) {
5402
+ if (ignoreUnresolved) {
5403
+ logger.logger.warn(rendered.summary);
5404
+ } else {
5405
+ if (verbose && rendered.details) {
5406
+ logger.logger.log(rendered.details);
5407
+ }
5408
+ throw new utils.InputError(rendered.summary);
5409
+ }
4339
5410
  }
4340
- // `--gradle-opts` / `--sbt-opts` are variadic on the Coana side; keep them
4341
- // last so the pass-through values don't swallow any following flags.
4342
- if (buildOpts.length) {
4343
- coanaArgs.push(buildOptsFlag, ...buildOpts);
5411
+ if (rendered.nonBlockingNotice) {
5412
+ logger.logger.info(rendered.nonBlockingNotice);
4344
5413
  }
4345
- logger.logger.log(`Generating Socket facts for the ${ecosystem} project at \`${cwd}\` ...`);
4346
- if (verbose) {
4347
- logger.logger.log('[VERBOSE] coana args:', coanaArgs);
5414
+ if (verbose && rendered.details) {
5415
+ logger.logger.log(rendered.details);
4348
5416
  }
4349
5417
 
4350
- // Stream Coana's output so the user sees build-tool progress and Coana's own
4351
- // "Socket facts file written to: ..." line.
4352
- const result = await utils.spawnCoanaDlx(coanaArgs, undefined, {
4353
- cwd
4354
- }, {
4355
- stdio: 'inherit'
4356
- });
4357
- if (!result.ok) {
4358
- process.exitCode = 1;
4359
- logger.logger.fail(result.message || 'Coana failed to generate Socket facts');
5418
+ // A non-zero build exit with no usable output (no graph, no first-party
5419
+ // modules, no failure records) means the build died before the socketFacts
5420
+ // task emitted anything — a script/plugin compile error, OOM, or an unchecked
5421
+ // exception in the extension. The build tool's own exit is the only signal, so
5422
+ // fail closed rather than silently dropping the ecosystem with an empty SBOM
5423
+ // (the empty-facts branch below would otherwise just log "nothing to upload").
5424
+ if (code !== 0 && !facts.components.length && !facts.projects?.length && !report.failures.length) {
5425
+ const message = `The ${ecosystem} build failed (exit code ${code}) before producing any Socket facts. Re-run with --verbose for the build tool's output.`;
5426
+ if (!ignoreUnresolved) {
5427
+ throw new utils.InputError(message);
5428
+ }
5429
+ logger.logger.warn(message);
4360
5430
  return;
4361
5431
  }
4362
- // A zero exit code doesn't guarantee a facts file was written: Coana skips
4363
- // emitting it when there are no resolvable dependencies (e.g. with
4364
- // --ignore-unresolved). We pinned the output to `factsPath` above, so confirm
4365
- // it exists before claiming success; otherwise the "next step: socket scan
4366
- // create" line would mislead.
4367
- if (!fs$1.existsSync(factsPath)) {
4368
- logger.logger.warn(`Coana completed but wrote no ${factsFile} (no resolvable dependencies?); nothing to upload.`);
5432
+
5433
+ // Nothing resolved at all no dependencies and no first-party modules. A
5434
+ // project with only first-party modules (empty components, non-empty projects)
5435
+ // still has source roots reachability needs, so it must be written.
5436
+ if (!facts.components.length && !facts.projects?.length) {
5437
+ logger.logger.warn(`No resolvable ${ecosystem} dependencies found; nothing to upload.`);
4369
5438
  return;
4370
5439
  }
5440
+ await fs$1.promises.writeFile(factsPath, JSON.stringify(facts, null, 2), 'utf8');
5441
+ if (withFiles && sidecarAcc) {
5442
+ accumulateSidecar(sidecarAcc, facts, artifactPaths);
5443
+ }
4371
5444
  logger.logger.success('Generated Socket facts');
4372
- logger.logger.log('');
4373
- logger.logger.log('Next step is to generate a Scan by running the `socket scan create` command on the same directory.');
4374
5445
  }
4375
5446
 
4376
- // Generates a `.socket.facts.json` for a Gradle project by delegating to the
4377
- // Coana CLI's `manifest gradle` command (which owns the Gradle init script that
4378
- // resolves the dependency graph). socket-cli no longer runs gradle itself; an
4379
- // explicit `bin` is forwarded as `--bin`, otherwise Coana defaults to
4380
- // `./gradlew`.
5447
+ // Generates `.socket.facts.json` for a Gradle project via the bundled init script.
4381
5448
  async function convertGradleToFacts({
4382
5449
  bin,
4383
5450
  cwd,
@@ -4385,26 +5452,25 @@ async function convertGradleToFacts({
4385
5452
  gradleOpts,
4386
5453
  ignoreUnresolved,
4387
5454
  includeConfigs,
4388
- verbose
5455
+ sidecarAcc,
5456
+ verbose,
5457
+ withFiles
4389
5458
  }) {
4390
- await runCoanaManifestFacts({
5459
+ await runManifestFacts({
4391
5460
  bin,
4392
5461
  buildOpts: gradleOpts,
4393
- buildOptsFlag: '--gradle-opts',
4394
5462
  cwd,
4395
5463
  ecosystem: 'gradle',
4396
5464
  excludeConfigs,
4397
5465
  ignoreUnresolved,
4398
5466
  includeConfigs,
4399
- verbose
5467
+ sidecarAcc,
5468
+ verbose,
5469
+ withFiles
4400
5470
  });
4401
5471
  }
4402
5472
 
4403
- // Generates a `.socket.facts.json` for a Maven project by delegating to the
4404
- // Coana CLI's `manifest maven` command (which owns the Maven plugin that
4405
- // resolves the dependency graph). socket-cli no longer runs maven itself; an
4406
- // explicit `bin` is forwarded as `--bin`, otherwise Coana defaults to `mvn` on
4407
- // PATH.
5473
+ // Generates `.socket.facts.json` for a Maven project via the bundled extension.
4408
5474
  async function convertMavenToFacts({
4409
5475
  bin,
4410
5476
  cwd,
@@ -4412,28 +5478,27 @@ async function convertMavenToFacts({
4412
5478
  ignoreUnresolved,
4413
5479
  includeConfigs,
4414
5480
  mavenOpts,
4415
- verbose
5481
+ sidecarAcc,
5482
+ verbose,
5483
+ withFiles
4416
5484
  }) {
4417
- await runCoanaManifestFacts({
5485
+ await runManifestFacts({
4418
5486
  bin,
4419
5487
  buildOpts: mavenOpts,
4420
- buildOptsFlag: '--maven-opts',
4421
5488
  cwd,
4422
5489
  ecosystem: 'maven',
4423
5490
  excludeConfigs,
4424
5491
  ignoreUnresolved,
4425
5492
  includeConfigs,
4426
- verbose
5493
+ sidecarAcc,
5494
+ verbose,
5495
+ withFiles
4427
5496
  });
4428
5497
  }
4429
5498
 
4430
- // Generates a `.socket.facts.json` for an sbt project by delegating to the
4431
- // Coana CLI's `manifest sbt` command (which owns the sbt plugin that resolves
4432
- // the dependency graph). socket-cli no longer runs sbt itself; an explicit
4433
- // `bin` is forwarded as `--bin`, otherwise Coana defaults to `sbt` on PATH.
4434
- // JDK-compatibility guidance (sbt 0.13/early 1.x cannot run on modern JDKs) is
4435
- // handled by Coana; pass a compatible JDK via `--sbt-opts "--java-home <path>"`
4436
- // or `JAVA_HOME`.
5499
+ // Generates `.socket.facts.json` for an sbt project via the bundled sbt plugin.
5500
+ // sbt 0.13/early 1.x can't run on modern JDKs pass a compatible JDK via
5501
+ // `--sbt-opts "--java-home <path>"` or `JAVA_HOME`.
4437
5502
  async function convertSbtToFacts({
4438
5503
  bin,
4439
5504
  cwd,
@@ -4441,18 +5506,21 @@ async function convertSbtToFacts({
4441
5506
  ignoreUnresolved,
4442
5507
  includeConfigs,
4443
5508
  sbtOpts,
4444
- verbose
5509
+ sidecarAcc,
5510
+ verbose,
5511
+ withFiles
4445
5512
  }) {
4446
- await runCoanaManifestFacts({
5513
+ await runManifestFacts({
4447
5514
  bin,
4448
5515
  buildOpts: sbtOpts,
4449
- buildOptsFlag: '--sbt-opts',
4450
5516
  cwd,
4451
5517
  ecosystem: 'sbt',
4452
5518
  excludeConfigs,
4453
5519
  ignoreUnresolved,
4454
5520
  includeConfigs,
4455
- verbose
5521
+ sidecarAcc,
5522
+ verbose,
5523
+ withFiles
4456
5524
  });
4457
5525
  }
4458
5526
 
@@ -4955,13 +6023,20 @@ function parseBuildToolOpts(opts) {
4955
6023
  }
4956
6024
 
4957
6025
  async function generateAutoManifest({
6026
+ computeArtifactsSidecar,
4958
6027
  cwd,
4959
6028
  detected,
4960
6029
  outputKind,
6030
+ reachContinueOnInstallErrors,
4961
6031
  verbose
4962
6032
  }) {
4963
6033
  const sockJson = utils.readOrDefaultSocketJson(cwd);
4964
6034
  const generatedFiles = [];
6035
+
6036
+ // Resolved paths across all JVM roots, serialized to one sidecar at the end.
6037
+ const sidecarAcc = computeArtifactsSidecar ? new Map() : undefined;
6038
+ // Reachability: the install-error gate decides abort; manifest path: socket.json.
6039
+ const resolveIgnoreUnresolved = configured => computeArtifactsSidecar ? configured || Boolean(reachContinueOnInstallErrors) : configured;
4965
6040
  if (verbose) {
4966
6041
  logger.logger.info(`Using this ${constants.SOCKET_JSON} for defaults:`, sockJson);
4967
6042
  }
@@ -4983,8 +6058,10 @@ async function generateAutoManifest({
4983
6058
  await convertSbtToFacts({
4984
6059
  ...sbtArgs,
4985
6060
  excludeConfigs: sockJson.defaults?.manifest?.sbt?.excludeConfigs ?? '',
4986
- ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.sbt?.ignoreUnresolved),
4987
- includeConfigs: sockJson.defaults?.manifest?.sbt?.includeConfigs ?? ''
6061
+ ignoreUnresolved: resolveIgnoreUnresolved(Boolean(sockJson.defaults?.manifest?.sbt?.ignoreUnresolved)),
6062
+ includeConfigs: sockJson.defaults?.manifest?.sbt?.includeConfigs ?? '',
6063
+ sidecarAcc,
6064
+ withFiles: computeArtifactsSidecar
4988
6065
  });
4989
6066
  } else {
4990
6067
  logger.logger.log('Detected a Scala sbt build, generating pom files with sbt...');
@@ -4996,10 +6073,8 @@ async function generateAutoManifest({
4996
6073
  }
4997
6074
  if (!sockJson?.defaults?.manifest?.gradle?.disabled && detected.gradle) {
4998
6075
  const gradleArgs = {
4999
- // Note: `gradlew` is more likely to be resolved against cwd.
5000
- // Note: .resolve() won't butcher an absolute path.
5001
- // TODO: `gradlew` (or anything else given) may want to resolve against PATH.
5002
- bin: sockJson.defaults?.manifest?.gradle?.bin ? path.resolve(cwd, sockJson.defaults.manifest.gradle.bin) : path.join(cwd, 'gradlew'),
6076
+ // Configured bin wins; else prefer ./gradlew, else gradle on PATH.
6077
+ bin: sockJson.defaults?.manifest?.gradle?.bin ? path.resolve(cwd, sockJson.defaults.manifest.gradle.bin) : resolveBuildToolBin('gradle', cwd),
5003
6078
  cwd,
5004
6079
  verbose: Boolean(sockJson.defaults?.manifest?.gradle?.verbose),
5005
6080
  gradleOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.gradle?.gradleOpts)
@@ -5011,8 +6086,10 @@ async function generateAutoManifest({
5011
6086
  await convertGradleToFacts({
5012
6087
  ...gradleArgs,
5013
6088
  excludeConfigs: sockJson.defaults?.manifest?.gradle?.excludeConfigs ?? '',
5014
- ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.gradle?.ignoreUnresolved),
5015
- includeConfigs: sockJson.defaults?.manifest?.gradle?.includeConfigs ?? ''
6089
+ ignoreUnresolved: resolveIgnoreUnresolved(Boolean(sockJson.defaults?.manifest?.gradle?.ignoreUnresolved)),
6090
+ includeConfigs: sockJson.defaults?.manifest?.gradle?.includeConfigs ?? '',
6091
+ sidecarAcc,
6092
+ withFiles: computeArtifactsSidecar
5016
6093
  });
5017
6094
  } else {
5018
6095
  logger.logger.log('Detected a gradle build (Gradle, Kotlin, Scala), running default gradle generator...');
@@ -5022,14 +6099,16 @@ async function generateAutoManifest({
5022
6099
  if (!sockJson?.defaults?.manifest?.maven?.disabled && detected.maven) {
5023
6100
  logger.logger.log('Detected a Maven pom.xml build, generating Socket facts...');
5024
6101
  await convertMavenToFacts({
5025
- // Note: `mvn` is more likely to be resolved against PATH env.
5026
- bin: sockJson.defaults?.manifest?.maven?.bin ?? 'mvn',
6102
+ // Configured bin wins; else prefer ./mvnw, else mvn on PATH.
6103
+ bin: sockJson.defaults?.manifest?.maven?.bin ?? resolveBuildToolBin('maven', cwd),
5027
6104
  cwd,
5028
6105
  excludeConfigs: sockJson.defaults?.manifest?.maven?.excludeConfigs ?? '',
5029
- ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.maven?.ignoreUnresolved),
6106
+ ignoreUnresolved: resolveIgnoreUnresolved(Boolean(sockJson.defaults?.manifest?.maven?.ignoreUnresolved)),
5030
6107
  includeConfigs: sockJson.defaults?.manifest?.maven?.includeConfigs ?? '',
5031
6108
  mavenOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.maven?.mavenOpts),
5032
- verbose: Boolean(sockJson.defaults?.manifest?.maven?.verbose)
6109
+ sidecarAcc,
6110
+ verbose: Boolean(sockJson.defaults?.manifest?.maven?.verbose),
6111
+ withFiles: computeArtifactsSidecar
5033
6112
  });
5034
6113
  }
5035
6114
  if (!sockJson?.defaults?.manifest?.conda?.disabled && detected.conda) {
@@ -5079,7 +6158,8 @@ async function generateAutoManifest({
5079
6158
  }
5080
6159
  }
5081
6160
  return {
5082
- generatedFiles
6161
+ generatedFiles,
6162
+ resolvedPathsSidecar: sidecarAcc && sidecarAcc.size ? serializeSidecar(sidecarAcc) : undefined
5083
6163
  };
5084
6164
  }
5085
6165
 
@@ -5142,6 +6222,9 @@ async function handleCreateNewScan({
5142
6222
  tmp,
5143
6223
  workspace
5144
6224
  });
6225
+
6226
+ // Sidecar forwarded to reachability; populated only when reach runs.
6227
+ let resolvedPathsSidecar;
5145
6228
  if (autoManifest) {
5146
6229
  logger.logger.info('Auto-generating manifest files ...');
5147
6230
  require$$9.debugFn('notice', 'Auto-manifest mode enabled');
@@ -5151,11 +6234,14 @@ async function handleCreateNewScan({
5151
6234
  detected
5152
6235
  });
5153
6236
  const autoManifestResult = await generateAutoManifest({
5154
- detected,
6237
+ computeArtifactsSidecar: reach.runReachabilityAnalysis,
5155
6238
  cwd,
6239
+ detected,
5156
6240
  outputKind,
6241
+ reachContinueOnInstallErrors: reach.reachContinueOnInstallErrors,
5157
6242
  verbose: false
5158
6243
  });
6244
+ resolvedPathsSidecar = autoManifestResult.resolvedPathsSidecar;
5159
6245
  if (autoManifestResult.generatedFiles.length) {
5160
6246
  scanTargets = Array.from(new Set([...targets, ...autoManifestResult.generatedFiles]));
5161
6247
  }
@@ -5239,6 +6325,7 @@ async function handleCreateNewScan({
5239
6325
  packagePaths,
5240
6326
  reachabilityOptions: mergedReachabilityOptions,
5241
6327
  repoName,
6328
+ resolvedPathsSidecar,
5242
6329
  spinner,
5243
6330
  target: targets[0]
5244
6331
  });
@@ -10313,7 +11400,7 @@ const config$c = {
10313
11400
  ...flags.commonFlags,
10314
11401
  bin: {
10315
11402
  type: 'string',
10316
- description: 'Location of gradlew binary to use, default: CWD/gradlew'
11403
+ description: 'Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH'
10317
11404
  },
10318
11405
  facts: {
10319
11406
  type: 'boolean',
@@ -10424,7 +11511,8 @@ async function run$D(argv, importMeta, {
10424
11511
  bin = sockJson.defaults?.manifest?.gradle?.bin;
10425
11512
  logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
10426
11513
  } else {
10427
- bin = path.join(cwd, 'gradlew');
11514
+ // Prefer the project's ./gradlew wrapper, else `gradle` on PATH.
11515
+ bin = resolveBuildToolBin('gradle', cwd);
10428
11516
  }
10429
11517
  }
10430
11518
  if (!gradleOpts) {
@@ -10558,7 +11646,7 @@ const config$b = {
10558
11646
  ...flags.commonFlags,
10559
11647
  bin: {
10560
11648
  type: 'string',
10561
- description: 'Location of gradlew binary to use, default: CWD/gradlew'
11649
+ description: 'Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH'
10562
11650
  },
10563
11651
  facts: {
10564
11652
  type: 'boolean',
@@ -10669,7 +11757,8 @@ async function run$C(argv, importMeta, {
10669
11757
  bin = sockJson.defaults?.manifest?.gradle?.bin;
10670
11758
  logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
10671
11759
  } else {
10672
- bin = path.join(cwd, 'gradlew');
11760
+ // Prefer the project's ./gradlew wrapper, else `gradle` on PATH.
11761
+ bin = resolveBuildToolBin('gradle', cwd);
10673
11762
  }
10674
11763
  }
10675
11764
  if (!gradleOpts) {
@@ -10796,7 +11885,7 @@ const config$a = {
10796
11885
  ...flags.commonFlags,
10797
11886
  bin: {
10798
11887
  type: 'string',
10799
- description: 'Location of the maven binary to use, default: mvn on PATH'
11888
+ description: 'Location of the maven binary to use, default: ./mvnw if present, else mvn on PATH'
10800
11889
  },
10801
11890
  includeConfigs: {
10802
11891
  type: 'string',
@@ -10890,7 +11979,8 @@ async function run$B(argv, importMeta, {
10890
11979
  bin = sockJson.defaults?.manifest?.maven?.bin;
10891
11980
  logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
10892
11981
  } else {
10893
- bin = 'mvn';
11982
+ // Prefer the project's ./mvnw wrapper, else `mvn` on PATH.
11983
+ bin = resolveBuildToolBin('maven', cwd);
10894
11984
  }
10895
11985
  }
10896
11986
  if (!mavenOpts) {
@@ -16596,13 +17686,6 @@ async function run$d(argv, importMeta, {
16596
17686
  pendingHead: Boolean(pendingHead),
16597
17687
  pullRequest: Number(pullRequest),
16598
17688
  reach: {
16599
- // Build-tool config for the reach-time resolution, mapped from socket.json
16600
- // (per-ecosystem). Best-effort on plain --reach; under --auto-manifest the
16601
- // config carries top-level failOnBuildToolError=true (fail-closed). Only
16602
- // built when reachability runs.
16603
- autoManifestConfig: reach ? utils.buildAutoManifestConfig(sockJson, {
16604
- autoManifest: Boolean(autoManifest)
16605
- }) : undefined,
16606
17689
  excludePaths,
16607
17690
  reachAnalysisMemoryLimit,
16608
17691
  reachAnalysisTimeout,
@@ -20667,5 +21750,5 @@ process.on('unhandledRejection', async (reason, promise) => {
20667
21750
  // eslint-disable-next-line n/no-process-exit
20668
21751
  process.exit(1);
20669
21752
  });
20670
- //# debugId=6c799c7a-7beb-4a6d-a648-0638ba473165
21753
+ //# debugId=bf57bd7a-dcc7-46d5-8b2d-9709236ff012
20671
21754
  //# sourceMappingURL=cli.js.map