@socketsecurity/cli-with-sentry 1.1.131 → 1.1.133
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 +13 -0
- package/dist/cli.js +1302 -139
- 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 +6 -1
- package/dist/types/commands/manifest/generate_auto_manifest.d.mts.map +1 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +21 -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 +31 -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,1238 @@ 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:
|
|
4289
4975
|
//
|
|
4290
|
-
//
|
|
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.
|
|
4976
|
+
// <tag>\t<field>\t<field>...
|
|
4294
4977
|
//
|
|
4295
|
-
//
|
|
4296
|
-
//
|
|
4297
|
-
//
|
|
4298
|
-
//
|
|
4299
|
-
//
|
|
4300
|
-
|
|
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
|
|
4988
|
+
//
|
|
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
|
+
// A build tool that exits non-zero rejects with the spawn-result shape: a
|
|
5177
|
+
// numeric exit `code` plus captured stdout/stderr. Return it so the caller
|
|
5178
|
+
// can assemble failure records / surface the output. Anything else — e.g. a
|
|
5179
|
+
// missing executable, whose `code` is a string like 'ENOENT' — propagates.
|
|
5180
|
+
// This mirrors how utils/dlx.mts classifies spawn failures (a numeric `code`
|
|
5181
|
+
// is a real process exit); the registry's isSpawnError is avoided here
|
|
5182
|
+
// because it is currently broken and never matches.
|
|
5183
|
+
if (e !== null && typeof e === 'object' && typeof e.code === 'number') {
|
|
5184
|
+
const err = e;
|
|
5185
|
+
return {
|
|
5186
|
+
code: err.code,
|
|
5187
|
+
stdout: typeof err.stdout === 'string' ? err.stdout : '',
|
|
5188
|
+
stderr: typeof err.stderr === 'string' ? err.stderr : ''
|
|
5189
|
+
};
|
|
5190
|
+
}
|
|
5191
|
+
throw e;
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
async function withTmpDir(prefix, fn) {
|
|
5195
|
+
const tmpDir = await fs$1.promises.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
5196
|
+
try {
|
|
5197
|
+
return await fn(tmpDir);
|
|
5198
|
+
} finally {
|
|
5199
|
+
await fs$1.promises.rm(tmpDir, {
|
|
5200
|
+
recursive: true,
|
|
5201
|
+
force: true
|
|
5202
|
+
}).catch(() => {});
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
async function writeSbtPlugin(globalBase) {
|
|
5206
|
+
const src = await fs$1.promises.readFile(manifestScriptsPath('socket-facts.plugin.scala'), 'utf8');
|
|
5207
|
+
const pluginsDir = path.join(globalBase, 'plugins');
|
|
5208
|
+
await fs$1.promises.mkdir(pluginsDir, {
|
|
5209
|
+
recursive: true
|
|
5210
|
+
});
|
|
5211
|
+
await fs$1.promises.writeFile(path.join(pluginsDir, SBT_PLUGIN_FILENAME), src);
|
|
5212
|
+
}
|
|
5213
|
+
async function assembleFromRecords(out, recordsFile) {
|
|
5214
|
+
const text = fs$1.existsSync(recordsFile) ? await fs$1.promises.readFile(recordsFile, 'utf8') : '';
|
|
5215
|
+
const {
|
|
5216
|
+
artifactPaths,
|
|
5217
|
+
facts,
|
|
5218
|
+
report
|
|
5219
|
+
} = assembleFacts(parseRecords(text));
|
|
5220
|
+
return {
|
|
5221
|
+
code: out.code,
|
|
5222
|
+
facts,
|
|
5223
|
+
report,
|
|
5224
|
+
artifactPaths,
|
|
5225
|
+
stderr: out.stderr,
|
|
5226
|
+
stdout: out.stdout
|
|
5227
|
+
};
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
// Missing only in an unbuilt local checkout. Fail loudly: without the extension,
|
|
5231
|
+
// Maven silently emits an empty SBOM.
|
|
5232
|
+
function assertMavenExtensionBuilt(jarPath) {
|
|
5233
|
+
if (fs$1.existsSync(jarPath)) {
|
|
5234
|
+
return;
|
|
5235
|
+
}
|
|
5236
|
+
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`);
|
|
5237
|
+
}
|
|
5238
|
+
|
|
5239
|
+
// Runs the build-tool script (which emits a records file) and assembles it.
|
|
5240
|
+
// Writes no files; the caller persists facts or consumes artifactPaths.
|
|
5241
|
+
async function runManifestScript(tool, opts) {
|
|
5242
|
+
switch (tool) {
|
|
5243
|
+
case 'gradle':
|
|
5244
|
+
return await runGradle(opts);
|
|
5245
|
+
case 'sbt':
|
|
5246
|
+
return await runSbt(opts);
|
|
5247
|
+
case 'maven':
|
|
5248
|
+
return await runMaven(opts);
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
function commonProps(opts, prefix) {
|
|
5252
|
+
const props = [];
|
|
5253
|
+
if (opts.withFiles) {
|
|
5254
|
+
props.push(`${prefix}socket.withFiles=true`);
|
|
5255
|
+
}
|
|
5256
|
+
if (opts.populateFilesFor) {
|
|
5257
|
+
props.push(`${prefix}socket.populateFilesFor=${opts.populateFilesFor}`);
|
|
5258
|
+
}
|
|
5259
|
+
if (opts.includeConfigs) {
|
|
5260
|
+
props.push(`${prefix}socket.includeConfigs=${opts.includeConfigs}`);
|
|
5261
|
+
}
|
|
5262
|
+
if (opts.excludeConfigs) {
|
|
5263
|
+
props.push(`${prefix}socket.excludeConfigs=${opts.excludeConfigs}`);
|
|
5264
|
+
}
|
|
5265
|
+
return props;
|
|
5266
|
+
}
|
|
5267
|
+
async function runGradle(opts) {
|
|
5268
|
+
const initScript = manifestScriptsPath('socket-facts.init.gradle');
|
|
5269
|
+
return await withTmpDir('socket-gradle-facts-', async tmp => {
|
|
5270
|
+
const recordsFile = path.join(tmp, 'records.tsv');
|
|
5271
|
+
const bin = resolveBuildToolBin('gradle', opts.projectDir, opts.bin);
|
|
5272
|
+
// Disable the configuration cache: the init script's legacy
|
|
5273
|
+
// resolvedConfiguration API and shared accumulator aren't cache-safe.
|
|
5274
|
+
const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []), FACTS_TASK, '--no-daemon', '--console=plain'];
|
|
5275
|
+
const out = await runNeverThrow(bin, args, opts);
|
|
5276
|
+
return await assembleFromRecords(out, recordsFile);
|
|
5277
|
+
});
|
|
5278
|
+
}
|
|
5279
|
+
async function runSbt(opts) {
|
|
5280
|
+
return await withTmpDir('socket-sbt-facts-', async globalBase => {
|
|
5281
|
+
await writeSbtPlugin(globalBase);
|
|
5282
|
+
const recordsFile = path.join(globalBase, 'records.tsv');
|
|
5283
|
+
const bin = resolveBuildToolBin('sbt', opts.projectDir, opts.bin);
|
|
5284
|
+
// Fresh per-run global base (not ~/.sbt): sbt executes everything under
|
|
5285
|
+
// plugins/, so a shared path is a code-injection surface. BSP off for this run.
|
|
5286
|
+
const props = [`-Dsbt.global.base=${globalBase}`, '-Dsbt.server.autostart=false', `-Dsocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-D')];
|
|
5287
|
+
// sbt's launcher doesn't always honor JAVA_HOME; never override a
|
|
5288
|
+
// caller-supplied --java-home.
|
|
5289
|
+
const javaHome = opts.env?.['JAVA_HOME'] ?? process.env['JAVA_HOME'];
|
|
5290
|
+
const javaHomeOpt = javaHome && !(opts.toolOpts ?? []).includes('--java-home') ? ['--java-home', javaHome] : [];
|
|
5291
|
+
const args = [...javaHomeOpt, ...props, ...(opts.toolOpts ?? []), '--batch', FACTS_TASK];
|
|
5292
|
+
const out = await runNeverThrow(bin, args, opts);
|
|
5293
|
+
return await assembleFromRecords(out, recordsFile);
|
|
5294
|
+
});
|
|
5295
|
+
}
|
|
5296
|
+
async function runMaven(opts) {
|
|
5297
|
+
const jarPath = manifestScriptsPath('maven-extension', 'coana-maven-extension.jar');
|
|
5298
|
+
assertMavenExtensionBuilt(jarPath);
|
|
5299
|
+
return await withTmpDir('socket-maven-facts-', async tmp => {
|
|
5300
|
+
const recordsFile = path.join(tmp, 'records.tsv');
|
|
5301
|
+
const bin = resolveBuildToolBin('maven', opts.projectDir, opts.bin);
|
|
5302
|
+
// `validate` is the cheapest phase that triggers the afterSessionEnd
|
|
5303
|
+
// extension; no compile needed (analysis uses configured paths, not classes).
|
|
5304
|
+
const props = [`-Dmaven.ext.class.path=${jarPath}`, '-Dcoana.task=socket-facts', `-Dsocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-D')];
|
|
5305
|
+
const args = [...props, ...(opts.toolOpts ?? []), '--batch-mode', 'validate'];
|
|
5306
|
+
const out = await runNeverThrow(bin, args, opts);
|
|
5307
|
+
return await assembleFromRecords(out, recordsFile);
|
|
5308
|
+
});
|
|
5309
|
+
}
|
|
5310
|
+
|
|
5311
|
+
// Frozen contract with `coana run --compute-artifacts-sidecar`; change only in
|
|
5312
|
+
// sync with the coana consumer. Per coordinate: targets/sources present →
|
|
5313
|
+
// resolved (coana uses the paths); both empty → resolved with no artifact
|
|
5314
|
+
// (pom/BOM), not a failure; absent → coana degrades that vuln to precomputed.
|
|
5315
|
+
|
|
5316
|
+
// Bare array, no schema version: socket-cli pins the coana version, so producer
|
|
5317
|
+
// and consumer never drift.
|
|
5318
|
+
|
|
5319
|
+
// Keyed by full coordinate; unions paths so multiple build roots merge into one.
|
|
5320
|
+
|
|
5321
|
+
function pushUnique(into, from) {
|
|
5322
|
+
for (const f of from) {
|
|
5323
|
+
if (!into.includes(f)) {
|
|
5324
|
+
into.push(f);
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
function addEntry(acc, artifactPaths, group, name, version, ext, classifier) {
|
|
5329
|
+
const coordKey = mavenCoordinateKey(group, name, ext || undefined, classifier ?? undefined, version || undefined);
|
|
5330
|
+
if (!coordKey) {
|
|
5331
|
+
return;
|
|
5332
|
+
}
|
|
5333
|
+
let entry = acc.get(coordKey);
|
|
5334
|
+
if (!entry) {
|
|
5335
|
+
entry = {
|
|
5336
|
+
group,
|
|
5337
|
+
name,
|
|
5338
|
+
version,
|
|
5339
|
+
ext,
|
|
5340
|
+
classifier,
|
|
5341
|
+
targets: [],
|
|
5342
|
+
sources: []
|
|
5343
|
+
};
|
|
5344
|
+
acc.set(coordKey, entry);
|
|
5345
|
+
}
|
|
5346
|
+
pushUnique(entry.targets, artifactPaths.targetsByCoord.get(coordKey) ?? []);
|
|
5347
|
+
pushUnique(entry.sources, artifactPaths.sourcesByCoord.get(coordKey) ?? []);
|
|
5348
|
+
}
|
|
5349
|
+
|
|
5350
|
+
// Emit an entry for every SBOM component AND every first-party project: a
|
|
5351
|
+
// top-level module is a project, not a dependency component, yet its source
|
|
5352
|
+
// roots are where reachability starts, so the sidecar must carry them.
|
|
5353
|
+
function accumulateSidecar(acc, facts, artifactPaths) {
|
|
5354
|
+
for (const comp of facts.components) {
|
|
5355
|
+
addEntry(acc, artifactPaths, comp.namespace ?? '', comp.name, comp.version ?? '', comp.qualifiers?.['ext'] ?? '', comp.qualifiers?.['classifier'] ?? null);
|
|
5356
|
+
}
|
|
5357
|
+
// First-party modules have no ext/classifier.
|
|
5358
|
+
for (const proj of facts.projects ?? []) {
|
|
5359
|
+
addEntry(acc, artifactPaths, proj.namespace ?? '', proj.name, proj.version ?? '', '', null);
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
function serializeSidecar(acc) {
|
|
5363
|
+
const resolved = [...acc.values()];
|
|
5364
|
+
for (const entry of resolved) {
|
|
5365
|
+
entry.targets.sort();
|
|
5366
|
+
entry.sources.sort();
|
|
5367
|
+
}
|
|
5368
|
+
resolved.sort((a, b) => {
|
|
5369
|
+
const ka = `${a.group}:${a.name}:${a.ext}:${a.classifier ?? ''}:${a.version}`;
|
|
5370
|
+
const kb = `${b.group}:${b.name}:${b.ext}:${b.classifier ?? ''}:${b.version}`;
|
|
5371
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
5372
|
+
});
|
|
5373
|
+
return resolved;
|
|
5374
|
+
}
|
|
5375
|
+
|
|
5376
|
+
// Last N non-empty lines of the captured build output, for diagnosing a crash
|
|
5377
|
+
// without forcing a --verbose rebuild.
|
|
5378
|
+
function tailBuildOutput(stdout, stderr) {
|
|
5379
|
+
const combined = [stdout, stderr].map(s => s.trimEnd()).filter(Boolean).join('\n');
|
|
5380
|
+
return combined.split('\n').slice(-40).join('\n');
|
|
5381
|
+
}
|
|
5382
|
+
|
|
5383
|
+
// Runs the bundled build-tool resolution script for a JVM project and writes
|
|
5384
|
+
// `.socket.facts.json`. `withFiles` (reachability only) additionally folds
|
|
5385
|
+
// resolved artifact paths into `sidecarAcc`. A blocking resolution failure sets
|
|
5386
|
+
// a non-zero exit code and returns (matching the `--pom` generator) unless
|
|
5387
|
+
// `ignoreUnresolved`; a crashed build — a process failure, not an unresolved
|
|
5388
|
+
// dependency — always fails.
|
|
5389
|
+
async function runManifestFacts({
|
|
4301
5390
|
bin,
|
|
4302
5391
|
buildOpts,
|
|
4303
|
-
buildOptsFlag,
|
|
4304
5392
|
cwd,
|
|
4305
5393
|
ecosystem,
|
|
4306
5394
|
excludeConfigs,
|
|
4307
5395
|
ignoreUnresolved,
|
|
4308
5396
|
includeConfigs,
|
|
4309
|
-
|
|
5397
|
+
sidecarAcc,
|
|
5398
|
+
verbose,
|
|
5399
|
+
withFiles
|
|
4310
5400
|
}) {
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
const
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
5401
|
+
const factsPath = path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
|
|
5402
|
+
logger.logger.log(`Generating Socket facts for the ${ecosystem} project at \`${cwd}\` ...`);
|
|
5403
|
+
const scriptOpts = {
|
|
5404
|
+
bin: bin || undefined,
|
|
5405
|
+
excludeConfigs: excludeConfigs || undefined,
|
|
5406
|
+
includeConfigs: includeConfigs || undefined,
|
|
5407
|
+
projectDir: cwd,
|
|
5408
|
+
// Stream the build tool's output only when asked; otherwise capture it and
|
|
5409
|
+
// show a spinner, surfacing the output only if the build crashes.
|
|
5410
|
+
stdio: verbose ? 'inherit' : 'pipe',
|
|
5411
|
+
toolOpts: buildOpts,
|
|
5412
|
+
withFiles
|
|
5413
|
+
};
|
|
5414
|
+
const {
|
|
5415
|
+
spinner
|
|
5416
|
+
} = constants.default;
|
|
5417
|
+
let result;
|
|
5418
|
+
try {
|
|
5419
|
+
if (verbose) {
|
|
5420
|
+
logger.logger.info(`(Running ${ecosystem} with output streaming; this can take a while.)`);
|
|
5421
|
+
result = await runManifestScript(ecosystem, scriptOpts);
|
|
5422
|
+
} else {
|
|
5423
|
+
logger.logger.info(`(No live output; pass --verbose to stream the ${ecosystem} build output.)`);
|
|
5424
|
+
spinner.start(`Resolving ${ecosystem} dependencies ...`);
|
|
5425
|
+
result = await runManifestScript(ecosystem, scriptOpts);
|
|
5426
|
+
if (result.code === 0) {
|
|
5427
|
+
spinner.successAndStop(`Resolved ${ecosystem} dependencies.`);
|
|
5428
|
+
} else {
|
|
5429
|
+
spinner.failAndStop(`${ecosystem} build exited with code ${result.code}.`);
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
} catch (e) {
|
|
5433
|
+
// Only a spawn-level failure (e.g. the build tool missing from PATH) reaches
|
|
5434
|
+
// here; runNeverThrow returns non-zero build exits rather than throwing.
|
|
5435
|
+
if (!verbose) {
|
|
5436
|
+
spinner.failAndStop(`Failed to run ${ecosystem}.`);
|
|
5437
|
+
}
|
|
5438
|
+
process.exitCode = 1;
|
|
5439
|
+
logger.logger.fail(`Could not run the ${ecosystem} build tool` + (verbose ? `: ${e}` : ' (run with --verbose for details).'));
|
|
5440
|
+
return;
|
|
4336
5441
|
}
|
|
4337
|
-
|
|
4338
|
-
|
|
5442
|
+
const {
|
|
5443
|
+
artifactPaths,
|
|
5444
|
+
code,
|
|
5445
|
+
facts,
|
|
5446
|
+
report,
|
|
5447
|
+
stderr,
|
|
5448
|
+
stdout
|
|
5449
|
+
} = result;
|
|
5450
|
+
const rendered = renderResolutionErrorReport(report.failures, report.scannedConfigs, ecosystem, {
|
|
5451
|
+
ignoreUnresolved
|
|
5452
|
+
});
|
|
5453
|
+
if (rendered.hasBlockingFailures) {
|
|
5454
|
+
if (ignoreUnresolved) {
|
|
5455
|
+
logger.logger.warn(rendered.summary);
|
|
5456
|
+
} else {
|
|
5457
|
+
process.exitCode = 1;
|
|
5458
|
+
logger.logger.fail(rendered.summary);
|
|
5459
|
+
if (verbose && rendered.details) {
|
|
5460
|
+
logger.logger.log(rendered.details);
|
|
5461
|
+
}
|
|
5462
|
+
return;
|
|
5463
|
+
}
|
|
4339
5464
|
}
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
if (buildOpts.length) {
|
|
4343
|
-
coanaArgs.push(buildOptsFlag, ...buildOpts);
|
|
5465
|
+
if (rendered.nonBlockingNotice) {
|
|
5466
|
+
logger.logger.info(rendered.nonBlockingNotice);
|
|
4344
5467
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
logger.logger.log('[VERBOSE] coana args:', coanaArgs);
|
|
5468
|
+
if (verbose && rendered.details) {
|
|
5469
|
+
logger.logger.log(rendered.details);
|
|
4348
5470
|
}
|
|
4349
5471
|
|
|
4350
|
-
//
|
|
4351
|
-
//
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
5472
|
+
// A non-zero build exit with no usable output (no graph, no first-party
|
|
5473
|
+
// modules, no failure records) means the build died before the socketFacts
|
|
5474
|
+
// task emitted anything — a script/plugin compile error, OOM, or an unchecked
|
|
5475
|
+
// exception in the extension. The build tool's own exit is the only signal, so
|
|
5476
|
+
// fail closed rather than silently dropping the ecosystem with an empty SBOM
|
|
5477
|
+
// (the empty-facts branch below would otherwise just log "nothing to upload").
|
|
5478
|
+
if (code !== 0 && !facts.components.length && !facts.projects?.length && !report.failures.length) {
|
|
5479
|
+
if (!verbose) {
|
|
5480
|
+
const tail = tailBuildOutput(stdout, stderr);
|
|
5481
|
+
if (tail) {
|
|
5482
|
+
logger.logger.group('Build output:');
|
|
5483
|
+
logger.logger.error(tail);
|
|
5484
|
+
logger.logger.groupEnd();
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
// A crashed build is a process failure (missing JDK/build tool, unparseable
|
|
5488
|
+
// project, OOM, plugin error), not an unresolved dependency, so it fails
|
|
5489
|
+
// regardless of `ignoreUnresolved` — that flag only tolerates dependencies a
|
|
5490
|
+
// successful run couldn't resolve.
|
|
4358
5491
|
process.exitCode = 1;
|
|
4359
|
-
logger.logger.fail(
|
|
5492
|
+
logger.logger.fail(`The ${ecosystem} build failed (exit code ${code}) before producing any Socket facts.`);
|
|
4360
5493
|
return;
|
|
4361
5494
|
}
|
|
4362
|
-
|
|
4363
|
-
//
|
|
4364
|
-
//
|
|
4365
|
-
//
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
logger.logger.warn(`Coana completed but wrote no ${factsFile} (no resolvable dependencies?); nothing to upload.`);
|
|
5495
|
+
|
|
5496
|
+
// Nothing resolved at all — no dependencies and no first-party modules. A
|
|
5497
|
+
// project with only first-party modules (empty components, non-empty projects)
|
|
5498
|
+
// still has source roots reachability needs, so it must be written.
|
|
5499
|
+
if (!facts.components.length && !facts.projects?.length) {
|
|
5500
|
+
logger.logger.warn(`No resolvable ${ecosystem} dependencies found; nothing to upload.`);
|
|
4369
5501
|
return;
|
|
4370
5502
|
}
|
|
5503
|
+
await fs$1.promises.writeFile(factsPath, JSON.stringify(facts, null, 2), 'utf8');
|
|
5504
|
+
if (withFiles && sidecarAcc) {
|
|
5505
|
+
accumulateSidecar(sidecarAcc, facts, artifactPaths);
|
|
5506
|
+
}
|
|
4371
5507
|
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
5508
|
}
|
|
4375
5509
|
|
|
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`.
|
|
5510
|
+
// Generates `.socket.facts.json` for a Gradle project via the bundled init script.
|
|
4381
5511
|
async function convertGradleToFacts({
|
|
4382
5512
|
bin,
|
|
4383
5513
|
cwd,
|
|
@@ -4385,26 +5515,25 @@ async function convertGradleToFacts({
|
|
|
4385
5515
|
gradleOpts,
|
|
4386
5516
|
ignoreUnresolved,
|
|
4387
5517
|
includeConfigs,
|
|
4388
|
-
|
|
5518
|
+
sidecarAcc,
|
|
5519
|
+
verbose,
|
|
5520
|
+
withFiles
|
|
4389
5521
|
}) {
|
|
4390
|
-
await
|
|
5522
|
+
await runManifestFacts({
|
|
4391
5523
|
bin,
|
|
4392
5524
|
buildOpts: gradleOpts,
|
|
4393
|
-
buildOptsFlag: '--gradle-opts',
|
|
4394
5525
|
cwd,
|
|
4395
5526
|
ecosystem: 'gradle',
|
|
4396
5527
|
excludeConfigs,
|
|
4397
5528
|
ignoreUnresolved,
|
|
4398
5529
|
includeConfigs,
|
|
4399
|
-
|
|
5530
|
+
sidecarAcc,
|
|
5531
|
+
verbose,
|
|
5532
|
+
withFiles
|
|
4400
5533
|
});
|
|
4401
5534
|
}
|
|
4402
5535
|
|
|
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.
|
|
5536
|
+
// Generates `.socket.facts.json` for a Maven project via the bundled extension.
|
|
4408
5537
|
async function convertMavenToFacts({
|
|
4409
5538
|
bin,
|
|
4410
5539
|
cwd,
|
|
@@ -4412,28 +5541,27 @@ async function convertMavenToFacts({
|
|
|
4412
5541
|
ignoreUnresolved,
|
|
4413
5542
|
includeConfigs,
|
|
4414
5543
|
mavenOpts,
|
|
4415
|
-
|
|
5544
|
+
sidecarAcc,
|
|
5545
|
+
verbose,
|
|
5546
|
+
withFiles
|
|
4416
5547
|
}) {
|
|
4417
|
-
await
|
|
5548
|
+
await runManifestFacts({
|
|
4418
5549
|
bin,
|
|
4419
5550
|
buildOpts: mavenOpts,
|
|
4420
|
-
buildOptsFlag: '--maven-opts',
|
|
4421
5551
|
cwd,
|
|
4422
5552
|
ecosystem: 'maven',
|
|
4423
5553
|
excludeConfigs,
|
|
4424
5554
|
ignoreUnresolved,
|
|
4425
5555
|
includeConfigs,
|
|
4426
|
-
|
|
5556
|
+
sidecarAcc,
|
|
5557
|
+
verbose,
|
|
5558
|
+
withFiles
|
|
4427
5559
|
});
|
|
4428
5560
|
}
|
|
4429
5561
|
|
|
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`.
|
|
5562
|
+
// Generates `.socket.facts.json` for an sbt project via the bundled sbt plugin.
|
|
5563
|
+
// sbt 0.13/early 1.x can't run on modern JDKs — pass a compatible JDK via
|
|
5564
|
+
// `--sbt-opts "--java-home <path>"` or `JAVA_HOME`.
|
|
4437
5565
|
async function convertSbtToFacts({
|
|
4438
5566
|
bin,
|
|
4439
5567
|
cwd,
|
|
@@ -4441,18 +5569,21 @@ async function convertSbtToFacts({
|
|
|
4441
5569
|
ignoreUnresolved,
|
|
4442
5570
|
includeConfigs,
|
|
4443
5571
|
sbtOpts,
|
|
4444
|
-
|
|
5572
|
+
sidecarAcc,
|
|
5573
|
+
verbose,
|
|
5574
|
+
withFiles
|
|
4445
5575
|
}) {
|
|
4446
|
-
await
|
|
5576
|
+
await runManifestFacts({
|
|
4447
5577
|
bin,
|
|
4448
5578
|
buildOpts: sbtOpts,
|
|
4449
|
-
buildOptsFlag: '--sbt-opts',
|
|
4450
5579
|
cwd,
|
|
4451
5580
|
ecosystem: 'sbt',
|
|
4452
5581
|
excludeConfigs,
|
|
4453
5582
|
ignoreUnresolved,
|
|
4454
5583
|
includeConfigs,
|
|
4455
|
-
|
|
5584
|
+
sidecarAcc,
|
|
5585
|
+
verbose,
|
|
5586
|
+
withFiles
|
|
4456
5587
|
});
|
|
4457
5588
|
}
|
|
4458
5589
|
|
|
@@ -4954,7 +6085,19 @@ function parseBuildToolOpts(opts) {
|
|
|
4954
6085
|
return tokens;
|
|
4955
6086
|
}
|
|
4956
6087
|
|
|
6088
|
+
// Under --auto-manifest, a manifest generator that failed — raising the exit
|
|
6089
|
+
// code above the value captured before it ran — aborts the whole run: a partial
|
|
6090
|
+
// or empty SBOM silently under-reports dependencies. The generator has already
|
|
6091
|
+
// logged the specifics. A tolerated resolution failure (ignoreUnresolved) warns
|
|
6092
|
+
// without touching the exit code, so it passes through here and the run
|
|
6093
|
+
// continues.
|
|
6094
|
+
function abortManifestRunIfFailed(ecosystem, beforeExitCode) {
|
|
6095
|
+
if (process.exitCode && process.exitCode !== beforeExitCode) {
|
|
6096
|
+
throw new utils.InputError(`Auto-manifest generation failed for the ${ecosystem} project; aborting (see the errors above).`);
|
|
6097
|
+
}
|
|
6098
|
+
}
|
|
4957
6099
|
async function generateAutoManifest({
|
|
6100
|
+
computeArtifactsSidecar,
|
|
4958
6101
|
cwd,
|
|
4959
6102
|
detected,
|
|
4960
6103
|
outputKind,
|
|
@@ -4962,6 +6105,9 @@ async function generateAutoManifest({
|
|
|
4962
6105
|
}) {
|
|
4963
6106
|
const sockJson = utils.readOrDefaultSocketJson(cwd);
|
|
4964
6107
|
const generatedFiles = [];
|
|
6108
|
+
|
|
6109
|
+
// Resolved paths across all JVM roots, serialized to one sidecar at the end.
|
|
6110
|
+
const sidecarAcc = computeArtifactsSidecar ? new Map() : undefined;
|
|
4965
6111
|
if (verbose) {
|
|
4966
6112
|
logger.logger.info(`Using this ${constants.SOCKET_JSON} for defaults:`, sockJson);
|
|
4967
6113
|
}
|
|
@@ -4980,26 +6126,30 @@ async function generateAutoManifest({
|
|
|
4980
6126
|
// `defaults.manifest.sbt.facts: false` in socket.json.
|
|
4981
6127
|
if (sockJson.defaults?.manifest?.sbt?.facts !== false) {
|
|
4982
6128
|
logger.logger.log('Detected a Scala sbt build, generating Socket facts...');
|
|
6129
|
+
const beforeExitCode = process.exitCode;
|
|
4983
6130
|
await convertSbtToFacts({
|
|
4984
6131
|
...sbtArgs,
|
|
4985
6132
|
excludeConfigs: sockJson.defaults?.manifest?.sbt?.excludeConfigs ?? '',
|
|
4986
6133
|
ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.sbt?.ignoreUnresolved),
|
|
4987
|
-
includeConfigs: sockJson.defaults?.manifest?.sbt?.includeConfigs ?? ''
|
|
6134
|
+
includeConfigs: sockJson.defaults?.manifest?.sbt?.includeConfigs ?? '',
|
|
6135
|
+
sidecarAcc,
|
|
6136
|
+
withFiles: computeArtifactsSidecar
|
|
4988
6137
|
});
|
|
6138
|
+
abortManifestRunIfFailed('sbt', beforeExitCode);
|
|
4989
6139
|
} else {
|
|
4990
6140
|
logger.logger.log('Detected a Scala sbt build, generating pom files with sbt...');
|
|
6141
|
+
const beforeExitCode = process.exitCode;
|
|
4991
6142
|
await convertSbtToMaven({
|
|
4992
6143
|
...sbtArgs,
|
|
4993
6144
|
out: sockJson.defaults?.manifest?.sbt?.outfile ?? './pom.xml'
|
|
4994
6145
|
});
|
|
6146
|
+
abortManifestRunIfFailed('sbt', beforeExitCode);
|
|
4995
6147
|
}
|
|
4996
6148
|
}
|
|
4997
6149
|
if (!sockJson?.defaults?.manifest?.gradle?.disabled && detected.gradle) {
|
|
4998
6150
|
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'),
|
|
6151
|
+
// Configured bin wins; else prefer ./gradlew, else gradle on PATH.
|
|
6152
|
+
bin: sockJson.defaults?.manifest?.gradle?.bin ? path.resolve(cwd, sockJson.defaults.manifest.gradle.bin) : resolveBuildToolBin('gradle', cwd),
|
|
5003
6153
|
cwd,
|
|
5004
6154
|
verbose: Boolean(sockJson.defaults?.manifest?.gradle?.verbose),
|
|
5005
6155
|
gradleOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.gradle?.gradleOpts)
|
|
@@ -5008,29 +6158,39 @@ async function generateAutoManifest({
|
|
|
5008
6158
|
// `defaults.manifest.gradle.facts: false` in socket.json.
|
|
5009
6159
|
if (sockJson.defaults?.manifest?.gradle?.facts !== false) {
|
|
5010
6160
|
logger.logger.log('Detected a gradle build (Gradle, Kotlin, Scala), generating Socket facts...');
|
|
6161
|
+
const beforeExitCode = process.exitCode;
|
|
5011
6162
|
await convertGradleToFacts({
|
|
5012
6163
|
...gradleArgs,
|
|
5013
6164
|
excludeConfigs: sockJson.defaults?.manifest?.gradle?.excludeConfigs ?? '',
|
|
5014
6165
|
ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.gradle?.ignoreUnresolved),
|
|
5015
|
-
includeConfigs: sockJson.defaults?.manifest?.gradle?.includeConfigs ?? ''
|
|
6166
|
+
includeConfigs: sockJson.defaults?.manifest?.gradle?.includeConfigs ?? '',
|
|
6167
|
+
sidecarAcc,
|
|
6168
|
+
withFiles: computeArtifactsSidecar
|
|
5016
6169
|
});
|
|
6170
|
+
abortManifestRunIfFailed('gradle', beforeExitCode);
|
|
5017
6171
|
} else {
|
|
5018
6172
|
logger.logger.log('Detected a gradle build (Gradle, Kotlin, Scala), running default gradle generator...');
|
|
6173
|
+
const beforeExitCode = process.exitCode;
|
|
5019
6174
|
await convertGradleToMaven(gradleArgs);
|
|
6175
|
+
abortManifestRunIfFailed('gradle', beforeExitCode);
|
|
5020
6176
|
}
|
|
5021
6177
|
}
|
|
5022
6178
|
if (!sockJson?.defaults?.manifest?.maven?.disabled && detected.maven) {
|
|
5023
6179
|
logger.logger.log('Detected a Maven pom.xml build, generating Socket facts...');
|
|
6180
|
+
const beforeExitCode = process.exitCode;
|
|
5024
6181
|
await convertMavenToFacts({
|
|
5025
|
-
//
|
|
5026
|
-
bin: sockJson.defaults?.manifest?.maven?.bin ?? '
|
|
6182
|
+
// Configured bin wins; else prefer ./mvnw, else mvn on PATH.
|
|
6183
|
+
bin: sockJson.defaults?.manifest?.maven?.bin ?? resolveBuildToolBin('maven', cwd),
|
|
5027
6184
|
cwd,
|
|
5028
6185
|
excludeConfigs: sockJson.defaults?.manifest?.maven?.excludeConfigs ?? '',
|
|
5029
6186
|
ignoreUnresolved: Boolean(sockJson.defaults?.manifest?.maven?.ignoreUnresolved),
|
|
5030
6187
|
includeConfigs: sockJson.defaults?.manifest?.maven?.includeConfigs ?? '',
|
|
5031
6188
|
mavenOpts: parseBuildToolOpts(sockJson.defaults?.manifest?.maven?.mavenOpts),
|
|
5032
|
-
|
|
6189
|
+
sidecarAcc,
|
|
6190
|
+
verbose: Boolean(sockJson.defaults?.manifest?.maven?.verbose),
|
|
6191
|
+
withFiles: computeArtifactsSidecar
|
|
5033
6192
|
});
|
|
6193
|
+
abortManifestRunIfFailed('maven', beforeExitCode);
|
|
5034
6194
|
}
|
|
5035
6195
|
if (!sockJson?.defaults?.manifest?.conda?.disabled && detected.conda) {
|
|
5036
6196
|
logger.logger.log('Detected an environment.yml file, running default Conda generator...');
|
|
@@ -5079,7 +6239,8 @@ async function generateAutoManifest({
|
|
|
5079
6239
|
}
|
|
5080
6240
|
}
|
|
5081
6241
|
return {
|
|
5082
|
-
generatedFiles
|
|
6242
|
+
generatedFiles,
|
|
6243
|
+
resolvedPathsSidecar: sidecarAcc && sidecarAcc.size ? serializeSidecar(sidecarAcc) : undefined
|
|
5083
6244
|
};
|
|
5084
6245
|
}
|
|
5085
6246
|
|
|
@@ -5142,6 +6303,9 @@ async function handleCreateNewScan({
|
|
|
5142
6303
|
tmp,
|
|
5143
6304
|
workspace
|
|
5144
6305
|
});
|
|
6306
|
+
|
|
6307
|
+
// Sidecar forwarded to reachability; populated only when reach runs.
|
|
6308
|
+
let resolvedPathsSidecar;
|
|
5145
6309
|
if (autoManifest) {
|
|
5146
6310
|
logger.logger.info('Auto-generating manifest files ...');
|
|
5147
6311
|
require$$9.debugFn('notice', 'Auto-manifest mode enabled');
|
|
@@ -5151,11 +6315,13 @@ async function handleCreateNewScan({
|
|
|
5151
6315
|
detected
|
|
5152
6316
|
});
|
|
5153
6317
|
const autoManifestResult = await generateAutoManifest({
|
|
5154
|
-
|
|
6318
|
+
computeArtifactsSidecar: reach.runReachabilityAnalysis,
|
|
5155
6319
|
cwd,
|
|
6320
|
+
detected,
|
|
5156
6321
|
outputKind,
|
|
5157
6322
|
verbose: false
|
|
5158
6323
|
});
|
|
6324
|
+
resolvedPathsSidecar = autoManifestResult.resolvedPathsSidecar;
|
|
5159
6325
|
if (autoManifestResult.generatedFiles.length) {
|
|
5160
6326
|
scanTargets = Array.from(new Set([...targets, ...autoManifestResult.generatedFiles]));
|
|
5161
6327
|
}
|
|
@@ -5239,6 +6405,7 @@ async function handleCreateNewScan({
|
|
|
5239
6405
|
packagePaths,
|
|
5240
6406
|
reachabilityOptions: mergedReachabilityOptions,
|
|
5241
6407
|
repoName,
|
|
6408
|
+
resolvedPathsSidecar,
|
|
5242
6409
|
spinner,
|
|
5243
6410
|
target: targets[0]
|
|
5244
6411
|
});
|
|
@@ -10313,7 +11480,7 @@ const config$c = {
|
|
|
10313
11480
|
...flags.commonFlags,
|
|
10314
11481
|
bin: {
|
|
10315
11482
|
type: 'string',
|
|
10316
|
-
description: 'Location of
|
|
11483
|
+
description: 'Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH'
|
|
10317
11484
|
},
|
|
10318
11485
|
facts: {
|
|
10319
11486
|
type: 'boolean',
|
|
@@ -10424,7 +11591,8 @@ async function run$D(argv, importMeta, {
|
|
|
10424
11591
|
bin = sockJson.defaults?.manifest?.gradle?.bin;
|
|
10425
11592
|
logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
|
|
10426
11593
|
} else {
|
|
10427
|
-
|
|
11594
|
+
// Prefer the project's ./gradlew wrapper, else `gradle` on PATH.
|
|
11595
|
+
bin = resolveBuildToolBin('gradle', cwd);
|
|
10428
11596
|
}
|
|
10429
11597
|
}
|
|
10430
11598
|
if (!gradleOpts) {
|
|
@@ -10558,7 +11726,7 @@ const config$b = {
|
|
|
10558
11726
|
...flags.commonFlags,
|
|
10559
11727
|
bin: {
|
|
10560
11728
|
type: 'string',
|
|
10561
|
-
description: 'Location of
|
|
11729
|
+
description: 'Location of the gradle binary to use, default: ./gradlew if present, else gradle on PATH'
|
|
10562
11730
|
},
|
|
10563
11731
|
facts: {
|
|
10564
11732
|
type: 'boolean',
|
|
@@ -10669,7 +11837,8 @@ async function run$C(argv, importMeta, {
|
|
|
10669
11837
|
bin = sockJson.defaults?.manifest?.gradle?.bin;
|
|
10670
11838
|
logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
|
|
10671
11839
|
} else {
|
|
10672
|
-
|
|
11840
|
+
// Prefer the project's ./gradlew wrapper, else `gradle` on PATH.
|
|
11841
|
+
bin = resolveBuildToolBin('gradle', cwd);
|
|
10673
11842
|
}
|
|
10674
11843
|
}
|
|
10675
11844
|
if (!gradleOpts) {
|
|
@@ -10796,7 +11965,7 @@ const config$a = {
|
|
|
10796
11965
|
...flags.commonFlags,
|
|
10797
11966
|
bin: {
|
|
10798
11967
|
type: 'string',
|
|
10799
|
-
description: 'Location of the maven binary to use, default: mvn on PATH'
|
|
11968
|
+
description: 'Location of the maven binary to use, default: ./mvnw if present, else mvn on PATH'
|
|
10800
11969
|
},
|
|
10801
11970
|
includeConfigs: {
|
|
10802
11971
|
type: 'string',
|
|
@@ -10890,7 +12059,8 @@ async function run$B(argv, importMeta, {
|
|
|
10890
12059
|
bin = sockJson.defaults?.manifest?.maven?.bin;
|
|
10891
12060
|
logger.logger.info(`Using default --bin from ${constants.SOCKET_JSON}:`, bin);
|
|
10892
12061
|
} else {
|
|
10893
|
-
|
|
12062
|
+
// Prefer the project's ./mvnw wrapper, else `mvn` on PATH.
|
|
12063
|
+
bin = resolveBuildToolBin('maven', cwd);
|
|
10894
12064
|
}
|
|
10895
12065
|
}
|
|
10896
12066
|
if (!mavenOpts) {
|
|
@@ -16596,13 +17766,6 @@ async function run$d(argv, importMeta, {
|
|
|
16596
17766
|
pendingHead: Boolean(pendingHead),
|
|
16597
17767
|
pullRequest: Number(pullRequest),
|
|
16598
17768
|
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
17769
|
excludePaths,
|
|
16607
17770
|
reachAnalysisMemoryLimit,
|
|
16608
17771
|
reachAnalysisTimeout,
|
|
@@ -20667,5 +21830,5 @@ process.on('unhandledRejection', async (reason, promise) => {
|
|
|
20667
21830
|
// eslint-disable-next-line n/no-process-exit
|
|
20668
21831
|
process.exit(1);
|
|
20669
21832
|
});
|
|
20670
|
-
//# debugId=
|
|
21833
|
+
//# debugId=124ab184-9379-4139-921b-7d32dd1456f3
|
|
20671
21834
|
//# sourceMappingURL=cli.js.map
|