@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +5 -5
  3. package/dist/cli.js +230 -223
  4. package/dist/cli.js.map +1 -1
  5. package/dist/constants.js +4 -4
  6. package/dist/constants.js.map +1 -1
  7. package/dist/manifest-scripts/maven-extension/coana-maven-extension.jar +0 -0
  8. package/dist/manifest-scripts/socket-facts.init.gradle +100 -18
  9. package/dist/manifest-scripts/socket-facts.plugin.scala +30 -3
  10. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  11. package/dist/types/commands/fix/coana-fix.d.mts.map +1 -1
  12. package/dist/types/commands/manifest/cmd-manifest-dynamic-sbom-inference.d.mts.map +1 -1
  13. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts +2 -1
  14. package/dist/types/commands/manifest/convert-gradle-to-facts.d.mts.map +1 -1
  15. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts +2 -1
  16. package/dist/types/commands/manifest/convert-maven-to-facts.d.mts.map +1 -1
  17. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts +1 -1
  18. package/dist/types/commands/manifest/convert-sbt-to-facts.d.mts.map +1 -1
  19. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts +1 -1
  20. package/dist/types/commands/manifest/generate-recursive-manifests.d.mts.map +1 -1
  21. package/dist/types/commands/manifest/run-manifest-facts.d.mts +3 -2
  22. package/dist/types/commands/manifest/run-manifest-facts.d.mts.map +1 -1
  23. package/dist/types/commands/manifest/scripts/assemble.d.mts +4 -1
  24. package/dist/types/commands/manifest/scripts/assemble.d.mts.map +1 -1
  25. package/dist/types/commands/manifest/scripts/build-tool.d.mts +2 -0
  26. package/dist/types/commands/manifest/scripts/build-tool.d.mts.map +1 -1
  27. package/dist/types/commands/manifest/scripts/facts.d.mts +18 -16
  28. package/dist/types/commands/manifest/scripts/facts.d.mts.map +1 -1
  29. package/dist/types/commands/manifest/scripts/records.d.mts +20 -2
  30. package/dist/types/commands/manifest/scripts/records.d.mts.map +1 -1
  31. package/dist/types/commands/manifest/scripts/run.d.mts +4 -0
  32. package/dist/types/commands/manifest/scripts/run.d.mts.map +1 -1
  33. package/dist/types/commands/manifest/scripts/sidecar.d.mts +6 -8
  34. package/dist/types/commands/manifest/scripts/sidecar.d.mts.map +1 -1
  35. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  36. package/dist/types/commands/scan/handle-create-new-scan.d.mts.map +1 -1
  37. package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
  38. package/dist/types/utils/coana.d.mts +10 -0
  39. package/dist/types/utils/coana.d.mts.map +1 -1
  40. package/dist/utils.js +25 -1
  41. package/dist/utils.js.map +1 -1
  42. 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
- // `.socket.facts.json` file whose own projects[]/components[] these entries
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
- // `targets`/`sources` present (possibly `[]`) means resolution was attempted
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 coordKey = mavenCoordinateKey(entry.namespace, entry.name, entry.qualifiers?.['ext'], entry.qualifiers?.['classifier'], entry.version);
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: [...(artifactPaths.targetsByCoord.get(coordKey) ?? [])].sort(),
1863
- sources: [...(artifactPaths.sourcesByCoord.get(coordKey) ?? [])].sort()
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
- // A second call for the same factsFile (a dual-marker directory where two
1881
- // build tools both target it) overwrites rather than merges, matching the
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(projectClasspathKey(proj)) ?? [])]
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
- // NOTE: previously stripped any `.socket.facts.json` from packagePaths
1992
- // here to avoid uploading leftover post-reachability output. With the
1993
- // producer flow (`socket manifest gradle --facts`) those files are
1994
- // legitimate INPUT to compute-artifacts, so we now upload them. Stale
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
- } = mergeByCoordinate(perRoot);
2679
+ } = mergeById(perRoot);
2697
2680
  const tool = parsed.tool || 'gradle';
2698
- const projectsByGav = new Map();
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()], projectsByGav, perRoot, fileExists)
2702
+ artifactPaths: buildArtifactPaths(finalNodes, [...parsed.projects.values()], perRoot, fileExists)
2724
2703
  };
2725
2704
  }
