@socketsecurity/cli-with-sentry 1.6.1 → 1.7.0
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 +16 -0
- package/README.md +5 -5
- package/dist/cli.js +230 -223
- 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 +100 -18
- package/dist/manifest-scripts/socket-facts.plugin.scala +30 -3
- package/dist/tsconfig.dts.tsbuildinfo +1 -1
- package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
- package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts.map +1 -1
- package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +2 -1
- 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 +2 -1
- 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 +1 -1
- package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +1 -1
- package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
- package/dist/types/commands/manifest/run-manifest-facts.d.mts +3 -2
- package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/assemble.d.mts +4 -1
- package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/build-tool.d.mts +2 -0
- package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/facts.d.mts +18 -16
- package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/records.d.mts +20 -2
- package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/run.d.mts +4 -0
- package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
- package/dist/types/commands/manifest/scripts/sidecar.d.mts +6 -8
- package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
- 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.map +1 -1
- package/dist/types/utils/coana.d.mts +10 -0
- package/dist/types/utils/coana.d.mts.map +1 -1
- package/dist/utils.js +25 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1814,28 +1814,9 @@ function reachMemoryLimitToMb(value) {
|
|
|
1814
1814
|
return match[2]?.toLowerCase() === 'gb' ? amount * 1024 : amount;
|
|
1815
1815
|
}
|
|
1816
1816
|
|
|
1817
|
-
// No sources/targets here: those are local absolute paths, returned in-memory
|
|
1818
|
-
// as ResolvedArtifactPaths, never serialized into the SBOM.
|
|
1819
|
-
|
|
1820
|
-
// Relative to the facts file's directory.
|
|
1821
|
-
|
|
1822
|
-
// Resolved on-disk paths for a --with-files run, keyed by coordinate. `targets`
|
|
1823
|
-
// = classpath entries (jars / module output dirs); `sources` = module source
|
|
1824
|
-
// roots.
|
|
1825
|
-
|
|
1826
|
-
function projectClasspathKey(project) {
|
|
1827
|
-
return `${project.subprojectDir} ${project.namespace ?? ''}:${project.name}`;
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
// Coordinate-based (not `id`-based) so it also matches foreign SBOMs like
|
|
1831
|
-
// CycloneDX. Empty segments dropped.
|
|
1832
|
-
function mavenCoordinateKey(groupId, artifactId, type, classifier, version) {
|
|
1833
|
-
return [groupId, artifactId, type, classifier, version].filter(Boolean).join(':');
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
1817
|
// Frozen contract with `coana run --compute-artifacts-sidecar`; change only
|
|
1837
1818
|
// in sync with the coana consumer. Keyed by the absolute path of the
|
|
1838
|
-
//
|
|
1819
|
+
// `*.socket.facts.json` file whose own projects[]/components[] these entries
|
|
1839
1820
|
// describe - the key IS the scope, so two independent reactors that happen to
|
|
1840
1821
|
// emit the same purl identity (e.g. a shared internal module name) can never
|
|
1841
1822
|
// collide: each is only ever looked up within its own key. No cross-reactor
|
|
@@ -1843,24 +1824,13 @@ function mavenCoordinateKey(groupId, artifactId, type, classifier, version) {
|
|
|
1843
1824
|
// independent reactors is intentionally duplicated across all of their
|
|
1844
1825
|
// components[].
|
|
1845
1826
|
|
|
1846
|
-
// `
|
|
1847
|
-
// for this coordinate - an empty array is a successful resolve that found
|
|
1848
|
-
// nothing (e.g. a pom/BOM with no artifact), not a failure. Both fields
|
|
1849
|
-
// omitted (undefined) means resolution couldn't even be attempted - the only
|
|
1850
|
-
// case here is a degenerate entry with no computable coordinate at all, since
|
|
1851
|
-
// every entry reaching this function already came from a resolved graph node
|
|
1852
|
-
// (an unresolved dependency lives in the resolution report, not here).
|
|
1827
|
+
// `[]` means resolved and found nothing (e.g. a pom/BOM with no artifact).
|
|
1853
1828
|
function attachPaths(entry, artifactPaths) {
|
|
1854
|
-
const
|
|
1855
|
-
if (!coordKey) {
|
|
1856
|
-
return {
|
|
1857
|
-
...entry
|
|
1858
|
-
};
|
|
1859
|
-
}
|
|
1829
|
+
const paths = artifactPaths.pathsById.get(entry.id);
|
|
1860
1830
|
return {
|
|
1861
1831
|
...entry,
|
|
1862
|
-
targets: [...(
|
|
1863
|
-
sources: [...(
|
|
1832
|
+
targets: [...(paths?.targets ?? [])],
|
|
1833
|
+
sources: [...(paths?.sources ?? [])]
|
|
1864
1834
|
};
|
|
1865
1835
|
}
|
|
1866
1836
|
function purlSortKey(entry) {
|
|
@@ -1877,9 +1847,8 @@ function sortByPurl(entries) {
|
|
|
1877
1847
|
// Emit an entry for every SBOM component AND every first-party project: a
|
|
1878
1848
|
// top-level module is a project, not a dependency component, yet its source
|
|
1879
1849
|
// roots are where reachability starts, so the sidecar must carry them.
|
|
1880
|
-
//
|
|
1881
|
-
//
|
|
1882
|
-
// existing last-writer-wins convention for that case.
|
|
1850
|
+
// Every build writes its own facts file, so a key is accumulated once; a
|
|
1851
|
+
// repeated call for the same factsFile (the same build run again) overwrites.
|
|
1883
1852
|
function accumulateSidecar(acc, facts, artifactPaths, factsFile,
|
|
1884
1853
|
// Off when artifact paths were not resolved; entries then omit `targets` and `sources`.
|
|
1885
1854
|
withPaths = true) {
|
|
@@ -1890,7 +1859,7 @@ withPaths = true) {
|
|
|
1890
1859
|
components: facts.components.map(paths),
|
|
1891
1860
|
projects: (facts.projects ?? []).map(proj => ({
|
|
1892
1861
|
...paths(proj),
|
|
1893
|
-
classpath: [...(artifactPaths.classpathByProject.get(
|
|
1862
|
+
classpath: [...(artifactPaths.classpathByProject.get(proj.id) ?? [])]
|
|
1894
1863
|
}))
|
|
1895
1864
|
});
|
|
1896
1865
|
}
|
|
@@ -1986,15 +1955,13 @@ async function performReachabilityAnalysis(options) {
|
|
|
1986
1955
|
}
|
|
1987
1956
|
const sockSdk = sockSdkCResult.data;
|
|
1988
1957
|
spinner?.start('Uploading manifests for reachability analysis...');
|
|
1958
|
+
const outputFilePath = outputPath || constants.default.DOT_SOCKET_DOT_FACTS_JSON;
|
|
1989
1959
|
|
|
1990
1960
|
// Ensure uploaded manifest files are relative to analysis target as coana resolves SBOM manifest files relative to this path
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
// facts files are cleaned up downstream — see the post-success
|
|
1996
|
-
// deletion in handle-create-new-scan.mts.
|
|
1997
|
-
const uploadCResult = await utils.handleApiCall(sockSdk.uploadManifestFiles(orgSlug, packagePaths, {
|
|
1961
|
+
const uploadCResult = await utils.handleApiCall(sockSdk.uploadManifestFiles(orgSlug, packagePaths.filter(p => !utils.isReachabilityReportPath(p, {
|
|
1962
|
+
cwd,
|
|
1963
|
+
outputPath: outputFilePath
|
|
1964
|
+
})), {
|
|
1998
1965
|
pathsRelativeTo: path.resolve(cwd, analysisTarget)
|
|
1999
1966
|
}), {
|
|
2000
1967
|
description: 'upload manifests',
|
|
@@ -2022,7 +1989,6 @@ async function performReachabilityAnalysis(options) {
|
|
|
2022
1989
|
spinner?.success(`Manifests uploaded successfully. Tar hash: ${tarHash}`);
|
|
2023
1990
|
spinner?.start();
|
|
2024
1991
|
spinner?.infoAndStop('Running reachability analysis with Coana...');
|
|
2025
|
-
const outputFilePath = outputPath || constants.default.DOT_SOCKET_DOT_FACTS_JSON;
|
|
2026
1992
|
|
|
2027
1993
|
// Temp file for --compute-artifacts-sidecar, removed in the finally below.
|
|
2028
1994
|
// Written even when empty under dynamicSbomInference, since the
|
|
@@ -2313,6 +2279,15 @@ const BUILD_TOOL_WRAPPER = {
|
|
|
2313
2279
|
maven: 'mvnw'
|
|
2314
2280
|
};
|
|
2315
2281
|
|
|
2282
|
+
// Gradle (8+) and sbt hold one build per directory; Maven builds are addressed
|
|
2283
|
+
// by POM file, so `mvn -f other-pom.xml` puts a second build in the directory.
|
|
2284
|
+
const ADDRESSED_BY_FILE = {
|
|
2285
|
+
__proto__: null,
|
|
2286
|
+
gradle: false,
|
|
2287
|
+
maven: true,
|
|
2288
|
+
sbt: false
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2316
2291
|
// sbt happily runs in any directory, synthesizing a default project from its
|
|
2317
2292
|
// name, so an sbt run outside a build yields a plausible but bogus SBOM. Maven
|
|
2318
2293
|
// and Gradle refuse such a directory themselves.
|
|
@@ -2330,6 +2305,14 @@ function resolveBuildToolBin(tool, projectDir, bin) {
|
|
|
2330
2305
|
return DEFAULT_BUILD_TOOL_BIN[tool];
|
|
2331
2306
|
}
|
|
2332
2307
|
|
|
2308
|
+
// Distinct for every build sharing a directory.
|
|
2309
|
+
function socketFactsFileName(tool, entryFile) {
|
|
2310
|
+
if (!ADDRESSED_BY_FILE[tool]) {
|
|
2311
|
+
return `${tool}${constants.default.DOT_SOCKET_DOT_FACTS_JSON}`;
|
|
2312
|
+
}
|
|
2313
|
+
return entryFile ? `${path.basename(entryFile)}${constants.default.DOT_SOCKET_DOT_FACTS_JSON}` : undefined;
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2333
2316
|
// Gradle's variant-aware resolver: distinct exceptions give mutually-exclusive
|
|
2334
2317
|
// phrasing, so most-specific-first substring checks classify reliably.
|
|
2335
2318
|
function classifyGradleFailure(detail) {
|
|
@@ -2687,19 +2670,15 @@ function renderResolutionErrorReport(failures, scannedConfigs = [], tool = 'grad
|
|
|
2687
2670
|
}
|
|
2688
2671
|
|
|
2689
2672
|
const PURL_TYPE_MAVEN = 'maven';
|
|
2690
|
-
function assembleFacts(parsed, opts
|
|
2673
|
+
function assembleFacts(parsed, opts) {
|
|
2691
2674
|
const fileExists = opts.fileExists ?? fs$1.existsSync;
|
|
2692
2675
|
const perRoot = buildPerRoot(parsed);
|
|
2693
2676
|
const {
|
|
2694
2677
|
directByRoot,
|
|
2695
2678
|
finalNodes
|
|
2696
|
-
} =
|
|
2679
|
+
} = mergeById(perRoot);
|
|
2697
2680
|
const tool = parsed.tool || 'gradle';
|
|
2698
|
-
const
|
|
2699
|
-
for (const p of parsed.projects.values()) {
|
|
2700
|
-
projectsByGav.set(gav(p.group, p.name, p.version), p);
|
|
2701
|
-
}
|
|
2702
|
-
const components = buildComponents(finalNodes, projectsByGav, buildManifestFilesByCoord(parsed, directByRoot, perRoot));
|
|
2681
|
+
const components = buildComponents(finalNodes, buildManifestFilesById(parsed, directByRoot, perRoot, opts.factsFileName));
|
|
2703
2682
|
const projects = opts.emitProjects === false ? [] : buildProjects(parsed, directByRoot, perRoot);
|
|
2704
2683
|
const metadata = {
|
|
2705
2684
|
format: 'socket-facts-sbom',
|
|
@@ -2720,35 +2699,50 @@ function assembleFacts(parsed, opts = {}) {
|
|
|
2720
2699
|
return {
|
|
2721
2700
|
facts,
|
|
2722
2701
|
report: buildReport(parsed),
|
|
2723
|
-
artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()],
|
|
2702
|
+
artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], perRoot, fileExists)
|
|
2724
2703
|
};
|
|
2725
2704
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2705
|
+
|
|
2706
|
+
// A node resolving to a build project takes that project's id, so projects
|
|
2707
|
+
// sharing a coordinate stay apart and the project's variants collapse into it.
|
|
2708
|
+
function componentId(coordId, project) {
|
|
2709
|
+
return project || coordId;
|
|
2728
2710
|
}
|
|
2729
2711
|
function buildPerRoot(parsed) {
|
|
2730
2712
|
const out = new Map();
|
|
2731
2713
|
for (const [rootId, r] of parsed.roots) {
|
|
2732
|
-
const
|
|
2714
|
+
const idOf = coordId => componentId(coordId, r.nodes.get(coordId)?.project ?? '');
|
|
2715
|
+
const nodes = new Map();
|
|
2716
|
+
for (const [coordId, n] of r.nodes) {
|
|
2717
|
+
const id = idOf(coordId);
|
|
2718
|
+
let node = nodes.get(id);
|
|
2719
|
+
if (!node) {
|
|
2720
|
+
node = {
|
|
2721
|
+
coord: n.project ? {
|
|
2722
|
+
...n.coord,
|
|
2723
|
+
classifier: '',
|
|
2724
|
+
ext: ''
|
|
2725
|
+
} : n.coord,
|
|
2726
|
+
children: [],
|
|
2727
|
+
direct: false,
|
|
2728
|
+
project: n.project,
|
|
2729
|
+
targets: []
|
|
2730
|
+
};
|
|
2731
|
+
nodes.set(id, node);
|
|
2732
|
+
}
|
|
2733
|
+
node.direct ||= n.direct;
|
|
2734
|
+
node.targets.push(...n.targets);
|
|
2735
|
+
}
|
|
2733
2736
|
for (const [p, c] of r.edges) {
|
|
2734
2737
|
if (!r.nodes.has(p) || !r.nodes.has(c)) {
|
|
2735
2738
|
continue;
|
|
2736
2739
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2740
|
+
const parentId = idOf(p);
|
|
2741
|
+
const childId = idOf(c);
|
|
2742
|
+
const parent = nodes.get(parentId);
|
|
2743
|
+
if (childId !== parentId && !parent.children.includes(childId)) {
|
|
2744
|
+
parent.children.push(childId);
|
|
2741
2745
|
}
|
|
2742
|
-
set.add(c);
|
|
2743
|
-
}
|
|
2744
|
-
const nodes = new Map();
|
|
2745
|
-
for (const [coordId, n] of r.nodes) {
|
|
2746
|
-
nodes.set(coordId, {
|
|
2747
|
-
coord: n.coord,
|
|
2748
|
-
children: [...(childrenByParent.get(coordId) ?? [])],
|
|
2749
|
-
direct: n.direct,
|
|
2750
|
-
targets: n.targets
|
|
2751
|
-
});
|
|
2752
2746
|
}
|
|
2753
2747
|
out.set(rootId, {
|
|
2754
2748
|
projectKey: r.projectKey,
|
|
@@ -2759,10 +2753,10 @@ function buildPerRoot(parsed) {
|
|
|
2759
2753
|
return out;
|
|
2760
2754
|
}
|
|
2761
2755
|
|
|
2762
|
-
// Components are merged by
|
|
2763
|
-
//
|
|
2764
|
-
//
|
|
2765
|
-
function
|
|
2756
|
+
// Components are merged by id across every resolution root; which ids belong
|
|
2757
|
+
// to which subproject is kept separately (classpathByProject) for
|
|
2758
|
+
// reachability, which needs each subproject's exact classpath.
|
|
2759
|
+
function mergeById(perRoot) {
|
|
2766
2760
|
const finalNodes = new Map();
|
|
2767
2761
|
const directByRoot = new Map();
|
|
2768
2762
|
for (const [rootId, {
|
|
@@ -2777,6 +2771,7 @@ function mergeByCoordinate(perRoot) {
|
|
|
2777
2771
|
children: new Set(),
|
|
2778
2772
|
prod: false,
|
|
2779
2773
|
direct: false,
|
|
2774
|
+
project: node.project,
|
|
2780
2775
|
targets: new Set()
|
|
2781
2776
|
};
|
|
2782
2777
|
finalNodes.set(coordId, fn);
|
|
@@ -2808,18 +2803,23 @@ function mergeByCoordinate(perRoot) {
|
|
|
2808
2803
|
directByRoot
|
|
2809
2804
|
};
|
|
2810
2805
|
}
|
|
2811
|
-
function
|
|
2806
|
+
function buildManifestFilesById(parsed, directByRoot, perRoot, factsFileName) {
|
|
2812
2807
|
const buildFilesByCoord = new Map();
|
|
2813
2808
|
for (const [rootId, ids] of directByRoot) {
|
|
2814
|
-
const
|
|
2815
|
-
const
|
|
2809
|
+
const root = perRoot.get(rootId);
|
|
2810
|
+
const project = parsed.projects.get(root?.projectKey ?? '');
|
|
2816
2811
|
for (const id of ids) {
|
|
2817
2812
|
let set = buildFilesByCoord.get(id);
|
|
2818
2813
|
if (!set) {
|
|
2819
2814
|
set = new Set();
|
|
2820
2815
|
buildFilesByCoord.set(id, set);
|
|
2821
2816
|
}
|
|
2822
|
-
|
|
2817
|
+
const coord = root?.nodes.get(id)?.coord;
|
|
2818
|
+
// Without a known declaring script, the project's build file, even one
|
|
2819
|
+
// absent on disk: its presence marks the dependency direct here.
|
|
2820
|
+
const declared = coord && project?.declaredIn.get(`${coord.group}:${coord.name}`);
|
|
2821
|
+
const files = declared ?? [...(project?.buildFiles ?? []), ...(project?.missingBuildFiles ?? [])];
|
|
2822
|
+
for (const f of files) {
|
|
2823
2823
|
set.add(f);
|
|
2824
2824
|
}
|
|
2825
2825
|
}
|
|
@@ -2827,11 +2827,11 @@ function buildManifestFilesByCoord(parsed, directByRoot, perRoot) {
|
|
|
2827
2827
|
return new Map([...buildFilesByCoord].map(({
|
|
2828
2828
|
0: id,
|
|
2829
2829
|
1: buildFiles
|
|
2830
|
-
}) => [id, [
|
|
2830
|
+
}) => [id, [...(factsFileName ? [factsFileName] : []), ...[...buildFiles].sort()].map(file => ({
|
|
2831
2831
|
file
|
|
2832
2832
|
}))]));
|
|
2833
2833
|
}
|
|
2834
|
-
function buildComponents(finalNodes,
|
|
2834
|
+
function buildComponents(finalNodes, manifestFilesById) {
|
|
2835
2835
|
return [...finalNodes.keys()].sort().map(id => {
|
|
2836
2836
|
const fn = finalNodes.get(id);
|
|
2837
2837
|
const c = fn.coord;
|
|
@@ -2862,13 +2862,13 @@ function buildComponents(finalNodes, projectsByGav, manifestFilesByCoord) {
|
|
|
2862
2862
|
if (!fn.prod) {
|
|
2863
2863
|
comp.dev = true;
|
|
2864
2864
|
}
|
|
2865
|
-
if (
|
|
2865
|
+
if (fn.project) {
|
|
2866
2866
|
comp.firstParty = true;
|
|
2867
2867
|
}
|
|
2868
2868
|
if (fn.children.size) {
|
|
2869
2869
|
comp.dependencies = [...fn.children].sort();
|
|
2870
2870
|
}
|
|
2871
|
-
const manifestFiles =
|
|
2871
|
+
const manifestFiles = manifestFilesById.get(id);
|
|
2872
2872
|
if (manifestFiles) {
|
|
2873
2873
|
comp.manifestFiles = manifestFiles;
|
|
2874
2874
|
}
|
|
@@ -2890,6 +2890,7 @@ function buildProjects(parsed, directByRoot, perRoot) {
|
|
|
2890
2890
|
}
|
|
2891
2891
|
const projects = [...parsed.projects.values()].map(p => {
|
|
2892
2892
|
const entry = {
|
|
2893
|
+
id: p.projectKey,
|
|
2893
2894
|
type: PURL_TYPE_MAVEN,
|
|
2894
2895
|
namespace: p.group,
|
|
2895
2896
|
name: p.name,
|
|
@@ -2899,6 +2900,15 @@ function buildProjects(parsed, directByRoot, perRoot) {
|
|
|
2899
2900
|
subprojectDir: p.dir,
|
|
2900
2901
|
dependencies: [...(directByProject.get(p.projectKey) ?? [])].sort()
|
|
2901
2902
|
};
|
|
2903
|
+
// A project without a build file of its own is defined by the scripts
|
|
2904
|
+
// declaring its dependencies, e.g. the root build script.
|
|
2905
|
+
const declared = [...new Set([...p.declaredIn.values()].flat())];
|
|
2906
|
+
const files = p.buildFiles.length ? p.buildFiles : declared.length ? declared : p.missingBuildFiles;
|
|
2907
|
+
if (files.length) {
|
|
2908
|
+
entry.manifestFiles = [...files].sort().map(file => ({
|
|
2909
|
+
file
|
|
2910
|
+
}));
|
|
2911
|
+
}
|
|
2902
2912
|
return entry;
|
|
2903
2913
|
});
|
|
2904
2914
|
projects.sort((a, b) => {
|
|
@@ -2908,21 +2918,6 @@ function buildProjects(parsed, directByRoot, perRoot) {
|
|
|
2908
2918
|
});
|
|
2909
2919
|
return projects;
|
|
2910
2920
|
}
|
|
2911
|
-
function unionInto(map, key, add) {
|
|
2912
|
-
if (!add.length) {
|
|
2913
|
-
return;
|
|
2914
|
-
}
|
|
2915
|
-
const acc = map.get(key);
|
|
2916
|
-
if (acc) {
|
|
2917
|
-
for (const f of add) {
|
|
2918
|
-
if (!acc.includes(f)) {
|
|
2919
|
-
acc.push(f);
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
} else {
|
|
2923
|
-
map.set(key, [...add]);
|
|
2924
|
-
}
|
|
2925
|
-
}
|
|
2926
2921
|
function buildClasspathByProject(projects, perRoot) {
|
|
2927
2922
|
const idsByProjectKey = new Map();
|
|
2928
2923
|
for (const {
|
|
@@ -2940,11 +2935,7 @@ function buildClasspathByProject(projects, perRoot) {
|
|
|
2940
2935
|
}
|
|
2941
2936
|
const classpathByProject = new Map();
|
|
2942
2937
|
for (const p of projects) {
|
|
2943
|
-
const key =
|
|
2944
|
-
name: p.name,
|
|
2945
|
-
namespace: p.group,
|
|
2946
|
-
subprojectDir: p.dir
|
|
2947
|
-
});
|
|
2938
|
+
const key = p.projectKey;
|
|
2948
2939
|
let set = classpathByProject.get(key);
|
|
2949
2940
|
if (!set) {
|
|
2950
2941
|
set = new Set();
|
|
@@ -2959,61 +2950,26 @@ function buildClasspathByProject(projects, perRoot) {
|
|
|
2959
2950
|
1: ids
|
|
2960
2951
|
}) => [key, [...ids].sort()]));
|
|
2961
2952
|
}
|
|
2962
|
-
function buildArtifactPaths(finalNodes, projects,
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
if (!coordKey) {
|
|
2971
|
-
continue;
|
|
2972
|
-
}
|
|
2973
|
-
coords.add(coordKey);
|
|
2974
|
-
const pi = projectsByGav.get(gav(c.group, c.name, c.version ?? ''));
|
|
2975
|
-
const sources = (pi?.sources ?? []).filter(fileExists).sort();
|
|
2976
|
-
const targets = [...new Set(pi ? pi.targets : fn.targets)].filter(fileExists).sort();
|
|
2977
|
-
if (sources.length) {
|
|
2978
|
-
sourcesByCoord.set(coordKey, sources);
|
|
2979
|
-
}
|
|
2980
|
-
if (!targets.length) {
|
|
2981
|
-
continue;
|
|
2982
|
-
}
|
|
2983
|
-
targetsByCoord.set(coordKey, targets);
|
|
2984
|
-
const gavKey = mavenCoordinateKey(c.group, c.name, undefined, undefined, c.version);
|
|
2985
|
-
if (gavKey) {
|
|
2986
|
-
const acc = targetsByGav.get(gavKey);
|
|
2987
|
-
if (acc) {
|
|
2988
|
-
for (const f of targets) {
|
|
2989
|
-
if (!acc.includes(f)) {
|
|
2990
|
-
acc.push(f);
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
} else {
|
|
2994
|
-
targetsByGav.set(gavKey, [...targets]);
|
|
2995
|
-
}
|
|
2953
|
+
function buildArtifactPaths(finalNodes, projects, perRoot, fileExists) {
|
|
2954
|
+
const pathsById = new Map();
|
|
2955
|
+
for (const [id, fn] of finalNodes) {
|
|
2956
|
+
if (!fn.project) {
|
|
2957
|
+
pathsById.set(id, {
|
|
2958
|
+
sources: [],
|
|
2959
|
+
targets: [...fn.targets].filter(fileExists).sort()
|
|
2960
|
+
});
|
|
2996
2961
|
}
|
|
2997
2962
|
}
|
|
2998
|
-
// A
|
|
2999
|
-
//
|
|
3000
|
-
// emit first-party module paths here.
|
|
2963
|
+
// A project's own component shares its id, so this also covers dependency
|
|
2964
|
+
// edges onto a sibling project.
|
|
3001
2965
|
for (const p of projects) {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
}
|
|
3006
|
-
coords.add(coordKey);
|
|
3007
|
-
unionInto(sourcesByCoord, coordKey, p.sources.filter(fileExists));
|
|
3008
|
-
const targets = p.targets.filter(fileExists);
|
|
3009
|
-
unionInto(targetsByCoord, coordKey, targets);
|
|
3010
|
-
unionInto(targetsByGav, coordKey, targets);
|
|
2966
|
+
pathsById.set(p.projectKey, {
|
|
2967
|
+
sources: [...new Set(p.sources)].filter(fileExists).sort(),
|
|
2968
|
+
targets: [...new Set(p.targets)].filter(fileExists).sort()
|
|
2969
|
+
});
|
|
3011
2970
|
}
|
|
3012
2971
|
return {
|
|
3013
|
-
|
|
3014
|
-
targetsByGav,
|
|
3015
|
-
sourcesByCoord,
|
|
3016
|
-
coords,
|
|
2972
|
+
pathsById,
|
|
3017
2973
|
classpathByProject: buildClasspathByProject(projects, perRoot)
|
|
3018
2974
|
};
|
|
3019
2975
|
}
|
|
@@ -3185,18 +3141,24 @@ function serializeExcludePathPatterns(paths) {
|
|
|
3185
3141
|
// <tag>\t<field>\t<field>...
|
|
3186
3142
|
//
|
|
3187
3143
|
// meta tool toolVersion javaVersion
|
|
3144
|
+
// buildRoot path (absolute; the facts file's directory)
|
|
3145
|
+
// entry path (file-addressed builds; build-root-relative)
|
|
3188
3146
|
// project projectKey group name version dir
|
|
3189
3147
|
// projectSrc projectKey path (--with-files only)
|
|
3190
3148
|
// projectTgt projectKey path (--with-files only)
|
|
3191
|
-
// projectBuild projectKey path
|
|
3149
|
+
// projectBuild projectKey path [missing] (build-root-relative; `missing`: configured, absent on disk)
|
|
3150
|
+
// declared projectKey group name path (script declaring the dependency; build-root-relative)
|
|
3192
3151
|
// root rootId projectKey config prod(0|1)
|
|
3193
|
-
// node rootId coordId group name version ext classifier direct(0|1)
|
|
3152
|
+
// node rootId coordId group name version ext classifier direct(0|1) project
|
|
3194
3153
|
// edge rootId parentCoordId childCoordId
|
|
3195
3154
|
// file rootId coordId path (--with-files only)
|
|
3196
3155
|
// scanned config
|
|
3197
3156
|
// failure coord detail config
|
|
3198
3157
|
// unscannable config detail
|
|
3199
3158
|
//
|
|
3159
|
+
// `projectKey` is the tool's unique project identity (Maven: GAV; Gradle:
|
|
3160
|
+
// project path; sbt: project id), emitted as the project id; a `node` whose
|
|
3161
|
+
// `project` names one resolves to that build project.
|
|
3200
3162
|
// A `root` is one (subproject, configuration) resolution root; `coordId` is the
|
|
3201
3163
|
// coordinate key (`group:name:ext:classifier:version`, empty segments dropped),
|
|
3202
3164
|
// used opaquely as the per-root node key. Unknown tags are ignored.
|
|
@@ -3225,6 +3187,8 @@ function parseRecords(text) {
|
|
|
3225
3187
|
tool: '',
|
|
3226
3188
|
toolVersion: '',
|
|
3227
3189
|
javaVersion: '',
|
|
3190
|
+
buildRoot: '',
|
|
3191
|
+
entry: '',
|
|
3228
3192
|
projects: new Map(),
|
|
3229
3193
|
roots: new Map(),
|
|
3230
3194
|
scannedConfigs: [],
|
|
@@ -3258,7 +3222,9 @@ function parseRecords(text) {
|
|
|
3258
3222
|
dir: '',
|
|
3259
3223
|
sources: [],
|
|
3260
3224
|
targets: [],
|
|
3261
|
-
buildFiles: []
|
|
3225
|
+
buildFiles: [],
|
|
3226
|
+
missingBuildFiles: [],
|
|
3227
|
+
declaredIn: new Map()
|
|
3262
3228
|
};
|
|
3263
3229
|
result.projects.set(key, p);
|
|
3264
3230
|
}
|
|
@@ -3275,6 +3241,12 @@ function parseRecords(text) {
|
|
|
3275
3241
|
result.toolVersion = f[2] ?? '';
|
|
3276
3242
|
result.javaVersion = f[3] ?? '';
|
|
3277
3243
|
break;
|
|
3244
|
+
case 'buildRoot':
|
|
3245
|
+
result.buildRoot = f[1] ?? '';
|
|
3246
|
+
break;
|
|
3247
|
+
case 'entry':
|
|
3248
|
+
result.entry = f[1] ?? '';
|
|
3249
|
+
break;
|
|
3278
3250
|
case 'project':
|
|
3279
3251
|
{
|
|
3280
3252
|
const p = project(f[1] ?? '');
|
|
@@ -3296,7 +3268,17 @@ function parseRecords(text) {
|
|
|
3296
3268
|
break;
|
|
3297
3269
|
case 'projectBuild':
|
|
3298
3270
|
if (f[2]) {
|
|
3299
|
-
project(f[1] ?? '')
|
|
3271
|
+
const p = project(f[1] ?? '');
|
|
3272
|
+
(f[3] === 'missing' ? p.missingBuildFiles : p.buildFiles).push(f[2]);
|
|
3273
|
+
}
|
|
3274
|
+
break;
|
|
3275
|
+
case 'declared':
|
|
3276
|
+
if (f[4]) {
|
|
3277
|
+
const {
|
|
3278
|
+
declaredIn
|
|
3279
|
+
} = project(f[1] ?? '');
|
|
3280
|
+
const ga = `${f[2] ?? ''}:${f[3] ?? ''}`;
|
|
3281
|
+
declaredIn.set(ga, [...(declaredIn.get(ga) ?? []), f[4]]);
|
|
3300
3282
|
}
|
|
3301
3283
|
break;
|
|
3302
3284
|
case 'root':
|
|
@@ -3321,6 +3303,7 @@ function parseRecords(text) {
|
|
|
3321
3303
|
classifier: f[7] ?? ''
|
|
3322
3304
|
},
|
|
3323
3305
|
direct: bool(f[8]),
|
|
3306
|
+
project: f[9] ?? '',
|
|
3324
3307
|
targets: []
|
|
3325
3308
|
});
|
|
3326
3309
|
break;
|
|
@@ -3428,16 +3411,22 @@ async function writeSbtPlugin(globalBase, sourceFilename, destFilename) {
|
|
|
3428
3411
|
});
|
|
3429
3412
|
await fs$1.promises.writeFile(path.join(pluginsDir, destFilename), src);
|
|
3430
3413
|
}
|
|
3431
|
-
async function assembleFromRecords(out, recordsFile) {
|
|
3414
|
+
async function assembleFromRecords(tool, out, recordsFile) {
|
|
3432
3415
|
const text = fs$1.existsSync(recordsFile) ? await fs$1.promises.readFile(recordsFile, 'utf8') : '';
|
|
3416
|
+
const parsed = parseRecords(text);
|
|
3417
|
+
const factsFileName = socketFactsFileName(tool, parsed.entry || undefined);
|
|
3433
3418
|
const {
|
|
3434
3419
|
artifactPaths,
|
|
3435
3420
|
facts,
|
|
3436
3421
|
report
|
|
3437
|
-
} = assembleFacts(
|
|
3422
|
+
} = assembleFacts(parsed, {
|
|
3423
|
+
factsFileName
|
|
3424
|
+
});
|
|
3438
3425
|
return {
|
|
3426
|
+
buildRoot: parsed.buildRoot || undefined,
|
|
3439
3427
|
code: out.code,
|
|
3440
3428
|
facts,
|
|
3429
|
+
factsFileName,
|
|
3441
3430
|
report,
|
|
3442
3431
|
artifactPaths,
|
|
3443
3432
|
stderr: out.stderr,
|
|
@@ -3510,7 +3499,7 @@ async function invokeGradle(opts, initScriptFilename, task, tmpDirPrefix) {
|
|
|
3510
3499
|
// resolvedConfiguration API and shared accumulator aren't cache-safe.
|
|
3511
3500
|
const args = ['--init-script', initScript, '-Dorg.gradle.configuration-cache=false', `-Psocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-P'), ...(opts.toolOpts ?? []), task, '--no-daemon', '--console=plain'];
|
|
3512
3501
|
const out = await runNeverThrow(bin, args, opts);
|
|
3513
|
-
return await assembleFromRecords(out, recordsFile);
|
|
3502
|
+
return await assembleFromRecords('gradle', out, recordsFile);
|
|
3514
3503
|
});
|
|
3515
3504
|
}
|
|
3516
3505
|
async function runGradle(opts) {
|
|
@@ -3536,7 +3525,7 @@ async function invokeSbtIn(globalBase, opts, pluginSourceFilename, pluginDestFil
|
|
|
3536
3525
|
const javaHomeOpt = javaHome && !(opts.toolOpts ?? []).includes('--java-home') ? ['--java-home', javaHome] : [];
|
|
3537
3526
|
const args = [...javaHomeOpt, ...props, ...(opts.toolOpts ?? []), '--batch', task];
|
|
3538
3527
|
const out = await runNeverThrow(bin, args, opts);
|
|
3539
|
-
return await assembleFromRecords(out, recordsFile);
|
|
3528
|
+
return await assembleFromRecords('sbt', out, recordsFile);
|
|
3540
3529
|
}
|
|
3541
3530
|
async function runSbt(opts) {
|
|
3542
3531
|
const run = globalBase => invokeSbtIn(globalBase, opts, 'socket-facts.plugin.scala', SBT_FACTS_PLUGIN_FILENAME, FACTS_TASK);
|
|
@@ -3563,7 +3552,7 @@ async function invokeMaven(opts, coanaTask, tmpDirPrefix) {
|
|
|
3563
3552
|
const props = [`-Dmaven.ext.class.path=${jarPath}`, `-Dcoana.task=${coanaTask}`, `-Dsocket.recordsFile=${recordsFile}`, ...commonProps(opts, '-D')];
|
|
3564
3553
|
const args = [...props, ...(opts.toolOpts ?? []), '--batch-mode', 'validate'];
|
|
3565
3554
|
const out = await runNeverThrow(bin, args, opts);
|
|
3566
|
-
return await assembleFromRecords(out, recordsFile);
|
|
3555
|
+
return await assembleFromRecords('maven', out, recordsFile);
|
|
3567
3556
|
});
|
|
3568
3557
|
}
|
|
3569
3558
|
async function runMaven(opts) {
|
|
@@ -3586,8 +3575,9 @@ function tailBuildOutput(stdout, stderr) {
|
|
|
3586
3575
|
// `process.exitCode`, which can already be non-zero for an unrelated reason.
|
|
3587
3576
|
|
|
3588
3577
|
// Runs the bundled build-tool resolution script for a JVM project and writes
|
|
3589
|
-
//
|
|
3590
|
-
// resolved artifact paths into
|
|
3578
|
+
// its `<entry>.socket.facts.json` (see socketFactsFileName). `withFiles`
|
|
3579
|
+
// (reachability only) additionally folds resolved artifact paths into
|
|
3580
|
+
// `sidecarAcc`. A blocking resolution failure sets
|
|
3591
3581
|
// a non-zero exit code and returns (matching the `--pom` generator) unless
|
|
3592
3582
|
// `ignoreUnresolved`; a crashed build — a process failure, not an unresolved
|
|
3593
3583
|
// dependency — always fails.
|
|
@@ -3606,7 +3596,6 @@ async function runManifestFacts({
|
|
|
3606
3596
|
verbose,
|
|
3607
3597
|
withFiles
|
|
3608
3598
|
}) {
|
|
3609
|
-
const factsPath = path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
|
|
3610
3599
|
let resolvedJavaHome;
|
|
3611
3600
|
if (javaHome) {
|
|
3612
3601
|
const expanded = expandEnvVarRefs(javaHome);
|
|
@@ -3671,8 +3660,10 @@ async function runManifestFacts({
|
|
|
3671
3660
|
}
|
|
3672
3661
|
const {
|
|
3673
3662
|
artifactPaths,
|
|
3663
|
+
buildRoot,
|
|
3674
3664
|
code,
|
|
3675
3665
|
facts,
|
|
3666
|
+
factsFileName,
|
|
3676
3667
|
report,
|
|
3677
3668
|
stderr,
|
|
3678
3669
|
stdout
|
|
@@ -3731,6 +3722,20 @@ async function runManifestFacts({
|
|
|
3731
3722
|
logger.logger.warn(`No resolvable ${ecosystem} dependencies found; nothing to upload.`);
|
|
3732
3723
|
return;
|
|
3733
3724
|
}
|
|
3725
|
+
if (!buildRoot || !factsFileName) {
|
|
3726
|
+
process.exitCode = 1;
|
|
3727
|
+
logger.logger.fail(`The ${ecosystem} build did not report its ${buildRoot ? 'entry build file' : 'root directory'}, so its Socket facts file cannot be placed.`);
|
|
3728
|
+
return null;
|
|
3729
|
+
}
|
|
3730
|
+
// Every path in the facts is relative to the build root, which `-f`/`-p`
|
|
3731
|
+
// can move away from cwd.
|
|
3732
|
+
const factsPath = path.join(buildRoot, factsFileName);
|
|
3733
|
+
// Not a name producers write, so a copy here is stale and would be uploaded
|
|
3734
|
+
// alongside the new file.
|
|
3735
|
+
const legacyFactsPath = path.join(buildRoot, constants.default.DOT_SOCKET_DOT_FACTS_JSON);
|
|
3736
|
+
if (fs$1.existsSync(legacyFactsPath)) {
|
|
3737
|
+
logger.logger.warn(`Found \`${legacyFactsPath}\`, which is uploaded alongside \`${factsFileName}\`. Delete it if an earlier \`socket manifest\` run left it behind.`);
|
|
3738
|
+
}
|
|
3734
3739
|
const socketCliVersion = constants.default.ENV.INLINED_SOCKET_CLI_VERSION;
|
|
3735
3740
|
if (facts.metadata && socketCliVersion) {
|
|
3736
3741
|
facts.metadata.socketCliVersion = socketCliVersion;
|
|
@@ -3876,8 +3881,11 @@ async function runEcosystemCandidates({
|
|
|
3876
3881
|
continue;
|
|
3877
3882
|
}
|
|
3878
3883
|
covered.add(dir);
|
|
3884
|
+
// `-f`/`-p` can root the reactor away from `dir`.
|
|
3885
|
+
// eslint-disable-next-line no-await-in-loop
|
|
3886
|
+
const buildRoot = await utils.realpathOrResolved(path.dirname(result.factsPath));
|
|
3879
3887
|
// eslint-disable-next-line no-await-in-loop
|
|
3880
|
-
const resolvedSubprojectDirs = await Promise.all(result.projects.map(project => utils.realpathOrResolved(path.resolve(
|
|
3888
|
+
const resolvedSubprojectDirs = await Promise.all(result.projects.map(project => utils.realpathOrResolved(path.resolve(buildRoot, project.subprojectDir))));
|
|
3881
3889
|
for (const subprojectDir of resolvedSubprojectDirs) {
|
|
3882
3890
|
// Only a genuinely nested member (a descendant of this reactor's own
|
|
3883
3891
|
// directory) has no independent existence worth its own standalone
|
|
@@ -3890,7 +3898,7 @@ async function runEcosystemCandidates({
|
|
|
3890
3898
|
// meaningful data point, not a redundant one. Never suppress its own
|
|
3891
3899
|
// build-root invocation, regardless of which reactor(s) also
|
|
3892
3900
|
// incorporate it or the order candidates happen to be discovered in.
|
|
3893
|
-
if (subprojectDir.startsWith(`${
|
|
3901
|
+
if (subprojectDir === buildRoot || subprojectDir.startsWith(`${buildRoot}${path.sep}`)) {
|
|
3894
3902
|
covered.add(subprojectDir);
|
|
3895
3903
|
}
|
|
3896
3904
|
}
|
|
@@ -3905,7 +3913,7 @@ async function runEcosystemCandidates({
|
|
|
3905
3913
|
return outcomes;
|
|
3906
3914
|
}
|
|
3907
3915
|
|
|
3908
|
-
// Generates one
|
|
3916
|
+
// Generates one Socket facts file per independent gradle/sbt/maven build
|
|
3909
3917
|
// root under `cwd`. Coverage is tracked per ecosystem via the facts SBOM's
|
|
3910
3918
|
// own projects[].subprojectDir, not by pruning the whole discovered subtree,
|
|
3911
3919
|
// so an unrelated nested project a reactor doesn't declare still gets its
|
|
@@ -6408,7 +6416,8 @@ async function extractBazelToMaven(opts) {
|
|
|
6408
6416
|
}
|
|
6409
6417
|
}
|
|
6410
6418
|
|
|
6411
|
-
// Generates
|
|
6419
|
+
// Generates `gradle.socket.facts.json` for a Gradle project via the bundled
|
|
6420
|
+
// init script.
|
|
6412
6421
|
async function convertGradleToFacts({
|
|
6413
6422
|
bin,
|
|
6414
6423
|
cwd,
|
|
@@ -6438,7 +6447,8 @@ async function convertGradleToFacts({
|
|
|
6438
6447
|
});
|
|
6439
6448
|
}
|
|
6440
6449
|
|
|
6441
|
-
// Generates
|
|
6450
|
+
// Generates `pom.xml.socket.facts.json` (named after the POM Maven runs on)
|
|
6451
|
+
// for a Maven project via the bundled extension.
|
|
6442
6452
|
async function convertMavenToFacts({
|
|
6443
6453
|
bin,
|
|
6444
6454
|
cwd,
|
|
@@ -6468,7 +6478,7 @@ async function convertMavenToFacts({
|
|
|
6468
6478
|
});
|
|
6469
6479
|
}
|
|
6470
6480
|
|
|
6471
|
-
// Generates
|
|
6481
|
+
// Generates `sbt.socket.facts.json` for an sbt project via the bundled sbt plugin.
|
|
6472
6482
|
// sbt 0.13/early 1.x can't run on modern JDKs — pass a compatible JDK via
|
|
6473
6483
|
// `--sbt-opts "--java-home <path>"` or `JAVA_HOME`.
|
|
6474
6484
|
async function convertSbtToFacts({
|
|
@@ -7231,7 +7241,7 @@ async function createNewScan({
|
|
|
7231
7241
|
});
|
|
7232
7242
|
if (reach.dynamicSbomInference) {
|
|
7233
7243
|
// Already generated recursively above; resolving cwd's own build
|
|
7234
|
-
// root a second time would race on the same
|
|
7244
|
+
// root a second time would race on the same facts file.
|
|
7235
7245
|
detected.gradle = false;
|
|
7236
7246
|
detected.sbt = false;
|
|
7237
7247
|
detected.maven = false;
|
|
@@ -7365,12 +7375,12 @@ async function createNewScan({
|
|
|
7365
7375
|
logger.logger.success('Reachability analysis completed successfully');
|
|
7366
7376
|
reachabilityReport = reachResult.data?.reachabilityReport;
|
|
7367
7377
|
|
|
7368
|
-
//
|
|
7369
|
-
//
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7378
|
+
// Mirror the SBOM inputs Coana analyzed; otherwise its fresh report
|
|
7379
|
+
// (appended below) supersedes every facts file.
|
|
7380
|
+
const pathsForScan = reach.reachUseOnlyPregeneratedSboms ? filterToPregeneratedSboms(packagePaths, supportedFiles).filter(p => !utils.isReachabilityReportPath(p, {
|
|
7381
|
+
cwd,
|
|
7382
|
+
outputPath: constants.default.DOT_SOCKET_DOT_FACTS_JSON
|
|
7383
|
+
})) : packagePaths.filter(p => !utils.isSocketFactsFile(p));
|
|
7374
7384
|
|
|
7375
7385
|
// Append coana's reachability report, but not twice: a pre-generated facts
|
|
7376
7386
|
// input can resolve to the same path coana wrote its report to.
|
|
@@ -7419,17 +7429,8 @@ async function createNewScan({
|
|
|
7419
7429
|
logger.logger.warn('Reachability analysis ran but no full application reachability scan ID was found; skipping reachability finalize. The scan was created but its reachability report was not linked.');
|
|
7420
7430
|
}
|
|
7421
7431
|
|
|
7422
|
-
//
|
|
7423
|
-
//
|
|
7424
|
-
// scans leave the file in place for debugging. Producer-written files
|
|
7425
|
-
// (e.g. from `socket manifest gradle --facts`) are NOT touched here —
|
|
7426
|
-
// those are user-owned input that the user can clean up themselves; in
|
|
7427
|
-
// the --reach path coana overwrites that file with its enriched output
|
|
7428
|
-
// anyway, so it's the same path that gets removed. `--reach-retain-facts-file`
|
|
7429
|
-
// opts out of this cleanup so the report can be inspected; the user is then
|
|
7430
|
-
// responsible for deleting it before the next full application reachability
|
|
7431
|
-
// scan (a stale file is picked up as pre-generated input and would make those
|
|
7432
|
-
// results unreliable).
|
|
7432
|
+
// A scan without --reach would upload a leftover report as an SBOM with
|
|
7433
|
+
// stale reachability results; a failed scan keeps it for debugging.
|
|
7433
7434
|
if (fullScanCResult.ok && scanId && reachabilityReport && !reach.reachRetainFactsFile) {
|
|
7434
7435
|
try {
|
|
7435
7436
|
await fs.unlink(path.resolve(cwd, reachabilityReport));
|
|
@@ -9006,9 +9007,6 @@ async function discoverGhsaIds(orgSlug, tarHash, options) {
|
|
|
9006
9007
|
}).catch(() => {});
|
|
9007
9008
|
}
|
|
9008
9009
|
}
|
|
9009
|
-
function isFactsFile(filepath) {
|
|
9010
|
-
return path.basename(filepath).toLowerCase() === constants.DOT_SOCKET_DOT_FACTS_JSON;
|
|
9011
|
-
}
|
|
9012
9010
|
async function gitWorkingTreeChanges(cwd) {
|
|
9013
9011
|
const {
|
|
9014
9012
|
0: modifiedCResult,
|
|
@@ -9129,16 +9127,15 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
|
|
|
9129
9127
|
cwd
|
|
9130
9128
|
});
|
|
9131
9129
|
const scanFilepaths = await findScanFilepaths();
|
|
9132
|
-
//
|
|
9133
|
-
|
|
9134
|
-
const factsFiles = scanFilepaths.filter(isFactsFile);
|
|
9130
|
+
// Facts files are analysis artifacts and must be removed before re-running fix.
|
|
9131
|
+
const factsFiles = scanFilepaths.filter(utils.isSocketFactsFile);
|
|
9135
9132
|
if (factsFiles.length) {
|
|
9136
9133
|
if (!silence) {
|
|
9137
9134
|
spinner?.stop();
|
|
9138
9135
|
}
|
|
9139
9136
|
return {
|
|
9140
9137
|
ok: false,
|
|
9141
|
-
message:
|
|
9138
|
+
message: 'Found Socket facts files in manifest files',
|
|
9142
9139
|
cause: `Delete the following ${words.pluralize('file', factsFiles.length)} before running socket fix again:\n` + factsFiles.map(p => ` - ${p}`).join('\n')
|
|
9143
9140
|
};
|
|
9144
9141
|
}
|
|
@@ -9155,7 +9152,7 @@ async function coanaFixWithFacts(fixConfig, factsSlot) {
|
|
|
9155
9152
|
});
|
|
9156
9153
|
} catch (e) {
|
|
9157
9154
|
// A failed build root aborts inference after others wrote their facts.
|
|
9158
|
-
const partial = (await findScanFilepaths()).filter(
|
|
9155
|
+
const partial = (await findScanFilepaths()).filter(utils.isSocketFactsFile);
|
|
9159
9156
|
await Promise.all(partial.map(p => fs$1.promises.rm(p, {
|
|
9160
9157
|
force: true
|
|
9161
9158
|
})));
|
|
@@ -9924,7 +9921,7 @@ const reachabilityFlags = {
|
|
|
9924
9921
|
reachRetainFactsFile: {
|
|
9925
9922
|
type: 'boolean',
|
|
9926
9923
|
default: false,
|
|
9927
|
-
description: 'Keep the `.socket.facts.json` reachability report that the analysis writes to the scan directory instead of deleting it after a successful scan.
|
|
9924
|
+
description: 'Keep the `.socket.facts.json` reachability report that the analysis writes to the scan directory instead of deleting it after a successful scan. Delete it once inspected: later reachability scans ignore it, but a scan without --reach uploads it as an SBOM carrying stale reachability results.'
|
|
9928
9925
|
},
|
|
9929
9926
|
reachSkipCache: {
|
|
9930
9927
|
type: 'boolean',
|
|
@@ -12931,7 +12928,10 @@ const config$d = {
|
|
|
12931
12928
|
$ ${command} [options] [CWD=.]
|
|
12932
12929
|
|
|
12933
12930
|
Recursively walks CWD, discovers independent gradle, sbt, and maven build
|
|
12934
|
-
roots, and generates a Socket facts SBOM
|
|
12931
|
+
roots, and generates a Socket facts SBOM for each
|
|
12932
|
+
(pom.xml.socket.facts.json, gradle.socket.facts.json, or
|
|
12933
|
+
sbt.socket.facts.json, so builds sharing a directory never overwrite each
|
|
12934
|
+
other),
|
|
12935
12935
|
skipping subproject/reactor-module directories a parent build root already
|
|
12936
12936
|
covers. Unlike \`socket manifest auto\`, this looks beyond CWD itself.
|
|
12937
12937
|
|
|
@@ -13020,11 +13020,11 @@ const config$c = {
|
|
|
13020
13020
|
},
|
|
13021
13021
|
facts: {
|
|
13022
13022
|
type: 'boolean',
|
|
13023
|
-
description: 'Emit a Socket facts JSON file (
|
|
13023
|
+
description: 'Emit a Socket facts JSON file (`gradle.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
|
|
13024
13024
|
},
|
|
13025
13025
|
pom: {
|
|
13026
13026
|
type: 'boolean',
|
|
13027
|
-
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (
|
|
13027
|
+
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (`gradle.socket.facts.json`)'
|
|
13028
13028
|
},
|
|
13029
13029
|
includeConfigs: {
|
|
13030
13030
|
type: 'string',
|
|
@@ -13055,9 +13055,9 @@ const config$c = {
|
|
|
13055
13055
|
Options
|
|
13056
13056
|
${utils.getFlagListOutput(config.flags)}
|
|
13057
13057
|
|
|
13058
|
-
By default, emits a single
|
|
13059
|
-
dependency graph of the whole build, using gradle (preferably your
|
|
13060
|
-
\`gradlew\`). An unresolved dependency is a fatal error. You can pass
|
|
13058
|
+
By default, emits a single \`gradle.socket.facts.json\` describing the
|
|
13059
|
+
resolved dependency graph of the whole build, using gradle (preferably your
|
|
13060
|
+
local \`gradlew\`). An unresolved dependency is a fatal error. You can pass
|
|
13061
13061
|
--include-configs / --exclude-configs (comma-separated glob patterns) to
|
|
13062
13062
|
control which configurations are resolved (e.g.
|
|
13063
13063
|
--include-configs=\`*CompileClasspath,*RuntimeClasspath\`), and
|
|
@@ -13274,11 +13274,11 @@ const config$b = {
|
|
|
13274
13274
|
},
|
|
13275
13275
|
facts: {
|
|
13276
13276
|
type: 'boolean',
|
|
13277
|
-
description: 'Emit a Socket facts JSON file (
|
|
13277
|
+
description: 'Emit a Socket facts JSON file (`gradle.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
|
|
13278
13278
|
},
|
|
13279
13279
|
pom: {
|
|
13280
13280
|
type: 'boolean',
|
|
13281
|
-
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (
|
|
13281
|
+
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (`gradle.socket.facts.json`)'
|
|
13282
13282
|
},
|
|
13283
13283
|
includeConfigs: {
|
|
13284
13284
|
type: 'string',
|
|
@@ -13309,9 +13309,9 @@ const config$b = {
|
|
|
13309
13309
|
Options
|
|
13310
13310
|
${utils.getFlagListOutput(config.flags)}
|
|
13311
13311
|
|
|
13312
|
-
By default, emits a single
|
|
13313
|
-
dependency graph of the whole build, using gradle (preferably your
|
|
13314
|
-
\`gradlew\`). An unresolved dependency is a fatal error. You can pass
|
|
13312
|
+
By default, emits a single \`gradle.socket.facts.json\` describing the
|
|
13313
|
+
resolved dependency graph of the whole build, using gradle (preferably your
|
|
13314
|
+
local \`gradlew\`). An unresolved dependency is a fatal error. You can pass
|
|
13315
13315
|
--include-configs / --exclude-configs (comma-separated glob patterns) to
|
|
13316
13316
|
control which configurations are resolved (e.g.
|
|
13317
13317
|
--include-configs=\`*CompileClasspath,*RuntimeClasspath\`), and
|
|
@@ -13548,8 +13548,8 @@ const config$a = {
|
|
|
13548
13548
|
Options
|
|
13549
13549
|
${utils.getFlagListOutput(config.flags)}
|
|
13550
13550
|
|
|
13551
|
-
Emits a single
|
|
13552
|
-
graph of your Maven project, using maven (\`mvn\` on PATH by default). It
|
|
13551
|
+
Emits a single \`pom.xml.socket.facts.json\` (named after the POM Maven
|
|
13552
|
+
runs on) describing the resolved dependency graph of your Maven project, using maven (\`mvn\` on PATH by default). It
|
|
13553
13553
|
reads dependency metadata only and never downloads artifacts; an unresolved
|
|
13554
13554
|
dependency is a fatal error. You can pass --include-configs /
|
|
13555
13555
|
--exclude-configs (comma-separated glob patterns) to control which Maven
|
|
@@ -13712,11 +13712,11 @@ const config$9 = {
|
|
|
13712
13712
|
},
|
|
13713
13713
|
facts: {
|
|
13714
13714
|
type: 'boolean',
|
|
13715
|
-
description: 'Emit a Socket facts JSON file (
|
|
13715
|
+
description: 'Emit a Socket facts JSON file (`sbt.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
|
|
13716
13716
|
},
|
|
13717
13717
|
pom: {
|
|
13718
13718
|
type: 'boolean',
|
|
13719
|
-
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (
|
|
13719
|
+
description: 'Generate `pom.xml` manifest file(s) instead of the default Socket facts file (`sbt.socket.facts.json`)'
|
|
13720
13720
|
},
|
|
13721
13721
|
includeConfigs: {
|
|
13722
13722
|
type: 'string',
|
|
@@ -13733,7 +13733,7 @@ const config$9 = {
|
|
|
13733
13733
|
},
|
|
13734
13734
|
out: {
|
|
13735
13735
|
type: 'string',
|
|
13736
|
-
description: 'Only with --pom: path of the output `pom.xml`, see also --stdout. Does not apply when generating Socket facts (always written to the project root as
|
|
13736
|
+
description: 'Only with --pom: path of the output `pom.xml`, see also --stdout. Does not apply when generating Socket facts (always written to the project root as `sbt.socket.facts.json`)'
|
|
13737
13737
|
},
|
|
13738
13738
|
stdout: {
|
|
13739
13739
|
type: 'boolean',
|
|
@@ -13755,8 +13755,8 @@ const config$9 = {
|
|
|
13755
13755
|
Options
|
|
13756
13756
|
${utils.getFlagListOutput(config.flags)}
|
|
13757
13757
|
|
|
13758
|
-
By default, emits a single
|
|
13759
|
-
dependency graph of the whole build. It reads dependency metadata only and
|
|
13758
|
+
By default, emits a single \`sbt.socket.facts.json\` describing the
|
|
13759
|
+
resolved dependency graph of the whole build. It reads dependency metadata only and
|
|
13760
13760
|
never downloads artifacts; an unresolved dependency is a fatal error. You
|
|
13761
13761
|
can pass --include-configs / --exclude-configs (comma-separated glob
|
|
13762
13762
|
patterns) to control which sbt configurations are resolved (e.g.
|
|
@@ -13937,7 +13937,7 @@ async function run$A(argv, importMeta, {
|
|
|
13937
13937
|
// would the file name be?
|
|
13938
13938
|
|
|
13939
13939
|
// --out / --stdout only affect the pom path. Socket facts are always written
|
|
13940
|
-
// to the project root as
|
|
13940
|
+
// to the project root as `sbt.socket.facts.json` so that `socket scan create`
|
|
13941
13941
|
// picks them up, so reject these flags in facts mode rather than silently
|
|
13942
13942
|
// ignoring an explicitly-passed output location.
|
|
13943
13943
|
const wasValidInput = utils.checkCommandInput(outputKind, {
|
|
@@ -13948,7 +13948,7 @@ async function run$A(argv, importMeta, {
|
|
|
13948
13948
|
}, {
|
|
13949
13949
|
nook: true,
|
|
13950
13950
|
test: !(facts && (cli.flags['out'] !== undefined || cli.flags['stdout'] !== undefined)),
|
|
13951
|
-
message: 'The `--out` and `--stdout` options only apply with `--pom`; Socket facts are always written to the project root as
|
|
13951
|
+
message: 'The `--out` and `--stdout` options only apply with `--pom`; Socket facts are always written to the project root as `sbt.socket.facts.json`',
|
|
13952
13952
|
fail: 'remove --out/--stdout, or pass --pom'
|
|
13953
13953
|
});
|
|
13954
13954
|
if (!wasValidInput) {
|
|
@@ -14541,7 +14541,7 @@ async function askForFactsFlag(current) {
|
|
|
14541
14541
|
choices: [{
|
|
14542
14542
|
name: 'Socket facts (default)',
|
|
14543
14543
|
value: 'yes',
|
|
14544
|
-
description: 'Generate a
|
|
14544
|
+
description: 'Generate a Socket facts file (*.socket.facts.json) describing the resolved dependency graph'
|
|
14545
14545
|
}, {
|
|
14546
14546
|
name: 'pom.xml',
|
|
14547
14547
|
value: 'no',
|
|
@@ -19953,6 +19953,13 @@ const generalFlags$1 = {
|
|
|
19953
19953
|
description: 'Scan each TARGET directory as a uv project, using the versions pinned in its uv.lock or its workspace root uv.lock. Uploads a CycloneDX dependency graph per TARGET in place of manifest discovery, including all extras and dependency groups. Requires uv on PATH.'
|
|
19954
19954
|
}
|
|
19955
19955
|
};
|
|
19956
|
+
function hasSocketFactsFileIn(dir) {
|
|
19957
|
+
try {
|
|
19958
|
+
return fs$1.readdirSync(dir).some(utils.isSocketFactsFile);
|
|
19959
|
+
} catch {
|
|
19960
|
+
return false;
|
|
19961
|
+
}
|
|
19962
|
+
}
|
|
19956
19963
|
const cmdScanCreate = {
|
|
19957
19964
|
description: description$c,
|
|
19958
19965
|
hidden: hidden$a,
|
|
@@ -20174,12 +20181,12 @@ async function run$d(argv, importMeta, {
|
|
|
20174
20181
|
}
|
|
20175
20182
|
}
|
|
20176
20183
|
const detected = await detectManifestActions(sockJson, cwd);
|
|
20177
|
-
// Suppress the --auto-manifest suggestion when a
|
|
20184
|
+
// Suppress the --auto-manifest suggestion when a Socket facts file is
|
|
20178
20185
|
// already present at cwd. That file is the output of `socket manifest auto`
|
|
20179
20186
|
// (and `--facts` mode of the per-ecosystem manifest commands), so suggesting
|
|
20180
20187
|
// to regenerate it would be misleading; the manifest data is already there
|
|
20181
20188
|
// and will be picked up by the scan.
|
|
20182
|
-
const hasFactsFile =
|
|
20189
|
+
const hasFactsFile = hasSocketFactsFileIn(cwd);
|
|
20183
20190
|
if (detected.count > 0 && !autoManifest && !dynamicSbomInference && !uvMembers && !hasFactsFile) {
|
|
20184
20191
|
logger.logger.info(`Detected ${detected.count} manifest targets we could try to generate. Please set the --auto-manifest flag if you want to include languages covered by \`socket manifest auto\` in the Scan.`);
|
|
20185
20192
|
}
|
|
@@ -24498,5 +24505,5 @@ process.on('unhandledRejection', async (reason, promise) => {
|
|
|
24498
24505
|
// eslint-disable-next-line n/no-process-exit
|
|
24499
24506
|
process.exit(1);
|
|
24500
24507
|
});
|
|
24501
|
-
//# debugId=
|
|
24508
|
+
//# debugId=679b39e8-2ace-4602-9ad0-c5cf11bec9e3
|
|
24502
24509
|
//# sourceMappingURL=cli.js.map
|