@socketsecurity/cli-with-sentry 1.1.130 → 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.
- package/CHANGELOG.md +12 -0
- package/dist/cli.js +1227 -144
- package/dist/cli.js.map +1 -1
- package/dist/constants.js +4 -4
- package/dist/constants.js.map +1 -1
- package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
- package/dist/manifest-scripts/socket-facts.init.gradle +479 -0
- package/dist/manifest-scripts/socket-facts.plugin.scala +444 -0
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/manifest/cmd-manifest-gradle.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-kotlin.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-maven.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +5 -6
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +5 -6
- package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +7 -8
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts +8 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +20 -0
- package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/assemble.d.mts +16 -0
- package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/build-tool.d.mts +3 -0
- package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/facts.d.mts +46 -0
- package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/records.d.mts +64 -0
- package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts +10 -0
- package/dist/types/commands/manifest/scripts/resolution-report-gradle.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts +7 -0
- package/dist/types/commands/manifest/scripts/resolution-report-ivy.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts +8 -0
- package/dist/types/commands/manifest/scripts/resolution-report-maven.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts +41 -0
- package/dist/types/commands/manifest/scripts/resolution-report-render.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/resolution-report.d.mts +12 -0
- package/dist/types/commands/manifest/scripts/resolution-report.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/run.d.mts +28 -0
- package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -0
- package/dist/types/commands/manifest/scripts/sidecar.d.mts +27 -0
- package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -0
- package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
- package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
- package/dist/types/commands/scan/perform-reachability-analysis.d.mts +4 -2
- package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
- package/dist/utils.js +1 -78
- package/dist/utils.js.map +1 -1
- package/package.json +3 -2
- package/dist/types/commands/manifest/coana-manifest-facts.d.mts +0 -27
- package/dist/types/commands/manifest/coana-manifest-facts.d.mts.map +0 -1
- package/dist/types/utils/auto-manifest-config.d.mts +0 -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
|
-
//
|
|
1844
|
-
//
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
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
|
-
//
|
|
1919
|
-
if (
|
|
1912
|
+
// Best-effort cleanup of the temp sidecar.
|
|
1913
|
+
if (sidecarPath) {
|
|
1920
1914
|
try {
|
|
1921
|
-
await fs$1.promises.unlink(
|
|
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
|
-
//
|
|
4286
|
-
//
|
|
4287
|
-
|
|
4288
|
-
|
|
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
|
-
//
|
|
4291
|
-
//
|
|
4292
|
-
//
|
|
4293
|
-
//
|
|
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
|
-
// `
|
|
4296
|
-
//
|
|
4297
|
-
//
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
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
|
-
|
|
5379
|
+
sidecarAcc,
|
|
5380
|
+
verbose,
|
|
5381
|
+
withFiles
|
|
4310
5382
|
}) {
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
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
|
-
|
|
4341
|
-
|
|
4342
|
-
if (buildOpts.length) {
|
|
4343
|
-
coanaArgs.push(buildOptsFlag, ...buildOpts);
|
|
5411
|
+
if (rendered.nonBlockingNotice) {
|
|
5412
|
+
logger.logger.info(rendered.nonBlockingNotice);
|
|
4344
5413
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
logger.logger.log('[VERBOSE] coana args:', coanaArgs);
|
|
5414
|
+
if (verbose && rendered.details) {
|
|
5415
|
+
logger.logger.log(rendered.details);
|
|
4348
5416
|
}
|
|
4349
5417
|
|
|
4350
|
-
//
|
|
4351
|
-
//
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
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
|
-
|
|
4363
|
-
//
|
|
4364
|
-
//
|
|
4365
|
-
//
|
|
4366
|
-
|
|
4367
|
-
|
|
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
|
|
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
|
-
|
|
5455
|
+
sidecarAcc,
|
|
5456
|
+
verbose,
|
|
5457
|
+
withFiles
|
|
4389
5458
|
}) {
|
|
4390
|
-
await
|
|
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
|
-
|
|
5467
|
+
sidecarAcc,
|
|
5468
|
+
verbose,
|
|
5469
|
+
withFiles
|
|
4400
5470
|
});
|
|
4401
5471
|
}
|
|
4402
5472
|
|
|
4403
|
-
// Generates
|
|
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
|
-
|
|
5481
|
+
sidecarAcc,
|
|
5482
|
+
verbose,
|
|
5483
|
+
withFiles
|
|
4416
5484
|
}) {
|
|
4417
|
-
await
|
|
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
|
-
|
|
5493
|
+
sidecarAcc,
|
|
5494
|
+
verbose,
|
|
5495
|
+
withFiles
|
|
4427
5496
|
});
|
|
4428
5497
|
}
|
|
4429
5498
|
|
|
4430
|
-
// Generates
|
|
4431
|
-
//
|
|
4432
|
-
//
|
|
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
|
-
|
|
5509
|
+
sidecarAcc,
|
|
5510
|
+
verbose,
|
|
5511
|
+
withFiles
|
|
4445
5512
|
}) {
|
|
4446
|
-
await
|
|
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
|
-
|
|
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
|
-
//
|
|
5000
|
-
|
|
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
|
-
//
|
|
5026
|
-
bin: sockJson.defaults?.manifest?.maven?.bin ?? '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
21753
|
+
//# debugId=bf57bd7a-dcc7-46d5-8b2d-9709236ff012
|
|
20671
21754
|
//# sourceMappingURL=cli.js.map
|