2726
- function gav(group, name, version) {
2727
- return `${group}:${name}:${version}`;
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 childrenByParent = new Map();
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
- let set = childrenByParent.get(p);
2738
- if (!set) {
2739
- set = new Set();
2740
- childrenByParent.set(p, set);
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 coordinate across every resolution root; which
2763
- // coordinates belong to which subproject is kept separately (classpathByProject)
2764
- // for reachability, which needs each subproject's exact classpath.
2765
- function mergeByCoordinate(perRoot) {
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 buildManifestFilesByCoord(parsed, directByRoot, perRoot) {
2806
+ function buildManifestFilesById(parsed, directByRoot, perRoot, factsFileName) {
2812
2807
  const buildFilesByCoord = new Map();
2813
2808
  for (const [rootId, ids] of directByRoot) {
2814
- const projectKey = perRoot.get(rootId)?.projectKey ?? '';
2815
- const buildFiles = parsed.projects.get(projectKey)?.buildFiles ?? [];
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
- for (const f of buildFiles) {
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, [constants.default.DOT_SOCKET_DOT_FACTS_JSON, ...[...buildFiles].sort()].map(file => ({
2830
+ }) => [id, [...(factsFileName ? [factsFileName] : []), ...[...buildFiles].sort()].map(file => ({
2831
2831
  file
2832
2832
  }))]));
2833
2833
  }
2834
- function buildComponents(finalNodes, projectsByGav, manifestFilesByCoord) {
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 (projectsByGav.has(gav(c.group, c.name, c.version ?? ''))) {
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 = manifestFilesByCoord.get(id);
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 = projectClasspathKey({
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, projectsByGav, perRoot, fileExists) {
2963
- const targetsByCoord = new Map();
2964
- const targetsByGav = new Map();
2965
- const sourcesByCoord = new Map();
2966
- const coords = new Set();
2967
- for (const fn of finalNodes.values()) {
2968
- const c = fn.coord;
2969
- const coordKey = mavenCoordinateKey(c.group, c.name, c.ext, c.classifier, c.version);
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 top-level module is a `project` but usually not a dependency node, so its
2999
- // source roots (where reachability starts) are missed by the node loop above;
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
- const coordKey = mavenCoordinateKey(p.group, p.name, undefined, undefined, p.version);
3003
- if (!coordKey) {
3004
- continue;
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
- targetsByCoord,
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 (build-root-relative)
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] ?? '').buildFiles.push(f[2]);
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(parseRecords(text));
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
- // `.socket.facts.json`. `withFiles` (reachability only) additionally folds
3590
- // resolved artifact paths into `sidecarAcc`. A blocking resolution failure sets
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(dir, project.subprojectDir))));
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(`${dir}${path.sep}`)) {
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 .socket.facts.json per independent gradle/sbt/maven build
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 `.socket.facts.json` for a Gradle project via the bundled init script.
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 `.socket.facts.json` for a Maven project via the bundled extension.
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 `.socket.facts.json` for an sbt project via the bundled sbt plugin.
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 .socket.facts.json.
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
- // When using only pre-generated SBOMs, build the scan from those inputs —
7369
- // CycloneDX, SPDX, and Socket facts (`.socket.facts.json`) — matching
7370
- // Coana's `--use-only-pregenerated-sboms` selection. Otherwise drop any
7371
- // stray `.socket.facts.json`; coana's fresh reachability report (appended
7372
- // below) is the authoritative facts file for the scan.
7373
- const pathsForScan = reach.reachUseOnlyPregeneratedSboms ? filterToPregeneratedSboms(packagePaths, supportedFiles) : packagePaths.filter(p => path.basename(p) !== constants.default.DOT_SOCKET_DOT_FACTS_JSON);
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
- // On a successful scan, clean up the `.socket.facts.json` coana wrote at
7423
- // the path we instructed it to write to (via `--socket-mode`). Failed
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
- // Fail if any .socket.facts.json files are present in the scan folder.
9133
- // These are analysis artifacts and must be removed before re-running fix.
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: `Found ${constants.DOT_SOCKET_DOT_FACTS_JSON} in manifest files`,
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(isFactsFile);
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. IMPORTANT: you must delete this file before running a fresh full application reachability scan. A stale `.socket.facts.json` left in place is picked up as a pre-generated input and silently overrides fresh analysis, so the new scan results will not be reliable.'
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 (.socket.facts.json) for each,
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 (`.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
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 (`.socket.facts.json`)'
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 \`.socket.facts.json\` describing the resolved
13059
- dependency graph of the whole build, using gradle (preferably your local
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 (`.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
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 (`.socket.facts.json`)'
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 \`.socket.facts.json\` describing the resolved
13313
- dependency graph of the whole build, using gradle (preferably your local
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 \`.socket.facts.json\` describing the resolved dependency
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 (`.socket.facts.json`) describing the resolved dependency graph. This is the default; pass `--pom` to generate `pom.xml` files instead'
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 (`.socket.facts.json`)'
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 `.socket.facts.json`)'
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 \`.socket.facts.json\` describing the resolved
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 `.socket.facts.json` so that `socket scan create`
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 `.socket.facts.json`',
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 .socket.facts.json file describing the resolved dependency graph'
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 `.socket.facts.json` is
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 = fs$1.existsSync(path.join(cwd, constants.default.DOT_SOCKET_DOT_FACTS_JSON));
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=e70a53df-f766-401c-8399-eee0c4d48cc7
24508
+ //# debugId=679b39e8-2ace-4602-9ad0-c5cf11bec9e3
24502
24509
  //# sourceMappingURL=cli.js.map